NEW: Github style code fence blocks. (Fixes #22)

This commit is contained in:
Will Rossiter 2013-02-19 22:23:22 +13:00
parent 440a2cbaa5
commit 3b27cd6b2b
5 changed files with 194 additions and 64 deletions

View File

@ -1,24 +1,32 @@
<?php <?php
/** /**
* Parser wrapping the Markdown Extra parser (see http://michelf.com/projects/php-markdown/extra/). * Parser wrapping the Markdown Extra parser.
*
* @see http://michelf.com/projects/php-markdown/extra/
* *
* @package docsviewer * @package docsviewer
*/ */
class DocumentationParser { class DocumentationParser {
/** const CODE_BLOCK_BACKTICK = 1;
* @var String Rewriting of api links in the format "[api:MyClass]" or "[api:MyClass::$my_property]". const CODE_BLOCK_COLON = 2;
*/
static $api_link_base = 'http://api.silverstripe.org/search/lookup/?q=%s&version=%s&module=%s';
static $heading_counts = array(); /**
* @var string Rewriting of api links in the format "[api:MyClass]" or "[api:MyClass::$my_property]".
*/
public static $api_link_base = 'http://api.silverstripe.org/search/lookup/?q=%s&version=%s&module=%s';
/**
* @var array
*/
public static $heading_counts = array();
/** /**
* Parse a given path to the documentation for a file. Performs a case * Parse a given path to the documentation for a file. Performs a case
* insensitive lookup on the file system. Automatically appends the file * insensitive lookup on the file system. Automatically appends the file
* extension to one of the markdown extensions as well so /install/ in a * extension to one of the markdown extensions as well so /install/ in a
* web browser will match /install.md or /INSTALL.md * web browser will match /install.md or /INSTALL.md.
* *
* Filepath: /var/www/myproject/src/cms/en/folder/subfolder/page.md * Filepath: /var/www/myproject/src/cms/en/folder/subfolder/page.md
* URL: http://myhost/mywebroot/dev/docs/2.4/cms/en/folder/subfolder/page * URL: http://myhost/mywebroot/dev/docs/2.4/cms/en/folder/subfolder/page
@ -27,8 +35,9 @@ class DocumentationParser {
* Pathparts: folder/subfolder/page * Pathparts: folder/subfolder/page
* *
* @param DocumentationPage $page * @param DocumentationPage $page
* @param String $baselink Link relative to webroot, up until the "root" of the module. * @param String $baselink Link relative to webroot, up until the "root"
* Necessary to rewrite relative links * of the module. Necessary to rewrite relative
* links
* *
* @return String * @return String
*/ */
@ -56,38 +65,110 @@ class DocumentationParser {
public static function rewrite_code_blocks($md) { public static function rewrite_code_blocks($md) {
$started = false; $started = false;
$inner = false; $inner = false;
$mode = false;
$end = false;
$lines = explode("\n", $md); $lines = explode("\n", $md);
$output = array();
foreach($lines as $i => $line) { foreach($lines as $i => $line) {
if(!$started && preg_match('/^\t*:::\s*(.*)/', $line, $matches)) { if(!$started && preg_match('/^\t*:::\s*(.*)/', $line, $matches)) {
// first line with custom formatting // first line with custom formatting
$started = true; $started = true;
$lines[$i] = sprintf('<pre class="brush: %s">', $matches[1]); $mode = self::CODE_BLOCK_COLON;
} elseif(preg_match('/^\t(.*)/', $line, $matches)) { $output[$i] = sprintf('<pre class="brush: %s">', (isset($matches[1])) ? $matches[1] : "");
// inner line of ::: block, or first line of standard markdown code block }
elseif(!$started && preg_match('/^\t*```\s*(.*)/', $line, $matches)) {
$started = true;
$mode = self::CODE_BLOCK_BACKTICK;
$output[$i] = sprintf('<pre class="brush: %s">', (isset($matches[1])) ? $matches[1] : "");
}
elseif($started && $mode == self::CODE_BLOCK_BACKTICK) {
// inside a backtick fenced box
if(preg_match('/^\t*```\s*/', $line, $matches)) {
// end of the backtick fenced box. Unset the line that contains the backticks
$end = true;
}
else {
// still inside the line.
$output[$i] = ($started) ? '' : '<pre>' . "\n";
$output[$i] .= htmlentities($line, ENT_COMPAT, 'UTF-8');
$inner = true;
}
}
elseif(preg_match('/^\t(.*)/', $line, $matches)) {
// inner line of block, or first line of standard markdown code block
// regex removes first tab (any following tabs are part of the code). // regex removes first tab (any following tabs are part of the code).
$lines[$i] = ($started) ? '' : '<pre>' . "\n"; $output[$i] = ($started) ? '' : '<pre>' . "\n";
$lines[$i] .= htmlentities($matches[1], ENT_COMPAT, 'UTF-8'); $output[$i] .= htmlentities($matches[1], ENT_COMPAT, 'UTF-8');
$inner = true; $inner = true;
$started = true; $started = true;
} elseif($started && $inner) { }
// remove any previous blank lines elseif($started && $inner && $mode == self::CODE_BLOCK_COLON && trim($line) === "") {
$j = $i-1; // still inside a colon based block, if the line is only whitespace
while(isset($lines[$j]) && preg_match('/^[\t\s]*$/', $lines[$j])) { // then continue with with it. We can continue with it for now as
unset($lines[$j]); // it'll be tidied up later in the $end section.
$inner = true;
$output[$i] = $line;
}
elseif($started && $inner) {
// line contains something other than whitespace, or tabbed. E.g
// > code
// > \n
// > some message
//
// So actually want to reset $i to the line before this new line
// and include this line. The edge case where this will fail is
// new the following segment contains a code block as well as it
// will not open.
$end = true;
$output[$i] = $line;
$i = $i -1;
}
else {
$output[$i] = $line;
}
if($end) {
$output = self::finalize_code_output($i, $output);
// reset state
$started = $inner = $mode = $end = false;
}
}
if($started) {
$output = self::finalize_code_output($i, $output);
}
return join("\n", $output);
}
/**
* @param int
* @param array
*
* @return array
*/
private static function finalize_code_output($i, $output) {
$j = $i;
while(isset($output[$j]) && trim($output[$j]) === "") {
unset($output[$j]);
$j--; $j--;
} }
// last line, close pre if(isset($output[$j])) {
$lines[$i] = '</pre>' . "\n\n" . $line; $output[$j] .= "</pre>\n";
// reset state
$started = $inner = false;
}
} }
return join("\n", $lines); else {
$output[$j] = "</pre>\n\n";
}
return $output;
} }
static function rewrite_image_links($md, $page) { static function rewrite_image_links($md, $page) {

View File

@ -3,26 +3,20 @@
The custom Markdown parser can render custom prefixes for code blocks, and The custom Markdown parser can render custom prefixes for code blocks, and
render it via a [javascript syntax highlighter](http://alexgorbatchev.com/SyntaxHighlighter). render it via a [javascript syntax highlighter](http://alexgorbatchev.com/SyntaxHighlighter).
In: To see examples of the syntax, check out the source of this file in docs/en/
:::php
my sourcecode
Out:
<pre class="brush: php">
my sourcecode
</pre>
To include the syntax highlighter source, add the following to your `DocumentationViewer->init()`: To include the syntax highlighter source, add the following to your `DocumentationViewer->init()`:
Requirements::javascript(THIRDPARTY_DIR .'/jquery/jquery.js');
Requirements::javascript('sapphiredocs/thirdparty/syntaxhighlighter/scripts/shCore.js'); ```php
Requirements::javascript('sapphiredocs/thirdparty/syntaxhighlighter/scripts/shBrushJScript.js');
Requirements::javascript('sapphiredocs/thirdparty/syntaxhighlighter/scripts/shBrushPHP.js'); Requirements::javascript(THIRDPARTY_DIR .'/jquery/jquery.js');
Requirements::javascript('sapphiredocs/thirdparty/syntaxhighlighter/scripts/shBrushXML.js'); Requirements::javascript('sapphiredocs/thirdparty/syntaxhighlighter/scripts/shCore.js');
// ... any additional syntaxes you want to support Requirements::javascript('sapphiredocs/thirdparty/syntaxhighlighter/scripts/shBrushJScript.js');
Requirements::combine_files( Requirements::javascript('sapphiredocs/thirdparty/syntaxhighlighter/scripts/shBrushPHP.js');
Requirements::javascript('sapphiredocs/thirdparty/syntaxhighlighter/scripts/shBrushXML.js');
// ... any additional syntaxes you want to support
Requirements::combine_files(
'syntaxhighlighter.js', 'syntaxhighlighter.js',
array( array(
'sapphiredocs/thirdparty/syntaxhighlighter/scripts/shCore.js', 'sapphiredocs/thirdparty/syntaxhighlighter/scripts/shCore.js',
@ -30,14 +24,15 @@ To include the syntax highlighter source, add the following to your `Documentati
'sapphiredocs/thirdparty/syntaxhighlighter/scripts/shBrushPHP.js', 'sapphiredocs/thirdparty/syntaxhighlighter/scripts/shBrushPHP.js',
'sapphiredocs/thirdparty/syntaxhighlighter/scripts/shBrushXML.js' 'sapphiredocs/thirdparty/syntaxhighlighter/scripts/shBrushXML.js'
) )
); );
Requirements::javascript('sapphiredocs/javascript/DocumentationViewer.js'); Requirements::javascript('sapphiredocs/javascript/DocumentationViewer.js');
// css // css
Requirements::css('sapphiredocs/thirdparty/syntaxhighlighter/styles/shCore.css'); Requirements::css('sapphiredocs/thirdparty/syntaxhighlighter/styles/shCore.css');
Requirements::css('sapphiredocs/thirdparty/syntaxhighlighter/styles/shCoreDefault.css'); Requirements::css('sapphiredocs/thirdparty/syntaxhighlighter/styles/shCoreDefault.css');
Requirements::css('sapphiredocs/thirdparty/syntaxhighlighter/styles/shThemeRDark.css'); Requirements::css('sapphiredocs/thirdparty/syntaxhighlighter/styles/shThemeRDark.css');
```
You can overload the `DocumentationViewer` class and add a custom route through `Director::addRule()` You can overload the `DocumentationViewer` class and add a custom route through `Director::addRule()`
if you prefer not to modify the module file. if you prefer not to modify the module file.

19
lang/en.yml Normal file
View File

@ -0,0 +1,19 @@
en:
DocumentationSearch:
SEARCH: Search
SEARCHRESULTS: 'Search Results'
DocumentationViewer:
CHANGE: Change
KEYWORDS: Keywords
LANGUAGE: Language
MODULES: Modules
OPENSEARCHDESC: 'Search the documentation'
OPENSEARCHNAME: 'Documentation Search'
OPENSEARCHTAGS: documentation
SEARCH: Search
VERSIONS: Versions
DocumentationViewer_home.ss:
DOCUMENTEDMODULES: 'Documented Modules'
NOMODULEDOCUMENTATION: 'No modules with documentation installed could be found.'
DocumentationViewer_results.ss:
ADVANCEDSEARCH: 'Advanced Search'

View File

@ -26,21 +26,40 @@ code block
with multiple with multiple
lines lines
and tab indent and tab indent
and escaped &lt; brackets and escaped &lt; brackets</pre>
</pre>
Normal text after code block Normal text after code block
HTML; HTML;
$this->assertContains($expected, $result, 'Custom code blocks with ::: prefix'); $this->assertContains($expected, $result, 'Custom code blocks with ::: prefix');
$expected = <<<HTML $expected = <<<HTML
<pre> <pre>
code block code block
without formatting prefix without formatting prefix</pre>
</pre>
HTML; HTML;
$this->assertContains($expected, $result, 'Traditional markdown code blocks'); $this->assertContains($expected, $result, 'Traditional markdown code blocks');
$expected = <<<HTML
<pre class="brush: ">
Fenced code block
</pre>
HTML;
$this->assertContains($expected, $result, 'Backtick code blocks');
$expected = <<<HTML
<pre class="brush: php">
Fenced box with
new lines in
between
content
</pre>
HTML;
$this->assertContains($expected, $result, 'Backtick with newlines');
} }
function testImageRewrites() { function testImageRewrites() {

View File

@ -25,6 +25,22 @@ Normal text after code block
code block code block
without formatting prefix without formatting prefix
```
Fenced code block
```
Did the fence work?
```php
Fenced box with
new lines in
between
content
```
# Heading one # Heading one
# Heading with custom anchor {#custom-anchor} # Heading with custom anchor {#custom-anchor}