diff --git a/.gitignore b/.gitignore index fb051901..dea44278 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,6 @@ /build/out /build/*.phar /tests/statics/index.* -src/Michelf/* src/.gitignore vendor/** diff --git a/phpdraft b/phpdraft index 90ea0f77..a1468992 100755 --- a/phpdraft +++ b/phpdraft @@ -28,6 +28,7 @@ try ->opt('help:h', 'This help text', false) ->opt('version:v', 'Print the version for PHPDraft.', false) ->opt('file:f', 'Specifies the file to parse.', false) + ->opt('openapi:a', 'Output location for an OpenAPI file.', false) ->opt('yes:y', 'Always accept using the online mode.', false, 'bool') ->opt('online:o', 'Always use the online mode.', false, 'bool') ->opt('template:t', 'Specifies the template to use. (defaults to \'default\').', false) @@ -90,6 +91,11 @@ try $data = json_decode($json_string); } + if (isset($args['openapi'])) { + $openapi = ParserFactory::getOpenAPI()->init($data); + $openapi->write($args['openapi']); + } + $html = ParserFactory::getJson()->init($data); $name = 'PHPD_SORT_' . strtoupper($args->getOpt('sort', '')); $html->sorting = Sorting::${$name} ?? Sorting::PHPD_SORT_NONE->value; diff --git a/src/PHPDraft/Model/HTTPRequest.php b/src/PHPDraft/Model/HTTPRequest.php index c551184f..1b88318c 100644 --- a/src/PHPDraft/Model/HTTPRequest.php +++ b/src/PHPDraft/Model/HTTPRequest.php @@ -43,7 +43,7 @@ class HTTPRequest implements Comparable * * @var string */ - public string $description; + public string $description = ''; /** * Parent class. diff --git a/src/PHPDraft/Model/Transition.php b/src/PHPDraft/Model/Transition.php index 8158f94e..2d488197 100644 --- a/src/PHPDraft/Model/Transition.php +++ b/src/PHPDraft/Model/Transition.php @@ -23,9 +23,9 @@ class Transition extends HierarchyElement /** * HTTP method used. * - * @var string + * @var string|null */ - public string $method; + public ?string $method = NULL; /** * URI. diff --git a/src/PHPDraft/Out/BaseTemplateRenderer.php b/src/PHPDraft/Out/BaseTemplateRenderer.php index b8052cad..5532a169 100644 --- a/src/PHPDraft/Out/BaseTemplateRenderer.php +++ b/src/PHPDraft/Out/BaseTemplateRenderer.php @@ -24,36 +24,21 @@ abstract class BaseTemplateRenderer * @var int */ public int $sorting; + /** - * CSS Files to load. - * - * @var string[] - */ - public array $css = []; - /** - * JS Files to load. - * - * @var string[] - */ - public array $js = []; - /** - * The image to use as a logo. - * - * @var string|null - */ - protected ?string $image = null; - /** - * The template file to load. + * JSON representation of an API Blueprint. * - * @var string + * @var object */ - protected string $template; + protected object $object; + /** * The base data of the API. * * @var array */ - protected array $base_data; + protected array $base_data = []; + /** * JSON object of the API blueprint. * @@ -66,4 +51,39 @@ abstract class BaseTemplateRenderer * @var ObjectStructureElement[] */ protected array $base_structures = []; + + /** + * Parse base data + * + * @param object $object + */ + protected function parse_base_data(object $object): void + { + //Prepare base data + if (!is_array($object->content[0]->content)) { + return; + } + + $this->base_data['TITLE'] = $object->content[0]->meta->title->content ?? ''; + + foreach ($object->content[0]->attributes->metadata->content as $meta) { + $this->base_data[$meta->content->key->content] = $meta->content->value->content; + } + + foreach ($object->content[0]->content as $value) { + if ($value->element === 'copy') { + $this->base_data['DESC'] = $value->content; + continue; + } + + $cat = new Category(); + $cat = $cat->parse($value); + + if (($value->meta->classes->content[0]->content ?? null) === 'dataStructures') { + $this->base_structures = array_merge($this->base_structures, $cat->structures); + } else { + $this->categories[] = $cat; + } + } + } } diff --git a/src/PHPDraft/Out/TemplateRenderer.php b/src/PHPDraft/Out/TemplateRenderer.php deleted file mode 100644 index 7ad6a025..00000000 --- a/src/PHPDraft/Out/TemplateRenderer.php +++ /dev/null @@ -1,231 +0,0 @@ - - */ - -namespace PHPDraft\Out; - -use Lukasoppermann\Httpstatus\Httpstatus; -use PHPDraft\Model\Category; -use PHPDraft\Model\Elements\ArrayStructureElement; -use PHPDraft\Model\Elements\ElementStructureElement; -use PHPDraft\Model\Elements\EnumStructureElement; -use PHPDraft\Model\Elements\ObjectStructureElement; -use PHPDraft\Parse\ExecutionException; -use Twig\Environment; -use Twig\Extra\Markdown\DefaultMarkdown; -use Twig\Extra\Markdown\MarkdownExtension; -use Twig\Extra\Markdown\MarkdownRuntime; -use Twig\Loader\FilesystemLoader; -use Twig\RuntimeLoader\RuntimeLoaderInterface; -use Twig\TwigFilter; -use Twig\TwigTest; - -class TemplateRenderer extends BaseTemplateRenderer -{ - /** - * TemplateGenerator constructor. - * - * @param string $template Name of the template to load - * @param string|null $image Image to use as Logo - */ - public function __construct(string $template, ?string $image) - { - $template_parts = explode('__', $template); - $this->template = $template_parts[0]; - $this->base_data['COLOR_1'] = $template_parts[1] ?? 'green'; - $this->base_data['COLOR_2'] = $template_parts[2] ?? 'light_green'; - $this->image = $image; - $this->sorting = Sorting::PHPD_SORT_NONE->value; - } - - /** - * Pre-parse objects needed and print HTML. - * - * @param object $object JSON to parse from - * - * @return string - * - * @throws ExecutionException When template is not found - * - * @throws \Twig\Error\LoaderError - * @throws \Twig\Error\RuntimeError - * @throws \Twig\Error\SyntaxError - */ - public function get(object $object): string - { - $include = $this->find_include_file($this->template, 'twig', false); - if ($include === null) { - throw new ExecutionException("Couldn't find template '$this->template'", 1); - } - - $this->parse_base_data($object); - - if (Sorting::sortStructures($this->sorting)) { - ksort($this->base_structures); - } - - if (Sorting::sortServices($this->sorting)) { - usort($this->categories, fn($a, $b) => strcmp($a->title, $b->title)); - foreach ($this->categories as $category) { - usort($category->children, fn($a, $b)=> strcmp($a->title, $b->title)); - } - } - - $loader = new FilesystemLoader([]); - $loader->addPath(stream_resolve_include_path(dirname($include))); - $loader->addPath(stream_resolve_include_path(dirname($this->find_include_file('default', 'twig')))); - - $twig = TwigFactory::get($loader); - $template = $twig->load('main.twig'); - - $extras = array_filter( - $this->base_data, - fn($value) => !in_array($value, ['HOST', 'TITLE', 'ALT_HOST', 'FORMAT', 'DESC', 'COLOR_1', 'COLOR_2'], true), - ARRAY_FILTER_USE_KEY - ); - $extras['host'] = $this->base_data['HOST'] ?? null; - - return $template->render([ - 'data' => $this->base_data, - 'extra_data' => $extras, - 'structures' => $this->base_structures, - 'categories' => $this->categories, - 'js' => $this->js, - 'css' => $this->css, - 'image' => $this->image, - 'template_css' => file_get_contents($this->find_include_file($this->template, 'css'), true), - 'template_js' => file_get_contents($this->find_include_file($this->template, 'js'), true), - ]); - } - - /** - * Parse base data - * - * @param object $object - */ - private function parse_base_data(object $object): void - { - //Prepare base data - if (!is_array($object->content[0]->content)) { - return; - } - - $this->base_data['TITLE'] = $object->content[0]->meta->title->content ?? ''; - - foreach ($object->content[0]->attributes->metadata->content as $meta) { - $this->base_data[$meta->content->key->content] = $meta->content->value->content; - } - - foreach ($object->content[0]->content as $value) { - if ($value->element === 'copy') { - $this->base_data['DESC'] = $value->content; - continue; - } - - $cat = new Category(); - $cat = $cat->parse($value); - - if (($value->meta->classes->content[0]->content ?? null) === 'dataStructures') { - $this->base_structures = array_merge($this->base_structures, $cat->structures); - } else { - $this->categories[] = $cat; - } - } - } - - /** - * Get the path to a file to include. - * - * @param string $template The name of the template to include - * @param string $extension Extension of the file to include - * - * @return null|string File path or null if not found - */ - public function find_include_file(string $template, string $extension = 'twig', bool $fallback = true): ?string - { - $includes = [ - 'templates' . DIRECTORY_SEPARATOR . $template . DIRECTORY_SEPARATOR . $template . ".{$extension}", - 'templates' . DIRECTORY_SEPARATOR . $template . DIRECTORY_SEPARATOR . 'main' . ".{$extension}", - 'templates' . DIRECTORY_SEPARATOR . $template . ".{$extension}", - $template . DIRECTORY_SEPARATOR . $template . ".{$extension}", - $template . ".{$extension}", - 'PHPDraft/Out/HTML/' . $template . DIRECTORY_SEPARATOR . $template . ".{$extension}", - 'PHPDraft/Out/HTML/' . $template . DIRECTORY_SEPARATOR . 'main' . ".{$extension}", - ]; - foreach ($includes as $include) { - if (!stream_resolve_include_path($include)) { - continue; - } - return $include; - } - - if (in_array($extension, ['twig', 'js', 'css'], true) && $template !== 'default' && $fallback === true) { - return $this->find_include_file('default', $extension); - } - - return null; - } - - /** - * Get an icon for a specific HTTP Method. - * - * @param string $method HTTP method - * - * @return string class to represent the HTTP Method - */ - public static function get_method_icon(string $method): string - { - $class = ['fas', strtoupper($method)]; - $class[] = match (strtoupper($method)) { - 'POST' => 'fa-plus-square', - 'PUT' => 'fa-pen-square', - 'GET' => 'fa-arrow-circle-down', - 'DELETE' => 'fa-minus-square', - 'HEAD' => 'fa-info', - 'CONNECT' => 'fa-ethernet', - 'OPTIONS' => 'fa-sliders-h', - 'TRACE' => 'fa-route', - 'PATCH' => 'fa-band-aid', - default => '' - }; - - return trim(join(' ', $class)); - } - - /** - * Get a bootstrap class to represent the HTTP return code range. - * - * @param int $response HTTP return code - * - * @return string Class to use - */ - public static function get_response_status(int $response): string - { - $http = new Httpstatus(); - return match ($http->getResponseClass($response)) { - Httpstatus::CLASS_SUCCESS => 'text-success', - Httpstatus::CLASS_REDIRECTION => 'text-warning', - default => 'text-error', - }; - } - - /** - * Strip spaces from links to objects. - * - * @param string $key key with potential spaces - * - * @return string key without spaces - */ - public static function strip_link_spaces(string $key): string - { - return str_replace(' ', '-', strtolower($key)); - } -} diff --git a/src/PHPDraft/Out/Tests/TemplateRendererTest.php b/src/PHPDraft/Out/Tests/TemplateRendererTest.php index 72863cad..c47fb1c5 100644 --- a/src/PHPDraft/Out/Tests/TemplateRendererTest.php +++ b/src/PHPDraft/Out/Tests/TemplateRendererTest.php @@ -10,17 +10,17 @@ namespace PHPDraft\Out\Tests; use Lunr\Halo\LunrBaseTest; -use PHPDraft\Out\TemplateRenderer; +use PHPDraft\Out\HtmlTemplateRenderer; /** * Class TemplateGeneratorTest * - * @covers \PHPDraft\Out\TemplateRenderer + * @covers \PHPDraft\Out\HtmlTemplateRenderer */ class TemplateRendererTest extends LunrBaseTest { /** - * @var TemplateRenderer + * @var HtmlTemplateRenderer */ protected $class; @@ -30,14 +30,14 @@ class TemplateRendererTest extends LunrBaseTest */ public function setUp(): void { - $this->class = new TemplateRenderer('default', 'none'); - $this->reflection = new \ReflectionClass('PHPDraft\Out\TemplateRenderer'); + $this->class = new HtmlTemplateRenderer('default', 'none'); + $this->reflection = new \ReflectionClass('PHPDraft\Out\HtmlTemplateRenderer'); } /** * Test if the value the class is initialized with is correct * - * @covers \PHPDraft\Out\TemplateRenderer + * @covers \PHPDraft\Out\HtmlTemplateRenderer */ public function testSetupCorrectly(): void { @@ -48,7 +48,7 @@ public function testSetupCorrectly(): void /** * Test if the value the class is initialized with is correct * - * @covers \PHPDraft\Out\TemplateRenderer::strip_link_spaces + * @covers \PHPDraft\Out\HtmlTemplateRenderer::strip_link_spaces */ public function testStripSpaces(): void { @@ -81,11 +81,11 @@ public static function responseStatusProvider(): array * @param int $code HTTP code * @param string $text Class to return * - * @covers \PHPDraft\Out\TemplateRenderer::get_response_status + * @covers \PHPDraft\Out\HtmlTemplateRenderer::get_response_status */ public function testResponseStatus(int $code, string $text): void { - $return = TemplateRenderer::get_response_status($code); + $return = HtmlTemplateRenderer::get_response_status($code); $this->assertEquals($text, $return); } @@ -120,18 +120,18 @@ public static function requestMethodProvider(): array * @param string $method HTTP Method * @param string $text Class to return * - * @covers \PHPDraft\Out\TemplateRenderer::get_method_icon + * @covers \PHPDraft\Out\HtmlTemplateRenderer::get_method_icon */ public function testRequestMethod(string $method, string $text): void { - $return = TemplateRenderer::get_method_icon($method); + $return = HtmlTemplateRenderer::get_method_icon($method); $this->assertEquals($text, $return); } /** * Test if the value the class is initialized with is correct * - * @covers \PHPDraft\Out\TemplateRenderer::find_include_file + * @covers \PHPDraft\Out\HtmlTemplateRenderer::find_include_file */ public function testIncludeFileDefault(): void { @@ -142,7 +142,7 @@ public function testIncludeFileDefault(): void /** * Test if the value the class is initialized with is correct * - * @covers \PHPDraft\Out\TemplateRenderer::find_include_file + * @covers \PHPDraft\Out\HtmlTemplateRenderer::find_include_file */ public function testIncludeFileFallback(): void { @@ -153,7 +153,7 @@ public function testIncludeFileFallback(): void /** * Test if the value the class is initialized with is correct * - * @covers \PHPDraft\Out\TemplateRenderer::find_include_file + * @covers \PHPDraft\Out\HtmlTemplateRenderer::find_include_file */ public function testIncludeFileNone(): void { @@ -164,7 +164,7 @@ public function testIncludeFileNone(): void /** * Test if the value the class is initialized with is correct * - * @covers \PHPDraft\Out\TemplateRenderer::find_include_file + * @covers \PHPDraft\Out\HtmlTemplateRenderer::find_include_file */ public function testIncludeFileSingle(): void { @@ -176,7 +176,7 @@ public function testIncludeFileSingle(): void /** * Test if the value the class is initialized with is correct * - * @covers \PHPDraft\Out\TemplateRenderer::find_include_file + * @covers \PHPDraft\Out\HtmlTemplateRenderer::find_include_file */ public function testIncludeFileMultiple(): void { @@ -192,7 +192,7 @@ public function testIncludeFileMultiple(): void } /** - * @covers \PHPDraft\Out\TemplateRenderer::get + * @covers \PHPDraft\Out\HtmlTemplateRenderer::get */ public function testGetTemplateFailsEmpty(): void { @@ -205,7 +205,7 @@ public function testGetTemplateFailsEmpty(): void } /** - * @covers \PHPDraft\Out\TemplateRenderer::get + * @covers \PHPDraft\Out\HtmlTemplateRenderer::get * @group twig */ public function testGetTemplate(): void @@ -216,7 +216,7 @@ public function testGetTemplate(): void } /** - * @covers \PHPDraft\Out\TemplateRenderer::get + * @covers \PHPDraft\Out\HtmlTemplateRenderer::get * @group twig */ public function testGetTemplateSorting(): void @@ -228,7 +228,7 @@ public function testGetTemplateSorting(): void } /** - * @covers \PHPDraft\Out\TemplateRenderer::get + * @covers \PHPDraft\Out\HtmlTemplateRenderer::get * @group twig */ public function testGetTemplateMetaData(): void @@ -247,7 +247,7 @@ public function testGetTemplateMetaData(): void } /** - * @covers \PHPDraft\Out\TemplateRenderer::get + * @covers \PHPDraft\Out\HtmlTemplateRenderer::get * @group twig */ public function testGetTemplateCategories(): void diff --git a/src/PHPDraft/Out/TwigFactory.php b/src/PHPDraft/Out/TwigFactory.php index 725cbe89..102ed100 100644 --- a/src/PHPDraft/Out/TwigFactory.php +++ b/src/PHPDraft/Out/TwigFactory.php @@ -25,9 +25,9 @@ public static function get(LoaderInterface $loader): Environment { $twig = new Environment($loader); - $twig->addFilter(new TwigFilter('method_icon', fn(string $string) => TemplateRenderer::get_method_icon($string))); - $twig->addFilter(new TwigFilter('strip_link_spaces', fn(string $string) => TemplateRenderer::strip_link_spaces($string))); - $twig->addFilter(new TwigFilter('response_status', fn(string $string) => TemplateRenderer::get_response_status((int) $string))); + $twig->addFilter(new TwigFilter('method_icon', fn(string $string) => HtmlTemplateRenderer::get_method_icon($string))); + $twig->addFilter(new TwigFilter('strip_link_spaces', fn(string $string) => HtmlTemplateRenderer::strip_link_spaces($string))); + $twig->addFilter(new TwigFilter('response_status', fn(string $string) => HtmlTemplateRenderer::get_response_status((int) $string))); $twig->addFilter(new TwigFilter('status_reason', fn(int $code) => (new Httpstatus())->getReasonPhrase($code))); $twig->addFilter(new TwigFilter('minify_css', function (string $string) { $minify = new Css(); diff --git a/src/PHPDraft/Parse/BaseHtmlGenerator.php b/src/PHPDraft/Parse/BaseHtmlGenerator.php index ab3e25b7..37d750cf 100644 --- a/src/PHPDraft/Parse/BaseHtmlGenerator.php +++ b/src/PHPDraft/Parse/BaseHtmlGenerator.php @@ -12,8 +12,6 @@ namespace PHPDraft\Parse; -use PHPDraft\Out\BaseTemplateRenderer; -use stdClass; use \Stringable; abstract class BaseHtmlGenerator implements Stringable diff --git a/src/PHPDraft/Parse/HtmlGenerator.php b/src/PHPDraft/Parse/HtmlGenerator.php index 3dc813ce..b8a1e6a5 100644 --- a/src/PHPDraft/Parse/HtmlGenerator.php +++ b/src/PHPDraft/Parse/HtmlGenerator.php @@ -12,7 +12,7 @@ namespace PHPDraft\Parse; -use PHPDraft\Out\TemplateRenderer; +use PHPDraft\Out\HtmlTemplateRenderer; /** * Class HtmlGenerator. @@ -36,7 +36,7 @@ class HtmlGenerator extends BaseHtmlGenerator */ public function build_html(string $template = 'default', ?string $image = null, ?string $css = null, ?string $js = null): void { - $gen = new TemplateRenderer($template, $image); + $gen = new HtmlTemplateRenderer($template, $image); if (!is_null($css)) { $gen->css = explode(',', $css); diff --git a/src/PHPDraft/Parse/ParserFactory.php b/src/PHPDraft/Parse/ParserFactory.php index 20271564..4a45dda3 100644 --- a/src/PHPDraft/Parse/ParserFactory.php +++ b/src/PHPDraft/Parse/ParserFactory.php @@ -4,6 +4,8 @@ namespace PHPDraft\Parse; +use PHPDraft\Out\OpenAPI\OpenApiRenderer; + /** * Class ParserFactory. */ @@ -39,4 +41,13 @@ public static function getJson(): BaseHtmlGenerator throw new ResourceException("Couldn't get a JSON parser", 255); } + + public static function getOpenAPI(): OpenApiRenderer + { + if (Drafter::available() || DrafterAPI::available()) { + return new OpenApiRenderer(); + } + + throw new ResourceException("Couldn't get a JSON parser", 255); + } }