diff --git a/camel/Camel.php b/camel/Camel.php index e18d1ef9..8b05cef1 100644 --- a/camel/Camel.php +++ b/camel/Camel.php @@ -19,9 +19,26 @@ class Camel * @var array */ public static array $groupFileNames = []; + + /** + * @deprecated Use the cacheDir() method instead + */ public static string $cacheDir = ".scribe/endpoints.cache"; + /** + * @deprecated Use the camelDir() method instead + */ public static string $camelDir = ".scribe/endpoints"; + public static function cacheDir(string $docsName = 'scribe') + { + return ".$docsName/endpoints.cache"; + } + + public static function camelDir(string $docsName = 'scribe') + { + return ".$docsName/endpoints"; + } + /** * Load endpoints from the Camel files into groups (arrays). * diff --git a/src/Commands/GenerateDocumentation.php b/src/Commands/GenerateDocumentation.php index 7c25e2b3..7d009479 100644 --- a/src/Commands/GenerateDocumentation.php +++ b/src/Commands/GenerateDocumentation.php @@ -5,6 +5,7 @@ use Illuminate\Console\Command; use Illuminate\Support\Arr; use Illuminate\Support\Facades\URL; +use Illuminate\Support\Str; use Knuckles\Camel\Camel; use Knuckles\Camel\Output\OutputEndpointData; use Knuckles\Scribe\Exceptions\GroupNotFound; @@ -23,7 +24,7 @@ class GenerateDocumentation extends Command {--force : Discard any changes you've made to the YAML or Markdown files} {--no-extraction : Skip extraction of route and API info and just transform the YAML and Markdown files into HTML} {--no-upgrade-check : Skip checking for config file upgrades. Won't make things faster, but can be helpful if the command is buggy} - {--config= : choose which config file to use} + {--config=scribe : choose which config file to use} "; protected $description = 'Generate API documentation from your Laravel/Dingo routes.'; @@ -34,6 +35,8 @@ class GenerateDocumentation extends Command private bool $forcing; + protected string $configName; + public function newLine($count = 1) { // TODO Remove when Laravel 6 is no longer supported @@ -45,9 +48,9 @@ public function handle(RouteMatcherInterface $routeMatcher, GroupedEndpointsFact { $this->bootstrap(); - $groupedEndpointsInstance = $groupedEndpointsFactory->make($this, $routeMatcher); + $groupedEndpointsInstance = $groupedEndpointsFactory->make($this, $routeMatcher, $this->configName); - $userDefinedEndpoints = Camel::loadUserDefinedEndpoints(Camel::$camelDir); + $userDefinedEndpoints = Camel::loadUserDefinedEndpoints(Camel::camelDir($this->configName)); $groupedEndpoints = $this->mergeUserDefinedEndpoints( $groupedEndpointsInstance->get(), $userDefinedEndpoints @@ -58,7 +61,7 @@ public function handle(RouteMatcherInterface $routeMatcher, GroupedEndpointsFact $this->writeExampleCustomEndpoint(); } - $writer = new Writer($this->docConfig); + $writer = new Writer($this->docConfig, $this->configName); $writer->writeDocs($groupedEndpoints); if ($groupedEndpointsInstance->hasEncounteredErrors()) { @@ -67,6 +70,8 @@ public function handle(RouteMatcherInterface $routeMatcher, GroupedEndpointsFact } $this->upgradeConfigFileIfNeeded(); + + $this->sayGoodbye(); } public function isForcing(): bool @@ -91,16 +96,17 @@ public function bootstrap(): void c::bootstrapOutput($this->output); - $this->docConfig = new DocumentationConfig(config('scribe')); - - if($this->option('config')){ - $config = config_path($this->option('config')).".php"; - if(!file_exists($config)){ - die("There is no suitable config found at {$config}\n"); + $this->configName = $this->option('config'); + if ($this->configName !== 'scribe') { + $configPath = config_path($this->configName) . ".php"; + if (!file_exists($configPath)) { + c::error("The specified config file doesn't exist: {$configPath}.\n"); + exit(1); } - $this->docConfig = new DocumentationConfig(config($this->option('config'))); } + $this->docConfig = new DocumentationConfig(config($this->configName)); + // Force root URL so it works in Postman collection $baseUrl = $this->docConfig->get('base_url') ?? config('app.url'); URL::forceRootUrl($baseUrl); @@ -109,7 +115,8 @@ public function bootstrap(): void $this->shouldExtract = !$this->option('no-extraction'); if ($this->forcing && !$this->shouldExtract) { - throw new \Exception("Can't use --force and --no-extraction together."); + c::error("Can't use --force and --no-extraction together.\n"); + exit(1); } // Reset this map (useful for tests) @@ -180,7 +187,7 @@ protected function mergeUserDefinedEndpoints(array $groupedEndpoints, array $use protected function writeExampleCustomEndpoint(): void { // We add an example to guide users in case they need to add a custom endpoint. - copy(__DIR__ . '/../../resources/example_custom_endpoint.yaml', Camel::$camelDir . '/custom.0.yaml'); + copy(__DIR__ . '/../../resources/example_custom_endpoint.yaml', Camel::camelDir($this->configName) . '/custom.0.yaml'); } protected function upgradeConfigFileIfNeeded(): void @@ -189,7 +196,7 @@ protected function upgradeConfigFileIfNeeded(): void $this->info("Checking for any pending upgrades to your config file..."); try { - $upgrader = Upgrader::ofConfigFile('config/scribe.php', __DIR__ . '/../../config/scribe.php') + $upgrader = Upgrader::ofConfigFile("config/{$this->configName}.php", __DIR__ . '/../../config/scribe.php') ->dontTouch( 'routes', 'example_languages', 'database_connections_to_transact', 'strategies', 'laravel.middleware', 'postman.overrides', 'openapi.overrides' @@ -222,4 +229,19 @@ protected function upgradeConfigFileIfNeeded(): void } } + + protected function sayGoodbye(): void + { + $message = 'All done. '; + if ($this->docConfig->get('type') == 'laravel') { + if ($this->docConfig->get('laravel.add_routes')) { + $message .= 'Visit your docs at ' . url($this->docConfig->get('laravel.docs_url')); + } + } else if (Str::endsWith(public_path(), 'public') && Str::startsWith($this->docConfig->get('static.output_path'), 'public/')) { + $message = 'Visit your docs at ' . url(str_replace('public/', '', $this->docConfig->get('static.output_path'))); + } + + $this->newLine(); + c::success($message); + } } diff --git a/src/Extracting/ApiDetails.php b/src/Extracting/ApiDetails.php index 1fa75c87..b20eb838 100644 --- a/src/Extracting/ApiDetails.php +++ b/src/Extracting/ApiDetails.php @@ -17,16 +17,17 @@ class ApiDetails private bool $preserveUserChanges; - private string $markdownOutputPath = '.scribe'; + private string $markdownOutputPath; private string $fileHashesTrackingFile; private array $lastKnownFileContentHashes = []; - public function __construct(DocumentationConfig $config = null, bool $preserveUserChanges = true) + public function __construct(DocumentationConfig $config = null, bool $preserveUserChanges = true, string $docsName = 'scribe') { + $this->markdownOutputPath = ".{$docsName}"; //.scribe by default // If no config is injected, pull from global. Makes testing easier. - $this->config = $config ?: new DocumentationConfig(config('scribe')); + $this->config = $config ?: new DocumentationConfig(config($docsName)); $this->baseUrl = $this->config->get('base_url') ?? config('app.url'); $this->preserveUserChanges = $preserveUserChanges; diff --git a/src/GroupedEndpoints/GroupedEndpointsFactory.php b/src/GroupedEndpoints/GroupedEndpointsFactory.php index b2565d31..f577338f 100644 --- a/src/GroupedEndpoints/GroupedEndpointsFactory.php +++ b/src/GroupedEndpoints/GroupedEndpointsFactory.php @@ -8,29 +8,30 @@ class GroupedEndpointsFactory { - public function make(GenerateDocumentation $command, RouteMatcherInterface $routeMatcher): GroupedEndpointsContract + public function make(GenerateDocumentation $command, RouteMatcherInterface $routeMatcher, string $docsName = 'scribe'): GroupedEndpointsContract { if ($command->isForcing()) { - return $this->makeGroupedEndpointsFromApp($command, $routeMatcher, false); + return $this->makeGroupedEndpointsFromApp($command, $routeMatcher, false, $docsName); } if ($command->shouldExtract()) { - return $this->makeGroupedEndpointsFromApp($command, $routeMatcher, true); + return $this->makeGroupedEndpointsFromApp($command, $routeMatcher, true, $docsName); } - return $this->makeGroupedEndpointsFromCamelDir(); + return $this->makeGroupedEndpointsFromCamelDir($docsName); } protected function makeGroupedEndpointsFromApp( GenerateDocumentation $command, RouteMatcherInterface $routeMatcher, - bool $preserveUserChanges + bool $preserveUserChanges, + string $docsName = 'scribe' ): GroupedEndpointsFromApp { - return new GroupedEndpointsFromApp($command, $routeMatcher, $preserveUserChanges); + return new GroupedEndpointsFromApp($command, $routeMatcher, $preserveUserChanges, $docsName); } - protected function makeGroupedEndpointsFromCamelDir(): GroupedEndpointsFromCamelDir + protected function makeGroupedEndpointsFromCamelDir(string $docsName = 'scribe'): GroupedEndpointsFromCamelDir { - return new GroupedEndpointsFromCamelDir(); + return new GroupedEndpointsFromCamelDir($docsName); } } diff --git a/src/GroupedEndpoints/GroupedEndpointsFromApp.php b/src/GroupedEndpoints/GroupedEndpointsFromApp.php index a2442f99..6979dac5 100644 --- a/src/GroupedEndpoints/GroupedEndpointsFromApp.php +++ b/src/GroupedEndpoints/GroupedEndpointsFromApp.php @@ -24,6 +24,7 @@ class GroupedEndpointsFromApp implements GroupedEndpointsContract { + protected string $docsName; private GenerateDocumentation $command; private RouteMatcherInterface $routeMatcher; private DocumentationConfig $docConfig; @@ -35,15 +36,19 @@ class GroupedEndpointsFromApp implements GroupedEndpointsContract private array $endpointGroupIndexes = []; - public function __construct(GenerateDocumentation $command, RouteMatcherInterface $routeMatcher, $preserveUserChanges) + public function __construct( + GenerateDocumentation $command, RouteMatcherInterface $routeMatcher, + bool $preserveUserChanges, string $docsName = 'scribe' + ) { $this->command = $command; $this->routeMatcher = $routeMatcher; $this->docConfig = $command->getDocConfig(); $this->preserveUserChanges = $preserveUserChanges; + $this->docsName = $docsName; - static::$camelDir = Camel::$camelDir; - static::$cacheDir = Camel::$cacheDir; + static::$camelDir = Camel::camelDir($this->docsName); + static::$cacheDir = Camel::cacheDir($this->docsName); } public function get(): array @@ -282,7 +287,7 @@ protected function extractAndWriteApiDetailsToDisk(): void protected function makeApiDetails(): ApiDetails { - return new ApiDetails($this->docConfig, !$this->command->option('force')); + return new ApiDetails($this->docConfig, !$this->command->option('force'), $this->docsName); } /** diff --git a/src/GroupedEndpoints/GroupedEndpointsFromCamelDir.php b/src/GroupedEndpoints/GroupedEndpointsFromCamelDir.php index 5459cf56..cb0fcc7e 100644 --- a/src/GroupedEndpoints/GroupedEndpointsFromCamelDir.php +++ b/src/GroupedEndpoints/GroupedEndpointsFromCamelDir.php @@ -7,15 +7,22 @@ class GroupedEndpointsFromCamelDir implements GroupedEndpointsContract { + protected string $docsName; + + public function __construct(string $docsName = 'scribe') + { + $this->docsName = $docsName; + } + public function get(): array { - if (!is_dir(Camel::$camelDir)) { + if (!is_dir(Camel::camelDir($this->docsName))) { throw new \InvalidArgumentException( - "Can't use --no-extraction because there are no endpoints in the " . Camel::$camelDir . " directory." + "Can't use --no-extraction because there are no endpoints in the " . Camel::camelDir($this->docsName) . " directory." ); } - return Camel::loadEndpointsIntoGroups(Camel::$camelDir); + return Camel::loadEndpointsIntoGroups(Camel::camelDir($this->docsName)); } public function hasEncounteredErrors(): bool diff --git a/src/Writing/Writer.php b/src/Writing/Writer.php index ac41e263..244214e5 100644 --- a/src/Writing/Writer.php +++ b/src/Writing/Writer.php @@ -15,7 +15,7 @@ class Writer private bool $isStatic; - private string $markdownOutputPath = '.scribe'; + private string $markdownOutputPath; private string $staticTypeOutputPath; @@ -29,15 +29,16 @@ class Writer 'js' => null, 'css' => null, 'images' => null, - ] + ], ]; private string $laravelAssetsPath; - public function __construct(DocumentationConfig $config = null) + public function __construct(DocumentationConfig $config = null, $docsName = 'scribe') { + $this->markdownOutputPath = ".{$docsName}"; //.scribe by default // If no config is injected, pull from global. Makes testing easier. - $this->config = $config ?: new DocumentationConfig(config('scribe')); + $this->config = $config ?: new DocumentationConfig(config($docsName)); $this->isStatic = $this->config->get('type') === 'static'; $this->staticTypeOutputPath = rtrim($this->config->get('static.output_path', 'public/docs'), '/'); @@ -178,7 +179,7 @@ protected function performFinalTasksForLaravelType(): void public function writeHtmlDocs(array $groupedEndpoints): void { - c::info('Writing HTML docs...'); + c::info('Writing ' . ($this->isStatic ? 'HTML' : 'Blade') . ' docs...'); // Then we convert them to HTML, and throw in the endpoints as well. /** @var HtmlWriter $writer */ @@ -191,14 +192,14 @@ public function writeHtmlDocs(array $groupedEndpoints): void if ($this->isStatic) { $outputPath = rtrim($this->staticTypeOutputPath, '/') . '/'; - c::success("Wrote HTML docs to: $outputPath"); + c::success("Wrote HTML docs and assets to: $outputPath"); $this->generatedFiles['html'] = realpath("{$outputPath}index.html"); $assetsOutputPath = $outputPath; } else { $outputPath = rtrim($this->laravelTypeOutputPath, '/') . '/'; c::success("Wrote Blade docs to: $outputPath"); $this->generatedFiles['blade'] = realpath("{$outputPath}index.blade.php"); - $assetsOutputPath = app()->get('path.public').$this->laravelAssetsPath; + $assetsOutputPath = app()->get('path.public') . $this->laravelAssetsPath; c::success("Wrote Laravel assets to: " . realpath($assetsOutputPath)); } $this->generatedFiles['assets']['js'] = realpath("{$assetsOutputPath}js");