diff --git a/composer.json b/composer.json index 0648db5..0ffe14f 100644 --- a/composer.json +++ b/composer.json @@ -28,6 +28,7 @@ } }, "require-dev": { - "phpunit/phpunit": "~4" + "phpunit/phpunit": "~4", + "mikey179/vfsStream": "^1.6" } } diff --git a/composer.lock b/composer.lock index 4c323ee..5839a43 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "bd4fe65872a143039090e3c62b4c5a40", - "content-hash": "de3684c6c65b1602b52e6534aa5e6eda", + "hash": "31e312c5eb200fe262cc2557d2b7a5c6", + "content-hash": "cae1a4262309f9c2953036c6a756f04b", "packages": [ { "name": "guzzlehttp/guzzle", @@ -667,6 +667,52 @@ ], "time": "2015-06-14 21:17:01" }, + { + "name": "mikey179/vfsStream", + "version": "v1.6.3", + "source": { + "type": "git", + "url": "https://github.com/mikey179/vfsStream.git", + "reference": "c19925cd0390d3c436a0203ae859afa460d0474b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mikey179/vfsStream/zipball/c19925cd0390d3c436a0203ae859afa460d0474b", + "reference": "c19925cd0390d3c436a0203ae859afa460d0474b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-0": { + "org\\bovigo\\vfs\\": "src/main/php" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Frank Kleine", + "homepage": "http://frankkleine.de/", + "role": "Developer" + } + ], + "description": "Virtual file system to mock the real file system in unit tests.", + "homepage": "http://vfs.bovigo.org/", + "time": "2016-04-09 09:42:01" + }, { "name": "phpdocumentor/reflection-docblock", "version": "2.0.4", diff --git a/docs/00_Getting_Started.md b/docs/00_Getting_Started.md index 5f383f5..6fb41be 100644 --- a/docs/00_Getting_Started.md +++ b/docs/00_Getting_Started.md @@ -6,20 +6,20 @@ ### For Authors -* [Auto Generated Navigation / Page sorting](!Features/Navigation_and_Sorting) -* [Internal documentation links](!Features/Internal_links) -* [Github Flavored Markdown](!Features/GitHub_Flavored_Markdown) -* [Auto created homepage/landing page](!Features/Landing_page) -* [Multiple Output Formats](!Features/Multiple_Output_Formats) -* [Multiple Languages Support](!Features/Multilanguage) -* [No Build Step](!Features/Live_mode) -* [Static Output Generation](!Features/Static_Site_Generation) -* [Table of Contents](!Features/Table_of_contents) +* [Auto Generated Navigation / Page sorting](01_Features/Navigation_and_Sorting.md) +* [Internal documentation links](01_Features/Internal_links.md) +* [Github Flavored Markdown](01_Features/GitHub_Flavored_Markdown.md) +* [Auto created homepage/landing page](01_Features/Landing_page.md) +* [Multiple Output Formats](01_Features/Multiple_Output_Formats.md) +* [Multiple Languages Support](01_Features/Multilanguage.md) +* [No Build Step](01_Features/Live_mode.md) +* [Static Output Generation](01_Features/Static_Site_Generation.md) +* [Table of Contents](01_Features/Table_of_contents.md) ### For Developers -* [Auto Syntax Highlighting](!Features/Auto_Syntax_Highlight) -* [Extend Daux.io with Processors](!For_Developers/Creating_a_Processor) +* [Auto Syntax Highlighting](01_Features/Auto_Syntax_Highlight.md) +* [Extend Daux.io with Processors](01_For_Developers/Creating_a_Processor.md) * Full access to the internal API to create new pages programatically * Work with pages metadata @@ -97,7 +97,7 @@ You can use PHP's embedded web server by running the following command in the ro Upload your files to an apache / nginx server and see your documentation -[More informations here](!Features/Live_mode) +[More informations here](01_Features/Live_mode.md) #### Export to other formats @@ -107,7 +107,7 @@ Daux.io is extendable and comes by default with three export formats: - Export all documentation in a single HTML page - Upload to your Atlassian Confluence server. -[See a detailed feature comparison matrix](!Features/Multiple_Output_Formats) +[See a detailed feature comparison matrix](01_Features/Multiple_Output_Formats.md) Here's how you run an export: @@ -115,11 +115,11 @@ Here's how you run an export: ./generate ``` -[See here for all options](!Features/Static_Site_Generation) +[See here for all options](01_Features/Static_Site_Generation.md) ## Configuration -Now that you got the basics, you can also [see what you can configure](!Configuration) +Now that you got the basics, you can also [see what you can configure](05_Configuration/_index.md) ## Known Issues diff --git a/docs/01_Features/Auto_Syntax_Highlight.md b/docs/01_Features/Auto_Syntax_Highlight.md index 030df79..58d993b 100644 --- a/docs/01_Features/Auto_Syntax_Highlight.md +++ b/docs/01_Features/Auto_Syntax_Highlight.md @@ -18,4 +18,4 @@ Here is a quick example : -[See the full list of supported languages in Daux.io](!Examples/Code_Highlighting) +[See the full list of supported languages in Daux.io](../02_Examples/Code_Highlighting.md) diff --git a/docs/01_Features/Internal_links.md b/docs/01_Features/Internal_links.md index 9b7aff8..818a846 100644 --- a/docs/01_Features/Internal_links.md +++ b/docs/01_Features/Internal_links.md @@ -1,7 +1,49 @@ You can create links from a page to an other, the link is then resolved to the real page. -Creating a link to another page is done exactly like a normal markdown link. In the url part, start with `!` and set the absolute path to the file, omitting the numbering and file extension +Each relative link in your pages will be resolved to a page or content within the documentation. +If the link's destination isn't found, the page generation will fail. -A link to `01_Examples/05_Code_Highlighting.md` Would be written like this: `[Code Highlight Examples](!Examples/Code_Highlighting)` +Any valid markdown link is a valid Daux.io link. -The page generation will fail if a link is wrong. +If your file structure looks like this: + +``` +├── 00_Getting_Started.md +├── 01_Features +│ ├── 01_GitHub_Flavored_Markdown.md +├── 02_Examples +│ ├── Hello_World.md +│ ├── 05_Code_Highlighting.md +``` + +From the page `01_Features/01_GitHub_Flavored_Markdown.md`, all the following links would be valid: + + [Getting Started](../00_Getting_Started.md) + [Getting Started](../00_Getting_Started.html) + [Getting Started](../00_Getting_Started) + [Getting Started](../Getting_Started) + + // A link starting with / means root of the documentation, not the server it will be served from. + [Getting Started](/Getting_Started.html) + [Getting Started](/Getting_Started) + + // These Will first be searched for in the current directory and then start at the root of the documentation + [Getting Started](Getting_Started) + [Getting Started](00_Getting_Started) + + [Hello World](../02_Examples/Hello_World.md) + [Hello World](../02_Examples/Hello_World.html) + [Hello World](../02_Examples/Hello_World) + [Hello World](../Examples/Hello_World) + [Hello World](/02_Examples/Hello_World.md) + [Hello World](Examples/Hello_World) + [Hello World](02_Examples/Hello_World) + +## Github publishing + +If you plan to publish your documentation on Github along with your source code, we recommend to only use relative links with full names. + +From the list of links above only these two will work both on Github and on Daux.io + + [Getting Started](../00_Getting_Started.md) + [Hello World](../02_Examples/Hello_World.md) diff --git a/docs/_index.md b/docs/_index.md index 297da74..b7e51b5 100644 --- a/docs/_index.md +++ b/docs/_index.md @@ -10,23 +10,23 @@ #### For Authors -* [Auto Generated Navigation / Page sorting](!Features/Navigation_and_Sorting) -* [Internal documentation links](!Features/Internal_links) -* [Github Flavored Markdown](!Features/GitHub_Flavored_Markdown) -* [Auto created homepage/landing page](!Features/Landing_page) -* [Multiple Output Formats](!Features/Multiple_Output_Formats) -* [Multiple Languages Support](!Features/Multilanguage) -* [No Build Step](!Features/Live_mode) -* [Static Output Generation](!Features/Static_Site_Generation) -* [Table of Contents](!Features/Table_of_contents) +* [Auto Generated Navigation / Page sorting](01_Features/Navigation_and_Sorting.md) +* [Internal documentation links](01_Features/Internal_links.md) +* [Github Flavored Markdown](01_Features/GitHub_Flavored_Markdown.md) +* [Auto created homepage/landing page](01_Features/Landing_page.md) +* [Multiple Output Formats](01_Features/Multiple_Output_Formats.md) +* [Multiple Languages Support](01_Features/Multilanguage.md) +* [No Build Step](01_Features/Live_mode.md) +* [Static Output Generation](01_Features/Static_Site_Generation.md) +* [Table of Contents](01_Features/Table_of_contents.md)
#### For Developers -* [Auto Syntax Highlighting](!Features/Auto_Syntax_Highlight) -* [Extend Daux.io with Processors](!For_Developers/Creating_a_Processor) +* [Auto Syntax Highlighting](01_Features/Auto_Syntax_Highlight.md) +* [Extend Daux.io with Processors](01_For_Developers/Creating_a_Processor.md) * Full access to the internal API to create new pages programatically * Work with pages metadata diff --git a/libs/Config.php b/libs/Config.php index 86ff348..3bd64f4 100644 --- a/libs/Config.php +++ b/libs/Config.php @@ -1,6 +1,7 @@ merge($newValues, false); } + + public function getCurrentPage() + { + return $this['current_page']; + } + + public function setCurrentPage(Content $entry) + { + $this['current_page'] = $entry; + } } diff --git a/libs/ContentTypes/Markdown/ContentType.php b/libs/ContentTypes/Markdown/ContentType.php index 9af36e1..7f6c536 100644 --- a/libs/ContentTypes/Markdown/ContentType.php +++ b/libs/ContentTypes/Markdown/ContentType.php @@ -27,6 +27,7 @@ class ContentType implements \Todaymade\Daux\ContentTypes\ContentType public function convert($raw, Content $node) { + $this->config->setCurrentPage($node); return $this->converter->convertToHtml($raw); } } diff --git a/libs/ContentTypes/Markdown/LinkRenderer.php b/libs/ContentTypes/Markdown/LinkRenderer.php index e7add47..435f7fd 100644 --- a/libs/ContentTypes/Markdown/LinkRenderer.php +++ b/libs/ContentTypes/Markdown/LinkRenderer.php @@ -28,13 +28,30 @@ class LinkRenderer extends \League\CommonMark\Inline\Renderer\LinkRenderer */ protected function resolveInternalFile($url) { - $file = DauxHelper::getFile($this->daux['tree'], $url); - if ($file) { + $triedAbsolute = false; + + // Legacy absolute paths could start with + // "!" In this case we will try to find + // the file starting at the root + if ($url[0] == '!' || $url[0] == '/') { + $url = ltrim($url, "!/"); + + if ($file = DauxHelper::getFile($this->daux['tree'], $url)) { + return $file; + } + + $triedAbsolute = true; + } + + // Seems it's not an absolute path or not found, + // so we'll continue with the current folder + if ($file = DauxHelper::getFile($this->daux->getCurrentPage()->getParent(), $url)) { return $file; } - $file = DauxHelper::getFile($this->daux['tree'], $url . '.html'); - if ($file) { + // If we didn't already try it, we'll + // do a pass starting at the root + if (!$triedAbsolute && $file = DauxHelper::getFile($this->daux['tree'], $url)) { return $file; } @@ -42,10 +59,10 @@ class LinkRenderer extends \League\CommonMark\Inline\Renderer\LinkRenderer } /** - * @param Link $inline + * @param AbstractInline|Link $inline * @param ElementRendererInterface $htmlRenderer - * * @return HtmlElement + * @throws Exception */ public function render(AbstractInline $inline, ElementRendererInterface $htmlRenderer) { @@ -63,12 +80,19 @@ class LinkRenderer extends \League\CommonMark\Inline\Renderer\LinkRenderer $element = parent::render($inline, $htmlRenderer); $url = $inline->getUrl(); - if (!empty($url) && $url[0] == '!') { - $file = $this->resolveInternalFile(ltrim($url, "!")); - $element->setAttribute('href', $this->daux['base_url'] . $file->getUrl()); + // Absolute urls, empty urls and anchors + // should not go through the url resolver + if (empty($url) || $url[0] == "#" || preg_match("|^(?:[a-z]+:)?//|", $url)) { + return $element; } + $file = $this->resolveInternalFile($url); + + $url = DauxHelper::getRelativePath($this->daux->getCurrentPage()->getUrl(), $file->getUrl()); + + $element->setAttribute('href', $url); + return $element; } } diff --git a/libs/DauxHelper.php b/libs/DauxHelper.php index 51b1dad..be3aa0e 100644 --- a/libs/DauxHelper.php +++ b/libs/DauxHelper.php @@ -1,5 +1,6 @@ getParent(); + continue; + } + $node = urldecode($node); // if the node exists in the current request tree, @@ -167,6 +188,16 @@ class DauxHelper continue; } + // if the node doesn't exist, we can try + // two variants of the requested file: + // with and w/o the .html extension + foreach (static::getFilenames($tree->getConfig(), $node) as $filename) { + if (isset($tree->getEntries()[$filename])) { + $tree = $tree->getEntries()[$filename]; + continue 2; + } + } + // At this stage, we're in a directory, but no // sub-item matches, so the current node must // be an index page or we failed @@ -194,7 +225,7 @@ class DauxHelper * * Taken from Stringy * - * @param string $title + * @param string $title * @return string */ public static function slug($title) @@ -357,4 +388,37 @@ class DauxHelper "\xE2\x80\xAF", "\xE2\x81\x9F", "\xE3\x80\x80"), ); } + + public static function getRelativePath($from, $to) + { + // some compatibility fixes for Windows paths + $from = is_dir($from) ? rtrim($from, '\/') . '/' : $from; + $to = is_dir($to) ? rtrim($to, '\/') . '/' : $to; + $from = str_replace('\\', '/', $from); + $to = str_replace('\\', '/', $to); + + $from = explode('/', $from); + $to = explode('/', $to); + $relPath = $to; + + foreach ($from as $depth => $dir) { + // find first non-matching dir + if ($dir === $to[$depth]) { + // ignore this directory + array_shift($relPath); + } else { + // get number of remaining dirs to $from + $remaining = count($from) - $depth; + if ($remaining > 1) { + // add traversals up to first matching dir + $padLength = (count($relPath) + $remaining - 1) * -1; + $relPath = array_pad($relPath, $padLength, '..'); + break; + } else { + //$relPath[0] = './' . $relPath[0]; + } + } + } + return implode('/', $relPath); + } } diff --git a/libs/Tree/Builder.php b/libs/Tree/Builder.php index ed52ed4..e357cf2 100644 --- a/libs/Tree/Builder.php +++ b/libs/Tree/Builder.php @@ -17,15 +17,17 @@ class Builder protected static function isIgnored(\SplFileInfo $file, $ignore) { - if (in_array($file->getFilename(), static::$IGNORED)) { + $filename = $file->getFilename(); + + if (in_array($filename, static::$IGNORED)) { return true; } - if ($file->isDir() && in_array($file->getFilename(), $ignore['folders'])) { + if (array_key_exists('folders', $ignore) && $file->isDir() && in_array($filename, $ignore['folders'])) { return true; } - if (!$file->isDir() && in_array($file->getFilename(), $ignore['files'])) { + if (array_key_exists('files', $ignore) && !$file->isDir() && in_array($filename, $ignore['files'])) { return true; } diff --git a/libs/Tree/Directory.php b/libs/Tree/Directory.php index d14603a..20631b9 100644 --- a/libs/Tree/Directory.php +++ b/libs/Tree/Directory.php @@ -1,8 +1,10 @@ children); + } + + /** + * Offset to retrieve + * @param mixed $offset The offset to retrieve. + * @return Entry Can return all value types. + */ + public function offsetGet($offset) + { + return $this->children[$offset]; + } + + /** + * Offset to set + * @param mixed $offset The offset to assign the value to. + * @param Entry $value The value to set. + * @return void + */ + public function offsetSet($offset, $value) + { + if (!$value instanceof Entry) { + throw new RuntimeException("The value is not of type Entry"); + } + + $this->addChild($value); + } + + /** + * Offset to unset + * @param string $offset the offset to unset + * @return void + */ + public function offsetUnset($offset) + { + unset($this->children[$offset]); + } + + public function getIterator() + { + return new ArrayIterator($this->children); + } } diff --git a/tests/ContentTypes/Markdown/LinkRendererTest.php b/tests/ContentTypes/Markdown/LinkRendererTest.php new file mode 100644 index 0000000..40667b5 --- /dev/null +++ b/tests/ContentTypes/Markdown/LinkRendererTest.php @@ -0,0 +1,68 @@ + [ + 'Page.md' => 'some text content', + ], + 'Widgets' => [ + 'Page.md' => 'another page', + 'Button.md' => 'another page', + ], + ]; + $root = vfsStream::setup('root', null, $structure); + + $config['valid_content_extensions'] = ['md']; + $config['mode'] = Daux::STATIC_MODE; + $config['index_key'] = 'index.html'; + + $tree = new Root($config, $root->url()); + Builder::build($tree, []); + + return $tree; + } + + public function providerRenderLink() + { + return [ + // /Widgets/Page + ['Link', "[Link](http://google.ch)", "Widgets/Page.html"], + ['Link', "[Link](#features)", "Widgets/Page.html"], + ['Link', "[Link](Button.md)", "Widgets/Page.html"], + ['Link', "[Link](./Button.md)", "Widgets/Page.html"], + ['Link', "[Link](Button)", "Widgets/Page.html"], + ['Link', "[Link](./Button)", "Widgets/Page.html"], + ['Link', "[Link](!Widgets/Button)", "Widgets/Page.html"], + + // /Content/Page + ['Link', "[Link](../Widgets/Button.md)", "Content/Page.html"], + ['Link', "[Link](!Widgets/Button)", "Content/Page.html"], + ]; + } + + /** + * @dataProvider providerRenderLink + */ + public function testRenderLink($expected, $string, $current) + { + $config = new Config(); + $config['base_url'] = ''; + + $config['tree'] = $this->getTree($config); + $config->setCurrentPage(DauxHelper::getFile($config['tree'], $current)); + + $converter = new CommonMarkConverter(['daux' => $config]); + + $this->assertEquals("

$expected

", trim($converter->convertToHtml($string))); + } +} diff --git a/tests/DauxHelperTest.php b/tests/DauxHelperTest.php new file mode 100644 index 0000000..b642ef1 --- /dev/null +++ b/tests/DauxHelperTest.php @@ -0,0 +1,25 @@ +assertEquals($expected, DauxHelper::getFilenames($config, $node)); + } + +} diff --git a/tests/Tree/BuilderIntegrationTest.php b/tests/Tree/BuilderIntegrationTest.php new file mode 100644 index 0000000..400c746 --- /dev/null +++ b/tests/Tree/BuilderIntegrationTest.php @@ -0,0 +1,50 @@ + [ + 'Page.md' => 'some text content', + ], + 'Widgets' => [ + 'Page.md' => 'another page', + 'Button.md' => 'another page', + ], + ]; + $this->root = vfsStream::setup('root', null, $structure); + } + + public function testCreateHierarchy() + { + $config = new Config(); + $config['valid_content_extensions'] = ['md']; + $config['mode'] = Daux::STATIC_MODE; + $config['index_key'] = 'index.html'; + + $tree = new Root($config, $this->root->url()); + Builder::build($tree, []); + + $this->assertCount(2, $tree); + + $this->assertTrue(array_key_exists('Contents', $tree->getEntries())); + $this->assertInstanceOf(Directory::class, $tree['Contents']); + $this->assertTrue(array_key_exists('Widgets', $tree->getEntries())); + $this->assertInstanceOf(Directory::class, $tree['Widgets']); + + // TODO :: should not be Page.html, this should not depend on the mode + $this->assertEquals('Page', $tree['Contents']['Page.html']->getTitle()); + $this->assertInstanceOf(Content::class, $tree['Contents']['Page.html']); + } +}