diff --git a/code/DocumentationParser.php b/code/DocumentationParser.php index fd8d40d..628cef9 100755 --- a/code/DocumentationParser.php +++ b/code/DocumentationParser.php @@ -218,7 +218,7 @@ class DocumentationParser ! \[ (.*?) # image title (non greedy) - \] + \] \( (.*?) # image url (non greedy) \) @@ -432,18 +432,18 @@ class DocumentationParser public static function rewrite_relative_links($md, $page) { $baselink = $page->getEntity()->Link(); - + $re = '/ ([^\!]?) # exclude image format \[ (.*?) # link title (non greedy) - \] + \] \( (.*?) # link url (non greedy) \) /x'; preg_match_all($re, $md, $matches); - + // relative path (relative to module base folder), without the filename. // For "sapphire/en/current/topics/templates", this would be "templates" $relativePath = DocumentationHelper::normalizePath(dirname($page->getRelativePath())); @@ -522,36 +522,4 @@ class DocumentationParser return $md; } - /** - * Strips out the metadata for a page - * - * @param DocumentationPage - */ - public static function retrieve_meta_data(DocumentationPage $page) - { - $md = $page->getMarkdown(); - if ($md) { - // get the text up to the first empty line - $extPattern = "/^(.+)\n\r*\n/Uis"; - $matches = preg_match($extPattern, $md, $block); - - if ($matches && $block[1]) { - - // find the key/value pairs - $lines = preg_split('/\v+/', $block[1]); - $key = ''; - $value = ''; - foreach ($lines as $line) { - if (strpos($line, ':') !== false) { - list($key, $value) = explode(':', $line, 2); - $key = trim($key); - $value = trim($value); - } else { - $value .= ' ' . trim($line); - } - $page->setMetaData($key, $value); - } - } - } - } } diff --git a/code/models/DocumentationPage.php b/code/models/DocumentationPage.php index 190bfd9..26019c6 100755 --- a/code/models/DocumentationPage.php +++ b/code/models/DocumentationPage.php @@ -172,13 +172,6 @@ class DocumentationPage extends ViewableData return false; } - public function setMetaData($key, $value) - { - $key = strtolower($key); - - $this->$key = $value; - } - /** * @return string */ @@ -280,37 +273,66 @@ class DocumentationPage extends ViewableData */ public function populateMetaDataFromText(&$md, $removeMetaData = false) { - if ($md) { - // get the text up to the first empty line - $extPattern = "/^(.+)\n\r*\n/Uis"; - $matches = preg_match($extPattern, $md, $block); + if (!$md) { + return; + } - if ($matches && $block[1]) { - $metaDataFound = false; - - // find the key/value pairs - $lines = preg_split('/\v+/', $block[1]); - $key = ''; - $value = ''; - foreach ($lines as $line) { - if (strpos($line, ':') !== false) { - list($key, $value) = explode(':', $line, 2); - $key = trim($key); - $value = trim($value); - } else { - $value .= ' ' . trim($line); - } - if (property_exists(get_class(), $key)) { - $this->$key = $value; - $metaDataFound = true; - } + // See if there is YAML metadata block at the top of the document. e.g. + // --- + // property: value + // another: value + // --- + // + // If we found one, then we'll use a YAML parser to extract the + // data out and then remove the whole block from the markdown string. + $parser = new \Mni\FrontYAML\Parser(); + $document = $parser->parse($md, false); + $yaml = $document->getYAML(); + if ($yaml) { + foreach ($yaml as $key => $value) { + if (!property_exists(get_class($this), $key)) { + continue; } + $this->$key = $value; + } + if ($removeMetaData) { + $md = $document->getContent(); + } + return; + } - // optionally remove the metadata block (only on the page that - // is displayed) - if ($metaDataFound && $removeMetaData) { - $md = preg_replace($extPattern, '', $md); + // this is the alternative way of parsing the properties out that don't contain + // a YAML block declared with --- + // + // get the text up to the first empty line + $extPattern = "/^(.+)\n\r*\n/Uis"; + $matches = preg_match($extPattern, $md, $block); + + if ($matches && $block[1]) { + $metaDataFound = false; + + // find the key/value pairs + $lines = preg_split('/\v+/', $block[1]); + $key = ''; + $value = ''; + foreach ($lines as $line) { + if (strpos($line, ':') !== false) { + list($key, $value) = explode(':', $line, 2); + $key = trim($key); + $value = trim($value); + } else { + $value .= ' ' . trim($line); } + if (property_exists(get_class(), $key)) { + $this->$key = $value; + $metaDataFound = true; + } + } + + // optionally remove the metadata block (only on the page that + // is displayed) + if ($metaDataFound && $removeMetaData) { + $md = preg_replace($extPattern, '', $md); } } } @@ -328,3 +350,4 @@ class DocumentationPage extends ViewableData return sprintf(get_class($this) .': %s)', $this->getPath()); } } + diff --git a/composer.json b/composer.json index 72db6ec..abe12f6 100644 --- a/composer.json +++ b/composer.json @@ -1,24 +1,25 @@ { - "name": "silverstripe/docsviewer", - "description": "Documentation viewer module for SilverStripe", - "type": "silverstripe-module", - "keywords": ["silverstripe", "documentation"], - "license": "BSD-3-Clause", - "authors": [{ - "name": "Will Rossiter", - "homepage": "http://wilr.github.io", - "email": "will@fullscreen.io" - }], - "support": { - "email": "will@fullscreen.io", - "irc": "irc://irc.freenode.org/silverstripe" - }, + "name": "silverstripe/docsviewer", + "description": "Documentation viewer module for SilverStripe", + "type": "silverstripe-module", + "keywords": ["silverstripe", "documentation"], + "license": "BSD-3-Clause", + "authors": [{ + "name": "Will Rossiter", + "homepage": "http://wilr.github.io", + "email": "will@fullscreen.io" + }], + "support": { + "email": "will@fullscreen.io", + "irc": "irc://irc.freenode.org/silverstripe" + }, "require": { - "silverstripe/framework": "~3.1", - "erusev/parsedown-extra": "0.2.2", - "erusev/parsedown": "~1.1.0" - }, - "suggest": { - "silverstripe/staticpublisher": "Allows publishing documentation as HTML" - } + "silverstripe/framework": "~3.1", + "erusev/parsedown-extra": "0.2.2", + "erusev/parsedown": "~1.1.0", + "mnapoli/front-yaml": "^1.5" + }, + "suggest": { + "silverstripe/staticpublisher": "Allows publishing documentation as HTML" + } } diff --git a/tests/DocumentationParserTest.php b/tests/DocumentationParserTest.php index 57ccecf..2f12033 100755 --- a/tests/DocumentationParserTest.php +++ b/tests/DocumentationParserTest.php @@ -11,7 +11,7 @@ class DocumentationParserTest extends SapphireTest public function tearDown() { parent::tearDown(); - + Config::unnest(); } @@ -21,7 +21,7 @@ class DocumentationParserTest extends SapphireTest Config::nest(); - // explicitly use dev/docs. Custom paths should be tested separately + // explicitly use dev/docs. Custom paths should be tested separately Config::inst()->update( 'DocumentationViewer', 'link_base', 'dev/docs/' ); @@ -48,7 +48,7 @@ class DocumentationParserTest extends SapphireTest 'subpage.md', DOCSVIEWER_PATH. '/tests/docs/en/subfolder/subpage.md' ); - + $this->subSubPage = new DocumentationPage( $this->entity, 'subsubpage.md', @@ -128,13 +128,12 @@ with multiple lines and tab indent and escaped < brackets - ``` Normal text after code block HTML; $this->assertContains($expected, $result, 'Custom code blocks with ::: prefix'); - + $expected = <<assertContains($expected, $result, 'Backtick code blocks'); - + $expected = <<indexPage->getMarkdown(), $this->indexPage ); - + $this->assertContains( '[link: subfolder index](dev/docs/en/documentationparsertest/2.4/subfolder/)', $result @@ -183,7 +182,7 @@ HTML; $this->page->getMarkdown(), $this->page ); - + $this->assertContains( '[link: subfolder index](dev/docs/en/documentationparsertest/2.4/subfolder/)', $result @@ -217,7 +216,7 @@ HTML; '[link: relative](dev/docs/en/documentationparsertest/2.4/subfolder/subpage.md/)', $result ); - + $this->assertContains( '[link: absolute index](dev/docs/en/documentationparsertest/2.4/)', $result @@ -233,22 +232,22 @@ HTML; '[link: relative index](dev/docs/en/documentationparsertest/2.4/)', $result ); - + $this->assertContains( '[link: relative parent page](dev/docs/en/documentationparsertest/2.4/test/)', $result ); - + $this->assertContains( '[link: absolute parent page](dev/docs/en/documentationparsertest/2.4/test/)', $result ); - + $result = DocumentationParser::rewrite_relative_links( $this->subSubPage->getMarkdown(), $this->subSubPage ); - + $this->assertContains( '[link: absolute index](dev/docs/en/documentationparsertest/2.4/)', $result @@ -286,7 +285,7 @@ HTML; } - + public function testImageRewrites() { $result = DocumentationParser::rewrite_image_links( @@ -311,7 +310,7 @@ HTML; ), $result ); - + $expected = Controller::join_links( Director::absoluteBaseURL(), DOCSVIEWER_DIR, '/tests/docs/en/_images/image.png' ); @@ -321,7 +320,7 @@ HTML; $result ); } - + public function testApiLinks() { @@ -357,14 +356,14 @@ HTML; } } - + public function testHeadlineAnchors() { $result = DocumentationParser::rewrite_heading_anchors( $this->page->getMarkdown(), $this->page ); - + /* # Heading one {#Heading-one} @@ -379,7 +378,7 @@ HTML; ## Heading duplicate {#Heading-duplicate-2} ## Heading duplicate {#Heading-duplicate-3} - + */ $this->assertContains('# Heading one {#heading-one}', $result); @@ -395,14 +394,41 @@ HTML; public function testRetrieveMetaData() { - DocumentationParser::retrieve_meta_data($this->metaDataPage); - - $this->assertEquals('Dr. Foo Bar.', $this->metaDataPage->author); - $this->assertEquals("Foo Bar's Test page.", $this->metaDataPage->getTitle()); - $this->assertEquals("Foo Bar's Test page.", $this->metaDataPage->getTitle()); - $this->assertEquals("A long intro that splits over many lines", $this->metaDataPage->getIntroduction()); + $this->metaDataPage->getMarkdown(true); + $this->assertEquals('Foo Bar\'s Test page.', $this->metaDataPage->getTitle()); + $this->assertEquals('A long intro that splits over many lines', $this->metaDataPage->getIntroduction()); + $this->assertEquals('Foo Bar Test page description', $this->metaDataPage->getSummary()); + + $parsed = DocumentationParser::parse($this->metaDataPage); + $expected = <<Content +HTML; + $this->assertEquals($parsed, $expected, 'Metadata block removed, parsed correctly'); } - + + public function testRetrieveMetaDataYamlBlock() + { + $page = new DocumentationPage( + $this->entityAlt, + 'MetaDataYamlBlockTest.md', + DOCSVIEWER_PATH . '/tests/docs-parser/en/MetaDataYamlBlockTest.md' + ); + $page->getMarkdown(true); + + $this->assertEquals('Foo Bar\'s Test page.', $page->getTitle()); + $this->assertEquals('This is the page\'s description', $page->getSummary()); + + $parsed = DocumentationParser::parse($page); + $expected = <<Content +

Content goes here.

+
+

randomblock: ignored

+HTML; + + $this->assertEquals($parsed, $expected, 'YAML metadata block removed, parsed correctly'); + } + public function testRewritingRelativeLinksToFiles() { $parsed = DocumentationParser::parse($this->filePage); @@ -411,7 +437,7 @@ HTML; DOCSVIEWER_DIR .'/tests/docs-parser/en/_images/external_link.png', $parsed ); - + $this->assertContains( DOCSVIEWER_DIR .'/tests/docs-parser/en/_images/test.tar.gz', $parsed diff --git a/tests/docs-parser/en/MetaDataTest.md b/tests/docs-parser/en/MetaDataTest.md index e51b484..1f8a33c 100755 --- a/tests/docs-parser/en/MetaDataTest.md +++ b/tests/docs-parser/en/MetaDataTest.md @@ -1,7 +1,6 @@ -Title: Foo Bar's Test page. -Author: Dr. Foo Bar. -Another: Test. -Introduction: A long intro that +title: Foo Bar's Test page. +summary: Foo Bar Test page description +introduction: A long intro that splits over many lines diff --git a/tests/docs-parser/en/MetaDataYamlBlockTest.md b/tests/docs-parser/en/MetaDataYamlBlockTest.md new file mode 100644 index 0000000..114e8d5 --- /dev/null +++ b/tests/docs-parser/en/MetaDataYamlBlockTest.md @@ -0,0 +1,13 @@ +--- +title: "Foo Bar's Test page." +summary: "This is the page's description" + +--- + +## Content + +Content goes here. + +--- +randomblock: ignored +--- diff --git a/tests/docs/en/test.md b/tests/docs/en/test.md index f925548..b11cb2c 100755 --- a/tests/docs/en/test.md +++ b/tests/docs/en/test.md @@ -29,7 +29,6 @@ test lines and tab indent and escaped < brackets - Normal text after code block