Skip to content

Commit

Permalink
Support external docs
Browse files Browse the repository at this point in the history
  • Loading branch information
shalvah committed Dec 25, 2023
1 parent ed7d87b commit 5714e3f
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 10 deletions.
3 changes: 3 additions & 0 deletions config/scribe.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,11 @@
// The type of documentation output to generate.
// - "static" will generate a static HTMl page in the /public/docs folder,
// - "laravel" will generate the documentation as a Blade view, so you can add routing and authentication.
// - "external_static" and "external_laravel" do the same as above, but generate a basic template,
// passing the OpenAPI spec as a URL, allowing you to easily use the docs with an external generator
'type' => 'static',

// See https://scribe.knuckles.wtf/laravel/reference/config#theme for supported options
'theme' => 'default',

'static' => [
Expand Down
13 changes: 13 additions & 0 deletions resources/views/external/rapidoc.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

<!doctype html> <!-- Important: must specify -->
<html>
<head>
<meta charset="utf-8"> <!-- Important: rapi-doc uses utf8 characters -->
<script type="module" src="https://unpkg.com/rapidoc/dist/rapidoc-min.js"></script>
</head>
<body>
<rapi-doc spec-url="{!! $metadata['openapi_spec_url'] !!}"
render-style="read"
> </rapi-doc>
</body>
</html>
23 changes: 23 additions & 0 deletions resources/views/external/scalar.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!doctype html>
<html>
<head>
<title>{!! $metadata['title'] !!}</title>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1" />
<style>
body {
margin: 0;
}
</style>
</head>
<body>

<script
id="api-reference"
data-url="{!! $metadata['openapi_spec_url'] !!}">
</script>
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
</body>
</html>
2 changes: 1 addition & 1 deletion src/Commands/GenerateDocumentation.php
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ protected function upgradeConfigFileIfNeeded(): void
protected function sayGoodbye(bool $errored = false): void
{
$message = 'All done. ';
if ($this->docConfig->get('type') == 'laravel') {
if ($this->docConfig->outputRoutedThroughLaravel()) {
if ($this->docConfig->get('laravel.add_routes')) {
$message .= 'Visit your docs at ' . url($this->docConfig->get('laravel.docs_url'));
}
Expand Down
16 changes: 16 additions & 0 deletions src/Config/Output.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,22 @@ public static function staticType(
return ['static', get_defined_vars()];
}

public static function externalStaticType(
string $outputPath = 'public/docs',
): array
{
return ['external_static', get_defined_vars()];
}

public static function externalLaravelType(
bool $addRoutes = true,
string $docsUrl = '/docs',
array $middleware = [],
): array
{
return ['external_laravel', get_defined_vars()];
}

public static function postman(
bool $enabled = true,
array $overrides = [],
Expand Down
6 changes: 4 additions & 2 deletions src/ScribeServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Knuckles\Scribe;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;
use Knuckles\Scribe\Commands\DiffConfig;
use Knuckles\Scribe\Commands\GenerateDocumentation;
use Knuckles\Scribe\Commands\MakeStrategy;
Expand Down Expand Up @@ -44,7 +45,7 @@ class_alias(\Illuminate\Support\Str::class, 'Str');
protected function bootRoutes()
{
if (
config('scribe.type', 'static') === 'laravel' &&
Str::endsWith(config('scribe.type', 'static'), 'laravel') &&
config('scribe.laravel.add_routes', false)
) {
$routesPath = Utils::isLumen() ? __DIR__ . '/../routes/lumen.php' : __DIR__ . '/../routes/laravel.php';
Expand All @@ -55,7 +56,7 @@ protected function bootRoutes()
protected function configureTranslations(): void
{
$this->publishes([
__DIR__.'/../lang/' => $this->app->langPath(),
__DIR__ . '/../lang/' => $this->app->langPath(),
], 'scribe-translations');

$this->loadTranslationsFrom($this->app->langPath('scribe.php'), 'scribe');
Expand All @@ -77,6 +78,7 @@ protected function registerViews(): void
'examples' => 'partials/example-requests',
'themes' => 'themes',
'markdown' => 'markdown',
'external' => 'external',
];
foreach ($viewGroups as $group => $path) {
$this->publishes([
Expand Down
16 changes: 16 additions & 0 deletions src/Tools/DocumentationConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Knuckles\Scribe\Tools;

use Illuminate\Support\Str;
use Knuckles\Scribe\Tools\ConsoleOutputUtils as c;

class DocumentationConfig
Expand Down Expand Up @@ -45,4 +46,19 @@ private function getRouter(array $config): string
return 'laravel';

}

public function outputIsStatic(): bool
{
return !$this->outputRoutedThroughLaravel();
}

public function outputRoutedThroughLaravel(): bool
{
return Str::is(['laravel', 'external_laravel'], $this->get('type'));
}

public function outputIsExternal(): bool
{
return Str::is(['external_static', 'external_laravel'], $this->get('type'));
}
}
47 changes: 47 additions & 0 deletions src/Writing/ExternalHtmlWriter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace Knuckles\Scribe\Writing;

use Illuminate\Support\Facades\View;

/**
* Writes a basic, mostly empty template, passing the OpenAPI spec URL in for an external client-side renderer.
*/
class ExternalHtmlWriter extends HtmlWriter
{
public function generate(array $groupedEndpoints, string $sourceFolder, string $destinationFolder)
{
$template = $this->config->get('theme');
$output = View::make("scribe::external.$template", [
'metadata' => $this->getMetadata(),
'baseUrl' => $this->baseUrl,
'tryItOut' => $this->config->get('try_it_out'),
])->render();

if (!is_dir($destinationFolder)) {
mkdir($destinationFolder, 0777, true);
}

file_put_contents($destinationFolder . '/index.html', $output);
}

public function getMetadata(): array
{
// NB:These paths are wrong for laravel type but will be set correctly by the Writer class
if ($this->config->get('postman.enabled', true)) {
$postmanCollectionUrl = "{$this->assetPathPrefix}collection.json";
}
if ($this->config->get('openapi.enabled', false)) {
$openApiSpecUrl = "{$this->assetPathPrefix}openapi.yaml";
}
return [
'title' => $this->config->get('title') ?: config('app.name', '') . ' Documentation',
'example_languages' => $this->config->get('example_languages'), // may be useful
'logo' => $this->config->get('logo') ?? false,
'last_updated' => $this->getLastUpdated(), // may be useful
'try_it_out' => $this->config->get('try_it_out'), // may be useful
"postman_collection_url" => $postmanCollectionUrl ?? null,
"openapi_spec_url" => $openApiSpecUrl ?? null,
];
}
}
48 changes: 41 additions & 7 deletions src/Writing/Writer.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Knuckles\Scribe\Writing;

use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Knuckles\Scribe\Tools\ConsoleOutputUtils as c;
use Knuckles\Scribe\Tools\DocumentationConfig;
use Knuckles\Scribe\Tools\Globals;
Expand All @@ -13,6 +14,7 @@
class Writer
{
private bool $isStatic;
private bool $isExternal;

private ?string $staticTypeOutputPath;

Expand All @@ -33,7 +35,9 @@ class Writer

public function __construct(protected DocumentationConfig $config, public PathConfig $paths)
{
$this->isStatic = $this->config->get('type') === 'static';
$this->isStatic = $this->config->outputIsStatic();
$this->isExternal = $this->config->outputIsExternal();

$this->laravelTypeOutputPath = $this->getLaravelTypeOutputPath();
$this->staticTypeOutputPath = rtrim($this->config->get('static.output_path', 'public/docs'), '/');

Expand All @@ -52,11 +56,15 @@ public function writeDocs(array $groupedEndpoints)
// For 'laravel' docs, the output files (index.blade.php, collection.json)
// go in resources/views/scribe/ and storage/app/scribe/ respectively.

$this->writeHtmlDocs($groupedEndpoints);

$this->writePostmanCollection($groupedEndpoints);

$this->writeOpenAPISpec($groupedEndpoints);
if ($this->isExternal) {
$this->writeOpenAPISpec($groupedEndpoints);
$this->writePostmanCollection($groupedEndpoints);
$this->writeExternalHtmlDocs();
} else {
$this->writeHtmlDocs($groupedEndpoints);
$this->writePostmanCollection($groupedEndpoints);
$this->writeOpenAPISpec($groupedEndpoints);
}

$this->runAfterGeneratingHook();
}
Expand All @@ -83,7 +91,7 @@ protected function writePostmanCollection(array $groups): void

protected function writeOpenAPISpec(array $parsedRoutes): void
{
if ($this->config->get('openapi.enabled', false)) {
if ($this->config->get('openapi.enabled', false) || $this->isExternal) {
c::info('Generating OpenAPI specification');

$spec = $this->generateOpenAPISpec($parsedRoutes);
Expand Down Expand Up @@ -169,6 +177,7 @@ protected function performFinalTasksForLaravelType(): void
$contents = preg_replace('#src="\.\./docs/(js|images)/(.+?)"#', 'src="{{ asset("' . $this->laravelAssetsPath . '/$1/$2") }}"', $contents);
$contents = str_replace('href="../docs/collection.json"', 'href="{{ route("' . $this->paths->outputPath('postman', '.') . '") }}"', $contents);
$contents = str_replace('href="../docs/openapi.yaml"', 'href="{{ route("' . $this->paths->outputPath('openapi', '.') . '") }}"', $contents);
$contents = str_replace('url="../docs/openapi.yaml"', 'url="{{ route("' . $this->paths->outputPath('openapi', '.') . '") }}"', $contents);

file_put_contents("$this->laravelTypeOutputPath/index.blade.php", $contents);
}
Expand Down Expand Up @@ -203,6 +212,31 @@ public function writeHtmlDocs(array $groupedEndpoints): void
$this->generatedFiles['assets']['images'] = realpath("{$assetsOutputPath}images");
}

public function writeExternalHtmlDocs(): void
{
c::info('Writing client-side HTML docs...');

/** @var ExternalHtmlWriter $writer */
$writer = app()->makeWith(ExternalHtmlWriter::class, ['config' => $this->config]);
$writer->generate([], $this->paths->intermediateOutputPath(), $this->staticTypeOutputPath);

if (!$this->isStatic) {
$this->performFinalTasksForLaravelType();
}

if ($this->isStatic) {
$outputPath = rtrim($this->staticTypeOutputPath, '/') . '/';
c::success("Wrote client-side HTML docs and assets to: $outputPath");
$this->generatedFiles['html'] = realpath("{$outputPath}index.html");
} else {
$outputPath = rtrim($this->laravelTypeOutputPath, '/') . '/';
c::success("Wrote Blade docs to: " . $this->makePathFriendly($outputPath));
$this->generatedFiles['blade'] = realpath("{$outputPath}index.blade.php");
$assetsOutputPath = public_path() . $this->laravelAssetsPath . '/';
c::success("Wrote Laravel assets to: " . $this->makePathFriendly($assetsOutputPath));
}
}

protected function runAfterGeneratingHook()
{
if (is_callable(Globals::$__afterGenerating)) {
Expand Down

0 comments on commit 5714e3f

Please sign in to comment.