Add the ability to do relative links in documentations

Cette révision appartient à :
Stéphane Goetz 2016-07-04 20:33:44 +02:00
Parent 563fcbca94
révision c1dd70748f
15 fichiers modifiés avec 433 ajouts et 47 suppressions

Voir le fichier

@ -28,6 +28,7 @@
}
},
"require-dev": {
"phpunit/phpunit": "~4"
"phpunit/phpunit": "~4",
"mikey179/vfsStream": "^1.6"
}
}

50
composer.lock générée
Voir le fichier

@ -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",

Voir le fichier

@ -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

Voir le fichier

@ -18,4 +18,4 @@ Here is a quick example :
<!-- here goes the rest of the page -->
</body>
[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)

Voir le fichier

@ -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)

Voir le fichier

@ -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)
</div>
<div class=col-third>
#### 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

Voir le fichier

@ -1,6 +1,7 @@
<?php namespace Todaymade\Daux;
use ArrayObject;
use Todaymade\Daux\Tree\Content;
class Config extends ArrayObject
{
@ -46,4 +47,14 @@ class Config extends ArrayObject
{
$this->merge($newValues, false);
}
public function getCurrentPage()
{
return $this['current_page'];
}
public function setCurrentPage(Content $entry)
{
$this['current_page'] = $entry;
}
}

Voir le fichier

@ -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);
}
}

Voir le fichier

@ -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;
}
}

Voir le fichier

@ -1,5 +1,6 @@
<?php namespace Todaymade\Daux;
use Todaymade\Daux\Tree\Builder;
use Todaymade\Daux\Tree\Directory;
class DauxHelper
@ -140,6 +141,16 @@ class DauxHelper
return implode(DIRECTORY_SEPARATOR, $absolutes);
}
public static function getFilenames(Config $config, $part)
{
$extensions = implode("|", array_map("preg_quote", $config["valid_content_extensions"])) . "|html";
$raw = preg_replace("/(.*)?\\.(" . $extensions . ")$/", "$1", $part);
$raw = Builder::removeSortingInformations($raw);
return ["$raw.html", $raw];
}
/**
* Locate a file in the tree. Returns the file if found or false
*
@ -157,6 +168,16 @@ class DauxHelper
return false;
}
// Some relative paths may start with ./
if ($node == '.') {
continue;
}
if ($node == '..') {
$tree = $tree->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);
}
}

Voir le fichier

@ -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;
}

Voir le fichier

@ -1,8 +1,10 @@
<?php namespace Todaymade\Daux\Tree;
use ArrayIterator;
use RuntimeException;
use Todaymade\Daux\Daux;
class Directory extends Entry
class Directory extends Entry implements \ArrayAccess, \IteratorAggregate
{
/** @var Entry[] */
protected $children = [];
@ -231,4 +233,54 @@ class Directory extends Entry
return $dump;
}
/**
* Whether a offset exists
* @param mixed $offset An offset to check for.
* @return boolean true on success or false on failure.
*/
public function offsetExists($offset)
{
return array_key_exists($offset, $this->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);
}
}

Voir le fichier

@ -0,0 +1,68 @@
<?php namespace Todaymade\Daux\ContentTypes\Markdown;
use org\bovigo\vfs\vfsStream;
use Todaymade\Daux\Config;
use Todaymade\Daux\Daux;
use Todaymade\Daux\DauxHelper;
use Todaymade\Daux\Tree\Builder;
use Todaymade\Daux\Tree\Root;
class LinkRendererTest extends \PHPUnit_Framework_TestCase
{
protected function getTree(Config $config)
{
$structure = [
'Content' => [
'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
['<a href="http://google.ch">Link</a>', "[Link](http://google.ch)", "Widgets/Page.html"],
['<a href="#features">Link</a>', "[Link](#features)", "Widgets/Page.html"],
['<a href="Button.html">Link</a>', "[Link](Button.md)", "Widgets/Page.html"],
['<a href="Button.html">Link</a>', "[Link](./Button.md)", "Widgets/Page.html"],
['<a href="Button.html">Link</a>', "[Link](Button)", "Widgets/Page.html"],
['<a href="Button.html">Link</a>', "[Link](./Button)", "Widgets/Page.html"],
['<a href="Button.html">Link</a>', "[Link](!Widgets/Button)", "Widgets/Page.html"],
// /Content/Page
['<a href="../Widgets/Button.html">Link</a>', "[Link](../Widgets/Button.md)", "Content/Page.html"],
['<a href="../Widgets/Button.html">Link</a>', "[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("<p>$expected</p>", trim($converter->convertToHtml($string)));
}
}

25
tests/DauxHelperTest.php Fichier normal
Voir le fichier

@ -0,0 +1,25 @@
<?php namespace Todaymade\Daux;
class DauxHelperTest extends \PHPUnit_Framework_TestCase {
public function providerGetFilenames() {
return [
[["Page.html", "Page"], "Page.html"],
[["Page.html", "Page"], "Page.md"],
[["Page.html", "Page"], "Page"],
[["Code_Highlighting.html", "Code_Highlighting"], "05_Code_Highlighting.md"],
];
}
/**
* @dataProvider providerGetFilenames
*/
public function testGetFilenames($expected, $node) {
$config = new Config();
$config['valid_content_extensions'] = ['md'];
$this->assertEquals($expected, DauxHelper::getFilenames($config, $node));
}
}

Voir le fichier

@ -0,0 +1,50 @@
<?php namespace Todaymade\Daux\Tree;
use org\bovigo\vfs\vfsStream;
use org\bovigo\vfs\vfsStreamDirectory;
use Todaymade\Daux\Config;
use Todaymade\Daux\Daux;
class BuilderIntegrationTest extends \PHPUnit_Framework_TestCase
{
/**
* @var vfsStreamDirectory
*/
private $root;
public function setUp()
{
$structure = [
'Contents' => [
'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']);
}
}