diff --git a/composer.json b/composer.json index a0cf1af..a999853 100644 --- a/composer.json +++ b/composer.json @@ -13,9 +13,9 @@ ], "require": { "php": ">=5.4", - "erusev/parsedown": "~1.0", "league/plates": "~3.1", - "guzzlehttp/guzzle": "~5.3" + "guzzlehttp/guzzle": "~5.3", + "league/commonmark": "0.8.*" }, "autoload": { "psr-4": {"Todaymade\\Daux\\": "libs/"} diff --git a/composer.lock b/composer.lock index 8be7745..d2afb82 100644 --- a/composer.lock +++ b/composer.lock @@ -4,47 +4,8 @@ "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "82c1c29dbd870c4ee2ea79c0561ea5d8", + "hash": "a7e29fdc079b4e2a588148bff949237a", "packages": [ - { - "name": "erusev/parsedown", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/erusev/parsedown.git", - "reference": "2da10d277b086372f17b96df6cdc903829e1dde0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/2da10d277b086372f17b96df6cdc903829e1dde0", - "reference": "2da10d277b086372f17b96df6cdc903829e1dde0", - "shasum": "" - }, - "type": "library", - "autoload": { - "psr-0": { - "Parsedown": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Emanuil Rusev", - "email": "hello@erusev.com", - "homepage": "http://erusev.com" - } - ], - "description": "Parser for Markdown.", - "homepage": "http://parsedown.org", - "keywords": [ - "markdown", - "parser" - ], - "time": "2014-05-14 10:14:49" - }, { "name": "guzzlehttp/guzzle", "version": "5.3.0", @@ -204,6 +165,67 @@ ], "time": "2014-10-12 19:18:40" }, + { + "name": "league/commonmark", + "version": "0.8.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "91696c88df298f75fdd2075e4bb19c6dbd7338ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/91696c88df298f75fdd2075e4bb19c6dbd7338ca", + "reference": "91696c88df298f75fdd2075e4bb19c6dbd7338ca", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.4.8" + }, + "replace": { + "colinodell/commonmark-php": "*" + }, + "require-dev": { + "erusev/parsedown": "~1.0", + "jgm/commonmark": "0.19", + "michelf/php-markdown": "~1.4", + "phpunit/phpunit": "~4.3", + "phpunit/phpunit-mock-objects": "2.3.0", + "symfony/finder": "~2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.8-dev" + } + }, + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "http://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Markdown parser for PHP based on the CommonMark spec", + "homepage": "https://github.com/thephpleague/commonmark", + "keywords": [ + "commonmark", + "markdown", + "parser" + ], + "time": "2015-04-29 18:01:46" + }, { "name": "league/plates", "version": "3.1.0", diff --git a/libs/Daux.php b/libs/Daux.php index d2aeaa4..ba50f48 100644 --- a/libs/Daux.php +++ b/libs/Daux.php @@ -84,7 +84,7 @@ class Daux // Read command line overrides $config_file = $this->local_base . DS . $override_file; - if (file_exists($config_file)) { + if (!is_null($override_file) && file_exists($config_file)) { $config = json_decode(file_get_contents($config_file), true); if (!isset($config)) { throw new Exception('The local config file is missing. Check path : ' . $config_file); diff --git a/libs/DauxHelper.php b/libs/DauxHelper.php index cee077a..157c9f7 100644 --- a/libs/DauxHelper.php +++ b/libs/DauxHelper.php @@ -50,12 +50,15 @@ class DauxHelper return $theme; } - public static function getCleanPath($path) { + public static function getCleanPath($path) + { $path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path); $parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen'); $absolutes = array(); foreach ($parts as $part) { - if ('.' == $part) continue; + if ('.' == $part) { + continue; + } if ('..' == $part) { array_pop($absolutes); } else { diff --git a/libs/Format/Base/MarkdownPage.php b/libs/Format/Base/MarkdownPage.php index 1506175..0481651 100644 --- a/libs/Format/Base/MarkdownPage.php +++ b/libs/Format/Base/MarkdownPage.php @@ -1,5 +1,6 @@ params = $params; } + protected function getMarkdownConverter() + { + return new CommonMarkConverter(); + } + + protected function convertPage($content) + { + return $this->getMarkdownConverter()->convertToHtml($content); + } + protected function generatePage() { - return (new \Parsedown())->text($this->content); + return $this->convertPage($this->content); } public static function fromFile(Content $file, $params) diff --git a/libs/Format/Confluence/Api.php b/libs/Format/Confluence/Api.php index 1e3f6d5..304e6b4 100644 --- a/libs/Format/Confluence/Api.php +++ b/libs/Format/Confluence/Api.php @@ -1,8 +1,12 @@ base_url = $base_url; $this->user = $user; $this->pass = $pass; - $this->setSpace($space_id); } - public function setSpace($space_id) { + public function setSpace($space_id) + { $this->space = $space_id; } + protected function getClient() + { + $options = [ + 'base_url' => $this->base_url . 'rest/api/', + 'defaults' => [ + 'auth' => [$this->user, $this->pass] + ] + ]; + + return new Client($options); + } + + /** + * The standard error message from guzzle is quite poor in informations, + * this will give little bit more sense to it and return it + * + * @param BadResponseException $e + * @return BadResponseException + */ + protected function handleError(BadResponseException $e) + { + $request = $e->getRequest(); + $response = $e->getResponse(); + + $level = floor($response->getStatusCode() / 100); + + if ($level == '4') { + $label = 'Client error response'; + } elseif ($level == '5') { + $label = 'Server error response'; + } else { + $label = 'Unsuccessful response'; + } + + $message = $label . + ' [url] ' . $request->getUrl() . + ' [status code] ' . $response->getStatusCode() . + ' [message] '; + + try { + $message .= $response->json()['message']; + } catch (ParseException $e) { + $message .= (string) $response->getBody(); + } + + return new BadResponseException($message, $request, $response, $e->getPrevious()); + } + /** * /rest/api/content/{id}/child/{type} * * @param $rootPage * @return mixed */ - public function getHierarchy($rootPage) { - $hiera = $this->getClient()->get("content/$rootPage/child/page?expand=version,body.storage")->json(); + public function getHierarchy($rootPage) + { + try { + $hierarchy = $this->getClient()->get("content/$rootPage/child/page?expand=version,body.storage")->json(); + } catch (BadResponseException $e) { + throw $this->handleError($e); + } $children = []; - foreach($hiera['results'] as $result) { + foreach ($hierarchy['results'] as $result) { $children[$result['title']] = [ "id" => $result['id'], "title" => $result['title'], @@ -44,8 +102,8 @@ class Api { return $children; } - public function createPage($parent_id, $title, $content) { - + public function createPage($parent_id, $title, $content) + { $body = [ 'type' => 'page', 'space' => ['key' => $this->space], @@ -54,12 +112,17 @@ class Api { 'body' => ['storage' => ['value' => $content, 'representation' => 'storage']] ]; - $response = $this->getClient()->post('content', [ 'json' => $body ])->json(); + try { + $response = $this->getClient()->post('content', [ 'json' => $body ])->json(); + } catch (BadResponseException $e) { + throw $this->handleError($e); + } return $response['id']; } - public function updatePage($parent_id, $page_id, $newVersion, $title, $content) { + public function updatePage($parent_id, $page_id, $newVersion, $title, $content) + { $body = [ 'type' => 'page', 'space' => ['key' => $this->space], @@ -69,22 +132,45 @@ class Api { 'body' => ['storage' => ['value' => $content, 'representation' => 'storage']] ]; - $this->getClient()->put("content/$page_id", [ 'json' => $body ])->json(); + try { + $this->getClient()->put("content/$page_id", ['json' => $body])->json(); + } catch (BadResponseException $e) { + throw $this->handleError($e); + } } - public function deletePage($page_id) { - return $this->getClient()->delete('content/' . $page_id)->json(); + public function deletePage($page_id) + { + try { + return $this->getClient()->delete('content/' . $page_id)->json(); + } catch (BadResponseException $e) { + throw $this->handleError($e); + } } - protected function getClient() { + public function uploadAttachment($id, $attachment) + { + //get if attachment is uploaded + try { + $result = $this->getClient()->get("content/$id/child/attachment?filename=$attachment[filename]")->json(); + } catch (BadResponseException $e) { + throw $this->handleError($e); + } - $options = [ - 'base_url' => $this->base_url . 'rest/api/', - 'defaults' => [ - 'auth' => [$this->user, $this->pass] - ] - ]; + $url = "content/$id/child/attachment" . (count($result['results'])? "/{$result['results'][0]['id']}/data" : ""); - return new Client($options); + try { + $this->getClient()->post( + $url, + [ + 'body' => ['file' => fopen($attachment['file']->getPath(), 'r')] , + 'headers' => ['X-Atlassian-Token' => 'nocheck'], + ] + ); + } catch (BadResponseException $e) { + throw $this->handleError($e); + } + + //FIXME :: When doing an update, Confluence does a null pointer exception } } diff --git a/libs/Format/Confluence/CommonMarkConverter.php b/libs/Format/Confluence/CommonMarkConverter.php new file mode 100644 index 0000000..d988022 --- /dev/null +++ b/libs/Format/Confluence/CommonMarkConverter.php @@ -0,0 +1,26 @@ +mergeConfig($config); + + //Add code renderer + $environment->addBlockRenderer('FencedCode', new FencedCodeRenderer()); + $environment->addBlockRenderer('IndentedCode', new IndentedCodeRenderer()); + + $this->docParser = new DocParser($environment); + $this->htmlRenderer = new HtmlRenderer($environment); + } +} diff --git a/libs/Format/Confluence/FencedCodeRenderer.php b/libs/Format/Confluence/FencedCodeRenderer.php new file mode 100644 index 0000000..3c19e27 --- /dev/null +++ b/libs/Format/Confluence/FencedCodeRenderer.php @@ -0,0 +1,85 @@ + 'html/xml', 'xml' => 'html/xml', 'js' => 'javascript']; + + /** + * @param AbstractBlock $block + * @param HtmlRendererInterface $htmlRenderer + * @param bool $inTightList + * + * @return HtmlElement|string + */ + public function render(AbstractBlock $block, HtmlRendererInterface $htmlRenderer, $inTightList = false) + { + if (!($block instanceof FencedCode)) { + throw new \InvalidArgumentException('Incompatible block type: ' . get_class($block)); + } + + $content = []; + + if ($language = $this->getLanguage($block->getInfoWords(), $htmlRenderer)) { + $content[] = new HtmlElement('ac:parameter', ['ac:name' => 'language'], $language); + } + + $content[] = new HtmlElement('ac:plain-text-body', [], 'getStringContent() . ']]>'); + + return new HtmlElement( + 'ac:structured-macro', + ['ac:name' => 'code'], + $content + ); + } + + public function getLanguage($infoWords, HtmlRendererInterface $htmlRenderer) + { + if (count($infoWords) === 0 || strlen($infoWords[0]) === 0) { + return false; + } + + $language = $htmlRenderer->escape($infoWords[0], true); + + if (array_key_exists($language, $this->known_conversions)) { + $language = $this->known_conversions[$language]; + } + + if (in_array($language, $this->supported_languages)) { + return $language; + } + + return false; + } +} diff --git a/libs/Format/Confluence/Generator.php b/libs/Format/Confluence/Generator.php index 951146b..ff839c7 100644 --- a/libs/Format/Confluence/Generator.php +++ b/libs/Format/Confluence/Generator.php @@ -1,6 +1,5 @@ getParams()['confluence']; $this->prefix = trim($confluence['prefix']) . " "; - $main_title = $this->prefix . $daux->getParams()['title']; - $params = $daux->getParams(); echo "Generating Tree...\n"; $tree = $this->generateRecursive($daux->tree, $params); - $tree['title'] = $main_title; + $tree['title'] = $this->prefix . $daux->getParams()['title']; - echo "Getting already published pages...\n"; - $this->client = new Api($confluence['base_url'], $confluence['user'], $confluence['pass'], $confluence['space_id']); - $all_published = $this->client->getHierarchy($confluence['ancestor_id']); - - echo "Finding Root Page...\n"; - $published = []; - foreach ($all_published as $page) { - if ($page['title'] == $main_title) { - $published = $page; - break; - } - } - - echo "Create placeholder pages...\n"; - $published = $this->createRecursive($confluence['ancestor_id'], $tree, $published); - - echo "Publishing updates...\n"; - $this->updateRecursive($confluence['ancestor_id'], $tree, $published); + echo "Start Publishing...\n"; + $publisher = new Publisher($confluence); + $publisher->publish($tree); echo "Done !\n"; } - private function createRecursive($parent_id, $entry, $published) - { - //TODO :: remove deleted pages - - if (!array_key_exists('id', $published)) { - if (array_key_exists('page', $entry)) { - echo "Creating: " . $entry['file']->getUrl() . "\n"; - $published['version'] = 1; - $published['id'] = $this->client->createPage($parent_id, $entry['title'], "The content will come very soon !"); - } else { - echo "Creating Placeholder page: " . $entry['title'] . "\n"; - $published['version'] = 1; - $published['id'] = $this->client->createPage($parent_id, $entry['title'], ""); - } - } - - if (array_key_exists('children', $entry)) { - foreach($entry['children'] as $child) { - $pub = []; - if (array_key_exists('children', $published) && array_key_exists($child['title'], $published['children'])) { - $pub = $published['children'][$child['title']]; - } - - $published['children'][$child['title']] = $this->createRecursive($published['id'], $child, $pub); - } - } - - return $published; - } - - private function updateRecursive($parent_id, $entry, $published) - { - if (array_key_exists('id', $published) && array_key_exists('page', $entry)) { - echo "Updating: " . $entry['file']->getUrl() . "\n"; - try { - $this->client->updatePage( - $parent_id, - $published['id'], - $published['version'] + 1, - $entry['title'], - $entry['page']->getContent() - ); - } catch (ClientException $e) { - echo "-> Failed with message: " . $e->getResponse()->json()['message'] . "\n"; - } - } - - if (array_key_exists('children', $entry)) { - foreach($entry['children'] as $child) { - $pub = []; - if (array_key_exists('children', $published) && array_key_exists($child['title'], $published['children'])) { - $pub = $published['children'][$child['title']]; - } - - $this->updateRecursive($published['id'], $child, $pub); - } - } - } - private function generateRecursive(Entry $tree, array $params, $base_url = '') { $final = ['title' => $this->prefix . $tree->getTitle()]; @@ -125,9 +42,12 @@ class Generator } foreach ($tree->value as $key => $node) { if ($node instanceof Directory) { - $final['children'][$this->prefix . $node->getTitle()] = $this->generateRecursive($node, $params, '../' . $base_url); - } else if ($node instanceof Content) { - + $final['children'][$this->prefix . $node->getTitle()] = $this->generateRecursive( + $node, + $params, + '../' . $base_url + ); + } elseif ($node instanceof Content) { $params['request'] = $node->getUrl(); $params['file_uri'] = $node->getName(); @@ -137,6 +57,10 @@ class Generator 'page' => MarkdownPage::fromFile($node, $params), ]; + // As the page is lazily generated + // We do it now to fail fast in case of problem + $data['page']->getContent(); + if ($key == 'index.html') { $final['title'] = $this->prefix . $tree->getTitle(); $final['file'] = $node; diff --git a/libs/Format/Confluence/IndentedCodeRenderer.php b/libs/Format/Confluence/IndentedCodeRenderer.php new file mode 100644 index 0000000..2270fa2 --- /dev/null +++ b/libs/Format/Confluence/IndentedCodeRenderer.php @@ -0,0 +1,30 @@ + 'code'], + new HtmlElement('ac:plain-text-body', [], 'getStringContent().']]>') + ); + } +} diff --git a/libs/Format/Confluence/MarkdownPage.php b/libs/Format/Confluence/MarkdownPage.php index a7aa340..896af37 100644 --- a/libs/Format/Confluence/MarkdownPage.php +++ b/libs/Format/Confluence/MarkdownPage.php @@ -1,18 +1,32 @@ ]*src=['\"]([^\"]*)['\"][^>]*>/", function ($matches) { - return str_replace($matches[1], $this->findImage($matches[1]), $matches[0]); + + if ($result = $this->findImage($matches[1], $matches[0])) { + return $result; + } + + return $matches[0]; }, $page ); @@ -20,7 +34,7 @@ class MarkdownPage extends \Todaymade\Daux\Format\Base\MarkdownPage return $page; } - private function findImage($src) + private function findImage($src, $tag) { //for protocol relative or http requests : keep the original one if (substr($src, 0, strlen("http")) === "http" || substr($src, 0, strlen("//")) === "//") { @@ -35,16 +49,46 @@ class MarkdownPage extends \Todaymade\Daux\Format\Base\MarkdownPage if ($file === false) { - return $src; + return false; } - $encoded = base64_encode(file_get_contents($file->getPath())); - $extension = pathinfo($file->getPath(), PATHINFO_EXTENSION); + $filename = basename($file->getPath()); - if (!in_array($extension, ['jpg', 'jpeg', 'png', 'gif'])) { - return $src; + //Add the attachment for later upload + $this->attachments[] = ['filename' => $filename, 'file' => $file]; + + return $this->createImageTag($filename, $this->getAttributes($tag)); + } + + private function getAttributes($tag) + { + $dom = new DOMDocument(); + $dom->loadHTML($tag); + + $img = $dom->getElementsByTagName('img')[0]; + + $attributes = ['align', 'class', 'title', 'style', 'alt', 'height', 'width']; + $used = []; + foreach ($attributes as $attr) { + if ($img->attributes->getNamedItem($attr)) { + $used[$attr] = $img->attributes->getNamedItem($attr)->value; + } } - return "data:image/$extension;base64,$encoded"; + return $used; + } + + private function createImageTag($filename, $attributes) + { + + $img = " $value) { + $img .= ' ac:' . $name.'="'.htmlentities($value, ENT_QUOTES, 'UTF-8', false).'"'; + } + + $img .= ">"; + + return $img; } } diff --git a/libs/Format/Confluence/Publisher.php b/libs/Format/Confluence/Publisher.php new file mode 100644 index 0000000..c53b05d --- /dev/null +++ b/libs/Format/Confluence/Publisher.php @@ -0,0 +1,212 @@ +confluence = $confluence; + + $this->client = new Api($confluence['base_url'], $confluence['user'], $confluence['pass']); + $this->client->setSpace($confluence['space_id']); + } + + public function publish(array $tree) + { + echo "Getting already published pages...\n"; + $all_published = $this->client->getHierarchy($this->confluence['ancestor_id']); + + echo "Finding Root Page...\n"; + $published = []; + foreach ($all_published as $page) { + if ($page['title'] == $tree['title']) { + $published = $page; + break; + } + } + + echo "Create placeholder pages...\n"; + $published = $this->createRecursive($this->confluence['ancestor_id'], $tree, $published); + + echo "Publishing updates...\n"; + $this->updateRecursive($this->confluence['ancestor_id'], $tree, $published); + } + + protected function niceTitle($title) + { + if ($title == "index.html") { + return "Homepage"; + } + + return rtrim(strtr($title, ['index.html' => '', '.html' => '']), "/"); + } + + protected function createPage($parent_id, $entry, $published) + { + if ($this->previous_title != "Creating") { + $this->previous_title = "Creating"; + echo "Creating Pages...\n"; + } + + echo "- " . $this->niceTitle($entry['file']->getUrl()); + $published['version'] = 1; + $published['id'] = $this->client->createPage($parent_id, $entry['title'], "The content will come very soon !"); + echo " √ \n"; + + return $published; + } + + protected function createPlaceholderPage($parent_id, $entry, $published) + { + if ($this->previous_title != "Creating Placeholder") { + $this->previous_title = "Creating Placeholder"; + echo "Creating Placeholder Pages...\n"; + } + + echo "- " . $entry['title']; + $published['version'] = 1; + $published['id'] = $this->client->createPage($parent_id, $entry['title'], ""); + echo " √ \n"; + + return $published; + } + + protected function recursiveWithCallback($parent_id, $entry, $published, $callback) + { + $published = $callback($parent_id, $entry, $published); + + if (array_key_exists('children', $entry)) { + foreach ($entry['children'] as $child) { + $pub = []; + if (isset($published['children']) && array_key_exists($child['title'], $published['children'])) { + $pub = $published['children'][$child['title']]; + } + + $published['children'][$child['title']] = $this->recursiveWithCallback( + $published['id'], + $child, + $pub, + $callback + ); + } + } + + return $published; + } + + protected function createRecursive($parent_id, $entry, $published) + { + $callback = function ($parent_id, $entry, $published) { + + //TODO :: remove deleted pages + + // nothing to do if the ID already exists + if (array_key_exists('id', $published)) { + return $published; + } + + if (array_key_exists('page', $entry)) { + return $this->createPage($parent_id, $entry, $published); + } + + // If we have no $entry['page'] it means the page + // doesn't exist, but to respect the hierarchy, + // we need a blank page there + return $this->createPlaceholderPage($parent_id, $entry, $published); + }; + + return $this->recursiveWithCallback($parent_id, $entry, $published, $callback); + } + + protected function updateRecursive($parent_id, $entry, $published) + { + $callback = function ($parent_id, $entry, $published) { + if (array_key_exists('id', $published) && array_key_exists('page', $entry)) { + $this->updatePage($parent_id, $entry, $published); + } + + return $published; + }; + + return $this->recursiveWithCallback($parent_id, $entry, $published, $callback); + } + + protected function shouldUpdate($local, $published) + { + if (!array_key_exists('content', $published)) { + return true; + } + + $trimmed_local = trim($local); + $trimmed_distant = trim($published['content']); + + if ($trimmed_local == $trimmed_distant) { + return false; + } + + similar_text($trimmed_local, $trimmed_distant, $percent); + + // I consider that if the files are 98% identical you + // don't need to update. This will work for false positives. + // But sadly will miss if it's just a typo update + if ($percent >= 98) { + return false; + } + + return true; + } + + protected function updatePage($parent_id, $entry, $published) + { + if ($this->previous_title != "Updating") { + $this->previous_title = "Updating"; + echo "Updating Pages...\n"; + } + + echo "- " . $this->niceTitle($entry['file']->getUrl()); + + try { + if ($this->shouldUpdate($entry['page']->getContent(), $published)) { + $this->client->updatePage( + $parent_id, + $published['id'], + $published['version'] + 1, + $entry['title'], + $entry['page']->getContent() + ); + echo " √\n"; + } else { + echo " √ (No update needed)\n"; + } + + if (count($entry['page']->attachments)) { + foreach ($entry['page']->attachments as $attachment) { + echo " With attachment: $attachment[filename]"; + $this->client->uploadAttachment($published['id'], $attachment); + echo " √\n"; + } + } + + } catch (BadResponseException $e) { + echo " X Failed with message: " . $e->getMessage() . "\n"; + } + } +} diff --git a/libs/Format/HTML/MarkdownPage.php b/libs/Format/HTML/MarkdownPage.php index 784ba83..1cc76c1 100644 --- a/libs/Format/HTML/MarkdownPage.php +++ b/libs/Format/HTML/MarkdownPage.php @@ -69,7 +69,7 @@ class MarkdownPage extends \Todaymade\Daux\Format\Base\MarkdownPage 'modified_time' => filemtime($this->file->getPath()), 'markdown' => $this->content, 'request' => $params['request'], - 'content' => (new \Parsedown())->text($this->content), + 'content' => $this->convertPage($this->content), 'breadcrumbs' => $params['breadcrumbs'] ]; diff --git a/libs/Generator/Getopt.php b/libs/Generator/Getopt.php index eac8320..acfb222 100644 --- a/libs/Generator/Getopt.php +++ b/libs/Generator/Getopt.php @@ -254,8 +254,7 @@ class Getopt $long = []; $short = ''; foreach ($this->rules as $rule) { - foreach($rule['alias'] as $alias) { - + foreach ($rule['alias'] as $alias) { $prepared = $alias; if ($rule['param'] == 'optional') { $prepared .= '::';