From f24e508b5772e7b59d829fc6cf1b1f81b0a751a1 Mon Sep 17 00:00:00 2001 From: Sean Molenaar Date: Wed, 14 Aug 2019 22:25:25 +0200 Subject: [PATCH] Add rudimentary support for drafter v4 --- phpdraft | 6 +- .../Model/Elements/BasicStructureElement.php | 17 +- .../Model/Elements/EnumStructureElement.php | 3 +- src/PHPDraft/Model/HTTPRequest.php | 2 +- src/PHPDraft/Model/HTTPResponse.php | 8 +- src/PHPDraft/Model/HierarchyElement.php | 2 +- src/PHPDraft/Model/Resource.php | 2 +- src/PHPDraft/Model/Transition.php | 3 +- src/PHPDraft/Out/BaseTemplateGenerator.php | 72 ++++++ src/PHPDraft/Out/LegacyTemplateGenerator.php | 219 ++++++++++++++++++ src/PHPDraft/Out/TemplateGenerator.php | 8 +- src/PHPDraft/Parse/BaseHtmlGenerator.php | 57 +++++ src/PHPDraft/Parse/Drafter.php | 6 +- .../{JsonToHTML.php => HtmlGenerator.php} | 48 +--- src/PHPDraft/Parse/LegacyHtmlGenerator.php | 50 ++++ src/PHPDraft/Parse/ParserFactory.php | 21 +- src/PHPDraft/Parse/Tests/JsonToHTMLTest.php | 13 +- 17 files changed, 468 insertions(+), 69 deletions(-) create mode 100644 src/PHPDraft/Out/BaseTemplateGenerator.php create mode 100644 src/PHPDraft/Out/LegacyTemplateGenerator.php create mode 100644 src/PHPDraft/Parse/BaseHtmlGenerator.php rename src/PHPDraft/Parse/{JsonToHTML.php => HtmlGenerator.php} (51%) create mode 100644 src/PHPDraft/Parse/LegacyHtmlGenerator.php diff --git a/phpdraft b/phpdraft index 761e1804..2bfd0993 100755 --- a/phpdraft +++ b/phpdraft @@ -15,6 +15,7 @@ use PHPDraft\Out\Sorting; use PHPDraft\Out\Version; use PHPDraft\Parse\ExecutionException; use PHPDraft\Parse\JsonToHTML; +use PHPDraft\Parse\LegacyHtmlGenerator; use PHPDraft\Parse\ParserFactory; use PHPDraft\Parse\ResourceException; @@ -56,15 +57,16 @@ try try { - $parser = ParserFactory::get(); + $parser = ParserFactory::getDrafter(); $parser = $parser->init($apib); + $data = $parser->parseToJson(); } catch (ResourceException $exception) { throw new ExecutionException('No drafter available', 255); } - $html = new JsonToHTML($parser->parseToJson()); + $html = ParserFactory::getJson()->init($data); $name = 'PHPD_SORT_' . strtoupper($args->getOpt('sort', '')); $html->sorting = Sorting::${$name} ?? -1; diff --git a/src/PHPDraft/Model/Elements/BasicStructureElement.php b/src/PHPDraft/Model/Elements/BasicStructureElement.php index 5120fd1b..64c21dae 100644 --- a/src/PHPDraft/Model/Elements/BasicStructureElement.php +++ b/src/PHPDraft/Model/Elements/BasicStructureElement.php @@ -92,9 +92,20 @@ protected function parse_common($object, array &$dependencies): void { $this->key = (isset($object->content->key->content)) ? $object->content->key->content : NULL; $this->type = (isset($object->content->value->element)) ? $object->content->value->element : NULL; - $this->description = isset($object->meta->description) ? htmlentities($object->meta->description) : NULL; - $this->status = - isset($object->attributes->typeAttributes) ? join(', ', $object->attributes->typeAttributes) : NULL; + $this->description = NULL; + if (isset($object->meta->description->content)){ + $this->description = htmlentities($object->meta->description->content); + } elseif (isset($object->meta->description)) { + $this->description = htmlentities($object->meta->description); + } + + $this->status = NULL; + if (isset($object->attributes->typeAttributes->content)){ + $data = array_map(function ($item) { return $item->content; }, $object->attributes->typeAttributes->content); + $this->status = join(', ', $data); + } elseif (isset($object->attributes->typeAttributes)) { + $this->status = join(', ', $object->attributes->typeAttributes); + } $this->description_as_html(); diff --git a/src/PHPDraft/Model/Elements/EnumStructureElement.php b/src/PHPDraft/Model/Elements/EnumStructureElement.php index 922839a9..a0574fd5 100644 --- a/src/PHPDraft/Model/Elements/EnumStructureElement.php +++ b/src/PHPDraft/Model/Elements/EnumStructureElement.php @@ -34,7 +34,8 @@ public function parse($object, array &$dependencies): StructureElement return $this; } - foreach ($object->content->value->content as $sub_item) { + $enumerations = $object->content->value->attributes->enumerations->content ?? $object->content->value->content; + foreach ($enumerations as $sub_item) { if (!in_array($sub_item->element, self::DEFAULTS)) { $dependencies[] = $sub_item->element; } diff --git a/src/PHPDraft/Model/HTTPRequest.php b/src/PHPDraft/Model/HTTPRequest.php index ffc6a18b..9e845512 100644 --- a/src/PHPDraft/Model/HTTPRequest.php +++ b/src/PHPDraft/Model/HTTPRequest.php @@ -93,7 +93,7 @@ public function __construct(Transition &$parent) */ public function parse(stdClass $object): self { - $this->method = $object->attributes->method; + $this->method = $object->attributes->method->content ?? $object->attributes->method; $this->title = isset($object->meta->title) ? $object->meta->title : NULL; if (($this->method === 'POST' || $this->method === 'PUT') && !empty($object->content)) { diff --git a/src/PHPDraft/Model/HTTPResponse.php b/src/PHPDraft/Model/HTTPResponse.php index b56638a9..fa7c1a3b 100644 --- a/src/PHPDraft/Model/HTTPResponse.php +++ b/src/PHPDraft/Model/HTTPResponse.php @@ -79,7 +79,9 @@ public function __construct(Transition $parent) */ public function parse(stdClass $object): self { - if (isset($object->attributes->statusCode)) { + if (isset($object->attributes->statusCode->content)) { + $this->statuscode = intval($object->attributes->statusCode->content); + } elseif (isset($object->attributes->statusCode)) { $this->statuscode = intval($object->attributes->statusCode); } if (isset($object->attributes->headers)) { @@ -130,7 +132,9 @@ protected function parse_content(stdClass $object): void continue; } - if (isset($value->attributes)) { + if (isset($value->attributes->contentType->content)) { + $this->content[$value->attributes->contentType->content] = $value->content; + } elseif (isset($value->attributes->contentType)) { $this->content[$value->attributes->contentType] = $value->content; } } diff --git a/src/PHPDraft/Model/HierarchyElement.php b/src/PHPDraft/Model/HierarchyElement.php index 419d478f..404dee72 100644 --- a/src/PHPDraft/Model/HierarchyElement.php +++ b/src/PHPDraft/Model/HierarchyElement.php @@ -55,7 +55,7 @@ abstract class HierarchyElement public function parse(stdClass $object) { if (isset($object->meta) && isset($object->meta->title)) { - $this->title = $object->meta->title; + $this->title = $object->meta->title->content ?? $object->meta->title; } if (!isset($object->content) || !is_array($object->content)) { diff --git a/src/PHPDraft/Model/Resource.php b/src/PHPDraft/Model/Resource.php index aa5f2b73..62537897 100644 --- a/src/PHPDraft/Model/Resource.php +++ b/src/PHPDraft/Model/Resource.php @@ -42,7 +42,7 @@ public function parse(stdClass $object): self parent::parse($object); if (isset($object->attributes)) { - $this->href = $object->attributes->href; + $this->href = $object->attributes->href->content ?? $object->attributes->href; } foreach ($object->content as $item) { diff --git a/src/PHPDraft/Model/Transition.php b/src/PHPDraft/Model/Transition.php index 1cd7998f..50d0bf20 100644 --- a/src/PHPDraft/Model/Transition.php +++ b/src/PHPDraft/Model/Transition.php @@ -70,7 +70,7 @@ class Transition extends HierarchyElement * * @param \PHPDraft\Model\Resource $parent A reference to the parent object */ - public function __construct(\PHPDraft\Model\Resource &$parent) + public function __construct(Resource &$parent) { $this->parent = $parent; } @@ -87,6 +87,7 @@ public function parse(stdClass $object): self parent::parse($object); $this->href = (isset($object->attributes->href)) ? $object->attributes->href : $this->parent->href; + $this->href = $this->href->content ?? $this->href; if (isset($object->attributes->hrefVariables)) { $deps = []; diff --git a/src/PHPDraft/Out/BaseTemplateGenerator.php b/src/PHPDraft/Out/BaseTemplateGenerator.php new file mode 100644 index 00000000..cf43073b --- /dev/null +++ b/src/PHPDraft/Out/BaseTemplateGenerator.php @@ -0,0 +1,72 @@ + + */ + +namespace PHPDraft\Out; + + +use Lukasoppermann\Httpstatus\Httpstatus; +use PHPDraft\Model\Elements\ObjectStructureElement; + +abstract class BaseTemplateGenerator +{ + /** + * Type of sorting to do on objects. + * + * @var int + */ + public $sorting; + /** + * CSS Files to load. + * + * @var array + */ + public $css = []; + /** + * JS Files to load. + * + * @var array + */ + public $js = []; + /** + * JSON object of the API blueprint. + * + * @var mixed + */ + protected $categories = []; + /** + * The template file to load. + * + * @var string + */ + protected $template; + /** + * The image to use as a logo. + * + * @var string + */ + protected $image = null; + /** + * The base URl of the API. + * + * @var + */ + protected $base_data; + /** + * The Http Status resolver. + * + * @var Httpstatus + */ + protected $http_status; + /** + * Structures used in all data. + * + * @var ObjectStructureElement[] + */ + protected $base_structures = []; +} \ No newline at end of file diff --git a/src/PHPDraft/Out/LegacyTemplateGenerator.php b/src/PHPDraft/Out/LegacyTemplateGenerator.php new file mode 100644 index 00000000..f388208f --- /dev/null +++ b/src/PHPDraft/Out/LegacyTemplateGenerator.php @@ -0,0 +1,219 @@ + + */ + +namespace PHPDraft\Out; + +use Lukasoppermann\Httpstatus\Httpstatus; +use Michelf\MarkdownExtra; +use PHPDraft\Model\Category; +use PHPDraft\Model\Elements\ObjectStructureElement; +use PHPDraft\Parse\ExecutionException; + +class LegacyTemplateGenerator extends BaseTemplateGenerator +{ + /** + * 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->http_status = new Httpstatus(); + $this->sorting = Sorting::$PHPD_SORT_NONE; + } + + /** + * Pre-parse objects needed and print HTML. + * + * @param mixed $object JSON to parse from + * + * @throws ExecutionException + * + * @return void + */ + public function get($object) + { + $include = $this->find_include_file($this->template); + if ($include === NULL) { + throw new ExecutionException("Couldn't find template '$this->template'", 1); + } + + //Prepare base data + if (is_array($object->content[0]->content)) { + foreach ($object->content[0]->attributes->meta 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'] = preg_replace('/(<\/?p>)/', '', MarkdownExtra::defaultTransform(htmlentities($value->content)), 2); + continue; + } + + $cat = new Category(); + $cat = $cat->parse($value); + + if ($value->meta->classes[0] === 'dataStructures') { + $this->base_structures = array_merge($this->base_structures, $cat->structures); + } else { + $this->categories[] = $cat; + } + } + + $this->base_data['TITLE'] = $object->content[0]->meta->title; + } + + if (Sorting::sortStructures($this->sorting)) { + ksort($this->base_structures); + } + + if (Sorting::sortServices($this->sorting)) { + usort($this->categories, function ($a, $b) { + return strcmp($a->title, $b->title); + }); + foreach ($this->categories as &$category) { + usort($category->children, function ($a, $b) { + return strcmp($a->title, $b->title); + }); + } + } + + require_once $include; + } + + /** + * 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 = 'phtml'): ?string + { + $include = NULL; + $fextension = '.' . $extension; + if (stream_resolve_include_path('templates' . DIRECTORY_SEPARATOR . $template . DIRECTORY_SEPARATOR . $template . $fextension)) { + $include = 'templates' . DIRECTORY_SEPARATOR . $template . DIRECTORY_SEPARATOR . $template . $fextension; + + return $include; + } + + if (stream_resolve_include_path('templates' . DIRECTORY_SEPARATOR . $template . $fextension)) { + $include = 'templates' . DIRECTORY_SEPARATOR . $template . $fextension; + + return $include; + } + + if (stream_resolve_include_path($template . DIRECTORY_SEPARATOR . $template . $fextension)) { + $include = $template . DIRECTORY_SEPARATOR . $template . $fextension; + + return $include; + } + + if (stream_resolve_include_path($template . $fextension)) { + $include = $template . $fextension; + + return $include; + } + + if (stream_resolve_include_path('PHPDraft/Out/HTML/' . $template . $fextension)) { + $include = 'PHPDraft/Out/HTML/' . $template . $fextension; + + return $include; + } + + if ($include === NULL && in_array($extension, ['phtml', 'js', 'css'])) { + 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 function get_method_icon(string $method): string + { + $class = ['fas', strtoupper($method)]; + switch (strtolower($method)) { + case 'post': + $class[] = 'fa-plus-square'; + break; + case 'put': + $class[] = 'fa-pen-square'; + break; + case 'get': + $class[] = 'fa-arrow-circle-down'; + break; + case 'delete': + $class[] = 'fa-minus-square'; + break; + case 'head': + $class[] = 'fa-info'; + break; + case 'connect': + $class[] = 'fa-ethernet'; + break; + case 'options': + $class[] = 'fa-sliders-h'; + break; + case 'trace': + $class[] = 'fa-route'; + break; + case 'patch': + $class[] = 'fa-band-aid'; + break; + default: + break; + } + + return 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 function get_response_status(int $response): string + { + if ($response <= 299) { + return 'text-success'; + } elseif ($response > 299 && $response <= 399) { + return 'text-warning'; + } else { + return 'text-error'; + } + } + + /** + * Strip spaces from links to objects. + * + * @param string $key key with potential spaces + * + * @return string key without spaces + */ + public function strip_link_spaces(string $key): string + { + return str_replace(' ', '-', strtolower($key)); + } +} diff --git a/src/PHPDraft/Out/TemplateGenerator.php b/src/PHPDraft/Out/TemplateGenerator.php index 987aaf73..c384ef59 100644 --- a/src/PHPDraft/Out/TemplateGenerator.php +++ b/src/PHPDraft/Out/TemplateGenerator.php @@ -15,7 +15,7 @@ use PHPDraft\Model\Elements\ObjectStructureElement; use PHPDraft\Parse\ExecutionException; -class TemplateGenerator +class TemplateGenerator extends BaseTemplateGenerator { /** * Type of sorting to do on objects. @@ -107,7 +107,7 @@ public function get($object) //Prepare base data if (is_array($object->content[0]->content)) { - foreach ($object->content[0]->attributes->meta as $meta) { + foreach ($object->content[0]->attributes->metadata->content as $meta) { $this->base_data[$meta->content->key->content] = $meta->content->value->content; } @@ -120,14 +120,14 @@ public function get($object) $cat = new Category(); $cat = $cat->parse($value); - if ($value->meta->classes[0] === 'dataStructures') { + if ($value->meta->classes->content[0]->content === 'dataStructures') { $this->base_structures = array_merge($this->base_structures, $cat->structures); } else { $this->categories[] = $cat; } } - $this->base_data['TITLE'] = $object->content[0]->meta->title; + $this->base_data['TITLE'] = $object->content[0]->meta->title->content ?? $object->content[0]->meta->title->content; } if (Sorting::sortStructures($this->sorting)) { diff --git a/src/PHPDraft/Parse/BaseHtmlGenerator.php b/src/PHPDraft/Parse/BaseHtmlGenerator.php new file mode 100644 index 00000000..fee38d9a --- /dev/null +++ b/src/PHPDraft/Parse/BaseHtmlGenerator.php @@ -0,0 +1,57 @@ + + */ + +namespace PHPDraft\Parse; + + +use PHPDraft\Out\BaseTemplateGenerator; +use PHPDraft\Out\TemplateGenerator; +use stdClass; + +abstract class BaseHtmlGenerator +{ + /** + * Type of sorting to do. + * + * @var int + */ + public $sorting; + + /** + * JSON representation of an API Blueprint. + * + * @var stdClass + */ + protected $object; + + /** + * JsonToHTML constructor. + * + * @param stdClass $json JSON representation of an API Blueprint + */ + public function init(stdClass $json): self + { + $this->object = $json; + return $this; + } + + /** + * Gets the default template HTML. + * + * @throws ExecutionException When parsing fails + * + * @return string + */ + public function __toString() + { + return $this->get_html(); + } + + public abstract function get_html(string $template = 'default', ?string $image = NULL, ?string $css = NULL, ?string $js = NULL): BaseTemplateGenerator; +} \ No newline at end of file diff --git a/src/PHPDraft/Parse/Drafter.php b/src/PHPDraft/Parse/Drafter.php index c9265592..fd5430c0 100644 --- a/src/PHPDraft/Parse/Drafter.php +++ b/src/PHPDraft/Parse/Drafter.php @@ -23,8 +23,6 @@ class Drafter extends BaseParser * * @param string $apib API Blueprint text * - * @throws ExecutionException - * * @return \PHPDraft\Parse\Drafter */ public function init(string $apib): BaseParser @@ -32,7 +30,7 @@ public function init(string $apib): BaseParser parent::init($apib); $this->drafter = self::location(); - throw new ExecutionException('The new Drafter V4 is not supported yet.', 100); + return $this; } /** @@ -55,7 +53,7 @@ public static function location() */ protected function parse(): void { - shell_exec($this->drafter . ' ' . $this->tmp_dir . '/index.apib -f json -o ' . $this->tmp_dir . '/index.json 2> /dev/null'); + shell_exec("{$this->drafter} {$this->tmp_dir}/index.apib -f json -o {$this->tmp_dir}/index.json 2> /dev/null"); $this->json = json_decode(file_get_contents($this->tmp_dir . '/index.json')); } diff --git a/src/PHPDraft/Parse/JsonToHTML.php b/src/PHPDraft/Parse/HtmlGenerator.php similarity index 51% rename from src/PHPDraft/Parse/JsonToHTML.php rename to src/PHPDraft/Parse/HtmlGenerator.php index 64a1876b..73de5bb1 100644 --- a/src/PHPDraft/Parse/JsonToHTML.php +++ b/src/PHPDraft/Parse/HtmlGenerator.php @@ -1,6 +1,6 @@ object = $json; - } - - /** - * Gets the default template HTML. - * - * @throws ExecutionException When parsing fails - * - * @return string - */ - public function __toString() - { - return $this->get_html(); - } - /** * Get the HTML representation of the JSON object. * @@ -63,9 +27,9 @@ public function __toString() * * @throws ExecutionException As a runtime exception * - * @return TemplateGenerator HTML template to display + * @return BaseTemplateGenerator HTML template to display */ - public function get_html(string $template = 'default', ?string $image = NULL, ?string $css = NULL, ?string $js = NULL): TemplateGenerator + public function get_html(string $template = 'default', ?string $image = NULL, ?string $css = NULL, ?string $js = NULL): BaseTemplateGenerator { $gen = new TemplateGenerator($template, $image); diff --git a/src/PHPDraft/Parse/LegacyHtmlGenerator.php b/src/PHPDraft/Parse/LegacyHtmlGenerator.php new file mode 100644 index 00000000..4bbb3f7b --- /dev/null +++ b/src/PHPDraft/Parse/LegacyHtmlGenerator.php @@ -0,0 +1,50 @@ + + */ + +namespace PHPDraft\Parse; + +use PHPDraft\Out\BaseTemplateGenerator; +use PHPDraft\Out\LegacyTemplateGenerator; + +/** + * Class LegacyHtmlGenerator. + */ +class LegacyHtmlGenerator extends BaseHtmlGenerator +{ + /** + * Get the HTML representation of the JSON object. + * + * @param string $template Type of template to display. + * @param string|null $image Image to use as a logo + * @param string|null $css CSS to load + * @param string|null $js JS to load + * + * @throws ExecutionException As a runtime exception + * + * @return BaseTemplateGenerator HTML template to display + */ + public function get_html(string $template = 'default', ?string $image = NULL, ?string $css = NULL, ?string $js = NULL): BaseTemplateGenerator + { + $gen = new LegacyTemplateGenerator($template, $image); + + if (!empty($css)) { + $gen->css[] = explode(',', $css); + } + + if (!empty($js)) { + $gen->js[] = explode(',', $js); + } + + $gen->sorting = $this->sorting; + + $gen->get($this->object); + + return $gen; + } +} diff --git a/src/PHPDraft/Parse/ParserFactory.php b/src/PHPDraft/Parse/ParserFactory.php index 492d9e0d..e4994f47 100644 --- a/src/PHPDraft/Parse/ParserFactory.php +++ b/src/PHPDraft/Parse/ParserFactory.php @@ -8,11 +8,11 @@ class ParserFactory { /** - * Get the applicable parser. + * Get the applicable Drafter parser. * * @return \PHPDraft\Parse\BaseParser The parser that can be used */ - public static function get(): BaseParser + public static function getDrafter(): BaseParser { if (Drafter::available()) { return new Drafter(); @@ -26,4 +26,21 @@ public static function get(): BaseParser throw new ResourceException("Couldn't get an apib parser", 255); } + + /** + * Get the applicable JSON parser. + * + * @return \PHPDraft\Parse\BaseHtmlGenerator The parser that can be used + */ + public static function getJson(): BaseHtmlGenerator + { + if (LegacyDrafter::available()) { + return new LegacyHtmlGenerator(); + } + if (Drafter::available() || DrafterAPI::available()) { + return new HtmlGenerator(); + } + + throw new ResourceException("Couldn't get a JSON parser", 255); + } } diff --git a/src/PHPDraft/Parse/Tests/JsonToHTMLTest.php b/src/PHPDraft/Parse/Tests/JsonToHTMLTest.php index 2d1adb57..d50d9980 100644 --- a/src/PHPDraft/Parse/Tests/JsonToHTMLTest.php +++ b/src/PHPDraft/Parse/Tests/JsonToHTMLTest.php @@ -9,18 +9,18 @@ namespace PHPDraft\Parse\Tests; use Lunr\Halo\LunrBaseTest; -use PHPDraft\Parse\JsonToHTML; +use PHPDraft\Parse\LegacyHtmlGenerator; use ReflectionClass; /** * Class JsonToHTMLTest - * @covers \PHPDraft\Parse\JsonToHTML + * @covers \PHPDraft\Parse\LegacyHtmlGenerator */ class JsonToHTMLTest extends LunrBaseTest { /** * Test Class - * @var JsonToHTML + * @var LegacyHtmlGenerator */ protected $class; @@ -35,8 +35,11 @@ class JsonToHTMLTest extends LunrBaseTest */ public function setUp(): void { - $this->class = new JsonToHTML(json_decode(file_get_contents(TEST_STATICS . '/drafter/json/index.json'))); - $this->reflection = new ReflectionClass('PHPDraft\Parse\JsonToHTML'); + $data = json_decode(file_get_contents(TEST_STATICS . '/drafter/json/index.json')); + $this->class = new LegacyHtmlGenerator(); + $this->class->init($data); + + $this->reflection = new ReflectionClass('PHPDraft\Parse\LegacyHtmlGenerator'); $this->mock_function('microtime', function () { return 'sometime'; }); $this->class->sorting = -1;