diff --git a/config/scribe.php b/config/scribe.php index 425e1dae..c1503ca9 100644 --- a/config/scribe.php +++ b/config/scribe.php @@ -187,6 +187,13 @@ 'base_url' => null, ], + 'from_tests' => [ + /** + * Generate api documentation by running phpunit tests following "Test-Driven Documentation" principle. + */ + 'enabled' => false, + ], + /* * How is your API authenticated? This information will be used in the displayed docs, generated examples and response calls. */ diff --git a/src/GroupedEndpoints/GroupedEndpointsAbstract.php b/src/GroupedEndpoints/GroupedEndpointsAbstract.php new file mode 100644 index 00000000..97893d01 --- /dev/null +++ b/src/GroupedEndpoints/GroupedEndpointsAbstract.php @@ -0,0 +1,86 @@ +command = $command; + $this->routeMatcher = $routeMatcher; + $this->docConfig = $command->getDocConfig(); + + static::$camelDir = GenerateDocumentation::$camelDir; + static::$cacheDir = GenerateDocumentation::$cacheDir; + } + + public function get(): array + { + $groupedEndpoints = $this->extractEndpointsInfoAndWriteToDisk(); + $this->extractAndWriteApiDetailsToDisk(); + + return $groupedEndpoints; + } + + protected function extractAndWriteApiDetailsToDisk(): void + { + $apiDetails = new ApiDetails($this->docConfig, !$this->command->option('force')); + $apiDetails->writeMarkdownFiles(); + } + + protected function writeEndpointsToDisk(array $grouped): void + { + Utils::deleteFilesMatching(static::$camelDir, function (array $file) { + return !Str::startsWith($file['basename'], 'custom.'); + }); + Utils::deleteDirectoryAndContents(static::$cacheDir); + + if (!is_dir(static::$camelDir)) { + mkdir(static::$camelDir, 0777, true); + } + + if (!is_dir(static::$cacheDir)) { + mkdir(static::$cacheDir, 0777, 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 + ); + if ( + count(Camel::$groupFileNames) == count($grouped) + && isset(Camel::$groupFileNames[$group['name']]) + ) { + $fileName = Camel::$groupFileNames[$group['name']]; + } else { + $fileName = "$fileNameIndex.yaml"; + $fileNameIndex++; + } + + file_put_contents(static::$camelDir . "/$fileName", $yaml); + file_put_contents(static::$cacheDir . "/$fileName", "## Autogenerated by Scribe. DO NOT MODIFY.\n\n" . $yaml); + } + } + + abstract protected function extractEndpointsInfoAndWriteToDisk(): array; +} diff --git a/src/GroupedEndpoints/GroupedEndpointsFactory.php b/src/GroupedEndpoints/GroupedEndpointsFactory.php index 9f9fa28f..a2ec9731 100644 --- a/src/GroupedEndpoints/GroupedEndpointsFactory.php +++ b/src/GroupedEndpoints/GroupedEndpointsFactory.php @@ -10,14 +10,14 @@ class GroupedEndpointsFactory { public function make(GenerateDocumentation $command, RouteMatcherInterface $routeMatcher): GroupedEndpointsContract { - if ($command->isForcing()) { - return new GroupedEndpointsFromApp($command, $routeMatcher, false); + if ($command->option('no-extraction')) { + return new GroupedEndpointsFromCamelDir; } - if ($command->shouldExtract()) { - return new GroupedEndpointsFromApp($command, $routeMatcher, true); + if ($command->getDocConfig()->get('from_tests.enabled')) { + return new GroupedEndpointsFromTests($command, $routeMatcher); } - return new GroupedEndpointsFromCamelDir; + return new GroupedEndpointsFromApp($command, $routeMatcher, !$command->isForcing()); } } diff --git a/src/GroupedEndpoints/GroupedEndpointsFromApp.php b/src/GroupedEndpoints/GroupedEndpointsFromApp.php index b05fc49a..3aa4dfe3 100644 --- a/src/GroupedEndpoints/GroupedEndpointsFromApp.php +++ b/src/GroupedEndpoints/GroupedEndpointsFromApp.php @@ -22,17 +22,13 @@ use ReflectionClass; use Symfony\Component\Yaml\Yaml; -class GroupedEndpointsFromApp implements GroupedEndpointsContract +class GroupedEndpointsFromApp extends GroupedEndpointsAbstract implements GroupedEndpointsContract { private GenerateDocumentation $command; private RouteMatcherInterface $routeMatcher; private DocumentationConfig $docConfig; private bool $preserveUserChanges = true; private bool $encounteredErrors = false; - - public static string $camelDir; - public static string $cacheDir; - private array $endpointGroupIndexes = []; public function __construct(GenerateDocumentation $command, RouteMatcherInterface $routeMatcher, $preserveUserChanges) @@ -48,10 +44,9 @@ public function __construct(GenerateDocumentation $command, RouteMatcherInterfac public function get(): array { - $groupedEndpoints = $this->extractEndpointsInfoAndWriteToDisk($this->routeMatcher, $this->preserveUserChanges); - $this->extractAndWriteApiDetailsToDisk(); + parent::__construct($command, $routeMatcher); - return $groupedEndpoints; + $this->preserveUserChanges = $preserveUserChanges; } public function hasEncounteredErrors(): bool @@ -59,19 +54,19 @@ public function hasEncounteredErrors(): bool return $this->encounteredErrors; } - protected function extractEndpointsInfoAndWriteToDisk(RouteMatcherInterface $routeMatcher, bool $preserveUserChanges): array + protected function extractEndpointsInfoAndWriteToDisk(): array { $latestEndpointsData = []; $cachedEndpoints = []; $groups = []; - if ($preserveUserChanges && is_dir(static::$camelDir) && is_dir(static::$cacheDir)) { + if ($this->preserveUserChanges && is_dir(static::$camelDir) && is_dir(static::$cacheDir)) { $latestEndpointsData = Camel::loadEndpointsToFlatPrimitivesArray(static::$camelDir); $cachedEndpoints = Camel::loadEndpointsToFlatPrimitivesArray(static::$cacheDir, true); $groups = Camel::loadEndpointsIntoGroups(static::$camelDir); } - $routes = $routeMatcher->getRoutes($this->docConfig->get('routes'), $this->docConfig->get('router')); + $routes = $this->routeMatcher->getRoutes($this->docConfig->get('routes'), $this->docConfig->get('router')); $endpoints = $this->extractEndpointsInfoFromLaravelApp($routes, $cachedEndpoints, $latestEndpointsData, $groups); $groupedEndpoints = Camel::groupEndpoints($endpoints, $this->endpointGroupIndexes); $this->writeEndpointsToDisk($groupedEndpoints); @@ -188,40 +183,6 @@ private function mergeAnyEndpointDataUpdates(ExtractedEndpointData $endpointData return [$endpointData, $index]; } - protected function writeEndpointsToDisk(array $grouped): void - { - Utils::deleteFilesMatching(static::$camelDir, function (array $file) { - return !Str::startsWith($file['basename'], 'custom.'); - }); - Utils::deleteDirectoryAndContents(static::$cacheDir); - - if (!is_dir(static::$camelDir)) { - mkdir(static::$camelDir, 0777, true); - } - - if (!is_dir(static::$cacheDir)) { - mkdir(static::$cacheDir, 0777, 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 - ); - if (count(Camel::$groupFileNames) == count($grouped) - && isset(Camel::$groupFileNames[$group['name']])) { - $fileName = Camel::$groupFileNames[$group['name']]; - } else { - $fileName = "$fileNameIndex.yaml"; - $fileNameIndex++; - } - - file_put_contents(static::$camelDir . "/$fileName", $yaml); - file_put_contents(static::$cacheDir . "/$fileName", "## Autogenerated by Scribe. DO NOT MODIFY.\n\n" . $yaml); - } - } - private function isValidRoute(array $routeControllerAndMethod = null): bool { if (is_array($routeControllerAndMethod)) { @@ -277,10 +238,4 @@ protected function writeExampleCustomEndpoint(): void copy(__DIR__ . '/../../resources/example_custom_endpoint.yaml', static::$camelDir . '/custom.0.yaml'); } } - - protected function extractAndWriteApiDetailsToDisk(): void - { - $apiDetails = new ApiDetails($this->docConfig, !$this->command->option('force')); - $apiDetails->writeMarkdownFiles(); - } } diff --git a/src/GroupedEndpoints/GroupedEndpointsFromTests.php b/src/GroupedEndpoints/GroupedEndpointsFromTests.php new file mode 100644 index 00000000..04e9699a --- /dev/null +++ b/src/GroupedEndpoints/GroupedEndpointsFromTests.php @@ -0,0 +1,34 @@ +encounteredErrors; + } + + protected function extractEndpointsInfoAndWriteToDisk(): array + { + $routes = $this->routeMatcher->matchFromTestsOnly()->getRoutes($this->docConfig->get('routes'), $this->docConfig->get('router')); + $endpoints = $this->extractEndpointsInfoFromTests($routes); + $groupedEndpoints = Camel::groupEndpoints($endpoints, $this->endpointGroupIndexes); + $this->writeEndpointsToDisk($groupedEndpoints); + $groupedEndpoints = Camel::prepareGroupedEndpointsForOutput($groupedEndpoints); + return $groupedEndpoints; + } + + private function extractEndpointsInfoFromTests(array $routes): array + { + // TODO: code + return []; + } +} diff --git a/src/Matching/RouteMatcher.php b/src/Matching/RouteMatcher.php index 24aadb3e..d31d4449 100644 --- a/src/Matching/RouteMatcher.php +++ b/src/Matching/RouteMatcher.php @@ -9,6 +9,15 @@ class RouteMatcher implements RouteMatcherInterface { + private bool $matchFromTestsOnly = false; + + public function matchFromTestsOnly(bool $bool = true): self + { + $this->matchFromTestsOnly = $bool; + + return $this; + } + public function getRoutes(array $routeRules = [], string $router = 'laravel'): array { $usingDingoRouter = strtolower($router) == 'dingo'; @@ -34,7 +43,10 @@ private function getRoutesToBeDocumented(array $routeRules, bool $usingDingoRout continue; } - if ($this->shouldIncludeRoute($route, $routeRule, $includes, $usingDingoRouter)) { + if ( + $this->shouldIncludeRoute($route, $routeRule, $includes, $usingDingoRouter) + && $this->isMatchedFromTests($route) + ) { $matchedRoutes[] = new MatchedRoute($route, $routeRule['apply'] ?? []); } } @@ -93,4 +105,15 @@ private function shouldExcludeRoute(Route $route, array $routeRule): bool return Str::is($excludes, $route->getName()) || Str::is($excludes, $route->uri()); } + + private function isMatchedFromTests(Route $route): bool + { + if ($this->matchFromTestsOnly == false) { + return true; + } + + // TODO: Run phpunit tests first to list all the routes that are included + // and check if route is included from tests + return true; + } }