update after stop build
All checks were successful
All checks were successful
This commit is contained in:
10
vendor/knuckleswtf/scribe/src/GroupedEndpoints/GroupedEndpointsContract.php
vendored
Normal file
10
vendor/knuckleswtf/scribe/src/GroupedEndpoints/GroupedEndpointsContract.php
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Knuckles\Scribe\GroupedEndpoints;
|
||||
|
||||
interface GroupedEndpointsContract
|
||||
{
|
||||
public function get(): array;
|
||||
|
||||
public function hasEncounteredErrors(): bool;
|
||||
}
|
||||
50
vendor/knuckleswtf/scribe/src/GroupedEndpoints/GroupedEndpointsFactory.php
vendored
Normal file
50
vendor/knuckleswtf/scribe/src/GroupedEndpoints/GroupedEndpointsFactory.php
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Knuckles\Scribe\GroupedEndpoints;
|
||||
|
||||
use Knuckles\Scribe\Commands\GenerateDocumentation;
|
||||
use Knuckles\Scribe\Matching\RouteMatcherInterface;
|
||||
use Knuckles\Scribe\Tools\PathConfig;
|
||||
|
||||
class GroupedEndpointsFactory
|
||||
{
|
||||
public function make(
|
||||
GenerateDocumentation $command,
|
||||
RouteMatcherInterface $routeMatcher,
|
||||
PathConfig $paths,
|
||||
): GroupedEndpointsContract {
|
||||
if ($command->isForcing()) {
|
||||
return static::fromApp(
|
||||
command: $command,
|
||||
routeMatcher: $routeMatcher,
|
||||
preserveUserChanges: false,
|
||||
paths: $paths
|
||||
);
|
||||
}
|
||||
|
||||
if ($command->shouldExtract()) {
|
||||
return static::fromApp(
|
||||
command: $command,
|
||||
routeMatcher: $routeMatcher,
|
||||
preserveUserChanges: true,
|
||||
paths: $paths
|
||||
);
|
||||
}
|
||||
|
||||
return static::fromCamelDir($paths);
|
||||
}
|
||||
|
||||
public static function fromApp(
|
||||
GenerateDocumentation $command,
|
||||
RouteMatcherInterface $routeMatcher,
|
||||
bool $preserveUserChanges,
|
||||
PathConfig $paths,
|
||||
): GroupedEndpointsFromApp {
|
||||
return new GroupedEndpointsFromApp($command, $routeMatcher, $paths, $preserveUserChanges);
|
||||
}
|
||||
|
||||
public static function fromCamelDir(PathConfig $paths): GroupedEndpointsFromCamelDir
|
||||
{
|
||||
return new GroupedEndpointsFromCamelDir($paths);
|
||||
}
|
||||
}
|
||||
309
vendor/knuckleswtf/scribe/src/GroupedEndpoints/GroupedEndpointsFromApp.php
vendored
Normal file
309
vendor/knuckleswtf/scribe/src/GroupedEndpoints/GroupedEndpointsFromApp.php
vendored
Normal file
@@ -0,0 +1,309 @@
|
||||
<?php
|
||||
|
||||
namespace Knuckles\Scribe\GroupedEndpoints;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Knuckles\Camel\Camel;
|
||||
use Knuckles\Camel\Extraction\ExtractedEndpointData;
|
||||
use Knuckles\Camel\Output\OutputEndpointData;
|
||||
use Knuckles\Scribe\Commands\GenerateDocumentation;
|
||||
use Knuckles\Scribe\Exceptions\CouldntGetRouteDetails;
|
||||
use Knuckles\Scribe\Extracting\ApiDetails;
|
||||
use Knuckles\Scribe\Extracting\Extractor;
|
||||
use Knuckles\Scribe\Matching\MatchedRoute;
|
||||
use Knuckles\Scribe\Matching\RouteMatcherInterface;
|
||||
use Knuckles\Scribe\Tools\ConsoleOutputUtils as c;
|
||||
use Knuckles\Scribe\Tools\DocumentationConfig;
|
||||
use Knuckles\Scribe\Tools\ErrorHandlingUtils as e;
|
||||
use Knuckles\Scribe\Tools\PathConfig;
|
||||
use Knuckles\Scribe\Tools\Utils;
|
||||
use Knuckles\Scribe\Tools\Utils as u;
|
||||
use Mpociot\Reflection\DocBlock;
|
||||
use Mpociot\Reflection\DocBlock\Tag;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class GroupedEndpointsFromApp implements GroupedEndpointsContract
|
||||
{
|
||||
public static string $camelDir;
|
||||
|
||||
public static string $cacheDir;
|
||||
|
||||
private DocumentationConfig $docConfig;
|
||||
|
||||
private bool $encounteredErrors = false;
|
||||
|
||||
public function __construct(
|
||||
private GenerateDocumentation $command,
|
||||
private RouteMatcherInterface $routeMatcher,
|
||||
protected PathConfig $paths,
|
||||
private bool $preserveUserChanges = true,
|
||||
) {
|
||||
$this->docConfig = $command->getDocConfig();
|
||||
|
||||
static::$camelDir = Camel::camelDir($this->paths);
|
||||
static::$cacheDir = Camel::cacheDir($this->paths);
|
||||
}
|
||||
|
||||
public function get(): array
|
||||
{
|
||||
$groupedEndpoints = $this->extractEndpointsInfoAndWriteToDisk($this->routeMatcher, $this->preserveUserChanges);
|
||||
$this->extractAndWriteApiDetailsToDisk();
|
||||
|
||||
return $groupedEndpoints;
|
||||
}
|
||||
|
||||
public function hasEncounteredErrors(): bool
|
||||
{
|
||||
return $this->encounteredErrors;
|
||||
}
|
||||
|
||||
protected function extractEndpointsInfoAndWriteToDisk(RouteMatcherInterface $routeMatcher, bool $preserveUserChanges): array
|
||||
{
|
||||
$latestEndpointsData = [];
|
||||
$cachedEndpoints = [];
|
||||
|
||||
if ($preserveUserChanges && is_dir(static::$camelDir) && is_dir(static::$cacheDir)) {
|
||||
$latestEndpointsData = Camel::loadEndpointsToFlatPrimitivesArray(static::$camelDir);
|
||||
$cachedEndpoints = Camel::loadEndpointsToFlatPrimitivesArray(static::$cacheDir);
|
||||
}
|
||||
|
||||
$routes = $routeMatcher->getRoutes($this->docConfig->get('routes', []));
|
||||
$endpoints = $this->extractEndpointsInfoFromLaravelApp($routes, $cachedEndpoints, $latestEndpointsData);
|
||||
|
||||
$groupedEndpoints = collect($endpoints)->groupBy('metadata.groupName')->map(function (Collection $endpointsInGroup) {
|
||||
return [
|
||||
'name' => $endpointsInGroup->first(function (ExtractedEndpointData $endpointData) {
|
||||
return ! empty($endpointData->metadata->groupName);
|
||||
})->metadata->groupName ?? '',
|
||||
'description' => $endpointsInGroup->first(function (ExtractedEndpointData $endpointData) {
|
||||
return ! empty($endpointData->metadata->groupDescription);
|
||||
})->metadata->groupDescription ?? '',
|
||||
'endpoints' => $endpointsInGroup->toArray(),
|
||||
];
|
||||
})->all();
|
||||
$this->writeEndpointsToDisk($groupedEndpoints);
|
||||
|
||||
return $groupedEndpoints;
|
||||
}
|
||||
|
||||
protected function writeEndpointsToDisk(array $grouped): void
|
||||
{
|
||||
Utils::deleteFilesMatching(static::$camelDir, function ($file) {
|
||||
/** @var array|\League\Flysystem\StorageAttributes $file */
|
||||
return ! Str::startsWith(basename($file['path']), 'custom.');
|
||||
});
|
||||
Utils::deleteDirectoryAndContents(static::$cacheDir);
|
||||
|
||||
if (! is_dir(static::$camelDir)) {
|
||||
mkdir(static::$camelDir, 0o777, true);
|
||||
}
|
||||
|
||||
if (! is_dir(static::$cacheDir)) {
|
||||
mkdir(static::$cacheDir, 0o777, true);
|
||||
}
|
||||
|
||||
$fileNameIndex = 0;
|
||||
foreach ($grouped as $group) {
|
||||
$yaml = Yaml::dump(
|
||||
$group,
|
||||
20,
|
||||
2,
|
||||
Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE | Yaml::DUMP_OBJECT_AS_MAP | Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK
|
||||
);
|
||||
|
||||
// Format numbers as two digits so they are sorted properly when retrieving later
|
||||
// (ie "10.yaml" comes after "9.yaml", not after "1.yaml")
|
||||
$fileName = sprintf('%02d.yaml', $fileNameIndex);
|
||||
$fileNameIndex++;
|
||||
|
||||
file_put_contents(static::$camelDir."/{$fileName}", $yaml);
|
||||
file_put_contents(static::$cacheDir."/{$fileName}", "## Autogenerated by Scribe. DO NOT MODIFY.\n\n".$yaml);
|
||||
}
|
||||
}
|
||||
|
||||
protected function extractAndWriteApiDetailsToDisk(): void
|
||||
{
|
||||
$apiDetails = $this->makeApiDetails();
|
||||
|
||||
$apiDetails->writeMarkdownFiles();
|
||||
}
|
||||
|
||||
protected function makeApiDetails(): ApiDetails
|
||||
{
|
||||
return new ApiDetails($this->paths, $this->docConfig, ! $this->command->option('force'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a new extractor.
|
||||
*/
|
||||
protected function makeExtractor(): Extractor
|
||||
{
|
||||
return new Extractor($this->docConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param MatchedRoute[] $matches
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function extractEndpointsInfoFromLaravelApp(array $matches, array $cachedEndpoints, array $latestEndpointsData): array
|
||||
{
|
||||
$extractor = $this->makeExtractor();
|
||||
|
||||
$parsedEndpoints = [];
|
||||
|
||||
foreach ($matches as $routeItem) {
|
||||
$route = $routeItem->getRoute();
|
||||
|
||||
$routeControllerAndMethod = u::getRouteClassAndMethodNames($route);
|
||||
if (! $this->isValidRoute($routeControllerAndMethod)) {
|
||||
c::warn('Skipping invalid route: '.c::getRouteRepresentation($route));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! $this->doesControllerMethodExist($routeControllerAndMethod)) {
|
||||
c::warn('Skipping route: '.c::getRouteRepresentation($route).' - Controller method does not exist.');
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->isRouteHiddenFromDocumentation($routeControllerAndMethod)) {
|
||||
c::warn('Skipping route: '.c::getRouteRepresentation($route).': @hideFromAPIDocumentation was specified.');
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// Start buffering warnings so they don't break the task output.
|
||||
c::startWarningBuffer();
|
||||
|
||||
$this->command->outputComponents()->task(
|
||||
c::getRouteRepresentation($route),
|
||||
function () use ($extractor, $route, $routeItem, $cachedEndpoints, $latestEndpointsData, &$parsedEndpoints) {
|
||||
$currentEndpointData = $extractor->processRoute($route, $routeItem->getRules());
|
||||
// If latest data is different from cached data, merge latest into current.
|
||||
$currentEndpointData = $this->mergeAnyEndpointDataUpdates($currentEndpointData, $cachedEndpoints, $latestEndpointsData);
|
||||
$parsedEndpoints[] = $currentEndpointData;
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
// Flush buffered warnings after a task completes.
|
||||
c::flushWarningBuffer();
|
||||
} catch (\Exception $exception) {
|
||||
$this->encounteredErrors = true;
|
||||
c::flushWarningBuffer(); // Flush warnings even in error.
|
||||
e::dumpExceptionIfVerbose($exception);
|
||||
}
|
||||
}
|
||||
|
||||
return $parsedEndpoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array[] $cachedEndpoints
|
||||
* @param array[] $latestEndpointsData
|
||||
* @return ExtractedEndpointData The extracted endpoint data
|
||||
*/
|
||||
private function mergeAnyEndpointDataUpdates(ExtractedEndpointData $endpointData, array $cachedEndpoints, array $latestEndpointsData): ExtractedEndpointData
|
||||
{
|
||||
// First, find the corresponding endpoint in cached and latest
|
||||
$thisEndpointCached = Arr::first($cachedEndpoints, function (array $endpoint) use ($endpointData) {
|
||||
return $endpoint['uri'] === $endpointData->uri && $endpoint['httpMethods'] === $endpointData->httpMethods;
|
||||
});
|
||||
if (! $thisEndpointCached) {
|
||||
return $endpointData;
|
||||
}
|
||||
|
||||
$thisEndpointLatest = Arr::first($latestEndpointsData, function (array $endpoint) use ($endpointData) {
|
||||
return $endpoint['uri'] === $endpointData->uri && $endpoint['httpMethods'] === $endpointData->httpMethods;
|
||||
});
|
||||
if (! $thisEndpointLatest) {
|
||||
return $endpointData;
|
||||
}
|
||||
|
||||
// Then compare cached and latest to see what sections changed.
|
||||
$properties = [
|
||||
'metadata',
|
||||
'headers',
|
||||
'urlParameters',
|
||||
'queryParameters',
|
||||
'bodyParameters',
|
||||
'responses',
|
||||
'responseFields',
|
||||
];
|
||||
|
||||
$changed = [];
|
||||
foreach ($properties as $property) {
|
||||
if ($thisEndpointCached[$property] !== $thisEndpointLatest[$property]) {
|
||||
$changed[] = $property;
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, merge any changed sections.
|
||||
$thisEndpointLatest = OutputEndpointData::create($thisEndpointLatest);
|
||||
foreach ($changed as $property) {
|
||||
$endpointData->{$property} = $thisEndpointLatest->{$property};
|
||||
}
|
||||
|
||||
return $endpointData;
|
||||
}
|
||||
|
||||
private function isValidRoute(?array $routeControllerAndMethod): bool
|
||||
{
|
||||
if (is_array($routeControllerAndMethod)) {
|
||||
if (count($routeControllerAndMethod) < 2) {
|
||||
throw CouldntGetRouteDetails::new();
|
||||
}
|
||||
[$classOrObject, $method] = $routeControllerAndMethod;
|
||||
if (u::isInvokableObject($classOrObject)) {
|
||||
return true;
|
||||
}
|
||||
$routeControllerAndMethod = $classOrObject.'@'.$method;
|
||||
}
|
||||
|
||||
return ! is_callable($routeControllerAndMethod) && ! is_null($routeControllerAndMethod);
|
||||
}
|
||||
|
||||
private function doesControllerMethodExist(array $routeControllerAndMethod): bool
|
||||
{
|
||||
if (count($routeControllerAndMethod) < 2) {
|
||||
throw CouldntGetRouteDetails::new();
|
||||
}
|
||||
[$class, $method] = $routeControllerAndMethod;
|
||||
$reflection = new \ReflectionClass($class);
|
||||
|
||||
if ($reflection->hasMethod($method)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function isRouteHiddenFromDocumentation(array $routeControllerAndMethod): bool
|
||||
{
|
||||
if (! ($class = $routeControllerAndMethod[0]) instanceof \Closure) {
|
||||
$classDocBlock = new DocBlock((new \ReflectionClass($class))->getDocComment() ?: '');
|
||||
$shouldIgnoreClass = collect($classDocBlock->getTags())
|
||||
->filter(function (Tag $tag) {
|
||||
return Str::lower($tag->getName()) === 'hidefromapidocumentation';
|
||||
})->isNotEmpty();
|
||||
|
||||
if ($shouldIgnoreClass) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$methodDocBlock = new DocBlock(u::getReflectedRouteMethod($routeControllerAndMethod)->getDocComment() ?: '');
|
||||
$shouldIgnoreMethod = collect($methodDocBlock->getTags())
|
||||
->filter(function (Tag $tag) {
|
||||
return Str::lower($tag->getName()) === 'hidefromapidocumentation';
|
||||
})->isNotEmpty();
|
||||
|
||||
return $shouldIgnoreMethod;
|
||||
}
|
||||
}
|
||||
27
vendor/knuckleswtf/scribe/src/GroupedEndpoints/GroupedEndpointsFromCamelDir.php
vendored
Normal file
27
vendor/knuckleswtf/scribe/src/GroupedEndpoints/GroupedEndpointsFromCamelDir.php
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Knuckles\Scribe\GroupedEndpoints;
|
||||
|
||||
use Knuckles\Camel\Camel;
|
||||
use Knuckles\Scribe\Tools\PathConfig;
|
||||
|
||||
class GroupedEndpointsFromCamelDir implements GroupedEndpointsContract
|
||||
{
|
||||
public function __construct(protected PathConfig $paths) {}
|
||||
|
||||
public function get(): array
|
||||
{
|
||||
if (! is_dir(Camel::camelDir($this->paths))) {
|
||||
throw new \InvalidArgumentException(
|
||||
"Can't use --no-extraction because there are no endpoints in the ".Camel::camelDir($this->paths).' directory.'
|
||||
);
|
||||
}
|
||||
|
||||
return Camel::loadEndpointsIntoGroups(Camel::camelDir($this->paths));
|
||||
}
|
||||
|
||||
public function hasEncounteredErrors(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user