Skip to content
This repository
Browse code

Use ronnjs 0.2 to generate docs. Small cosmetic change.

  • Loading branch information...
commit 2b8a9a835821f64fccfc7b571a7c4fb99328f684 1 parent a7e1efc
Jérémy Lal kapouer authored ry committed
5 LICENSE
@@ -28,6 +28,11 @@ are:
28 28 - HTTP Parser, located at deps/http_parser, is a small C library
29 29 copyrighted by Ryan Lienhart Dahl and has a MIT license.
30 30
  31 + - RonnJS, located at tools/ronnjs, is a library that generates man pages
  32 + and HTML from markdown. RonnJS is released under an MIT-style license
  33 + and has copyrights from Jérémy Lal, Ryan Tomayko, Dominic Baggott, Ash
  34 + Berlin, and Joey Mazzarelli.
  35 +
31 36
32 37 Node's license follows:
33 38
6 Makefile
@@ -44,8 +44,8 @@ benchmark: all
44 44 doc: doc/node.1 doc/api.html doc/index.html doc/changelog.html
45 45
46 46 ## HACK to give the ronn-generated page a TOC
47   -doc/api.html: doc/api.markdown doc/api_header.html doc/api_footer.html
48   - ronn -f --html doc/api.markdown \
  47 +doc/api.html: all doc/api.markdown doc/api_header.html doc/api_footer.html
  48 + build/default/node tools/ronnjs/bin/ronn.js --fragment doc/api.markdown \
49 49 | sed "s/<h2>\(.*\)<\/h2>/<h2 id=\"\1\">\1<\/h2>/g" \
50 50 | cat doc/api_header.html - doc/api_footer.html > doc/api.html
51 51
@@ -53,7 +53,7 @@ doc/changelog.html: ChangeLog doc/changelog_header.html doc/changelog_footer.htm
53 53 cat doc/changelog_header.html ChangeLog doc/changelog_footer.html > doc/changelog.html
54 54
55 55 doc/node.1: doc/api.markdown
56   - ronn --roff doc/api.markdown > doc/node.1
  56 + build/default/node tools/ronnjs/bin/ronn.js --roff doc/api.markdown > doc/node.1
57 57
58 58 website-upload: doc
59 59 scp doc/* ryan@nodejs.org:~/tinyclouds/node/
2  doc/api_header.html
@@ -300,8 +300,6 @@
300 300 </div>
301 301 <div id='man'>
302 302 <div id="man-content">
303   -<h1 class='man-title'>node(1)</h1>
304   -
305 303 <ol class='head man'>
306 304 <li class='tl'>node(1)</li>
307 305
12 tools/ronnjs/CHANGES
... ... @@ -0,0 +1,12 @@
  1 +Ronnjs Changes and Release Notes
  2 +==============================
  3 +
  4 +Version 0.2
  5 +------------------------------
  6 +
  7 +Supports output to html fragment.
  8 +
  9 +Version 0.1
  10 +------------------------------
  11 +
  12 +Initial release.
62 tools/ronnjs/LICENSE
... ... @@ -0,0 +1,62 @@
  1 +Ronnjs is a javascript port of Ronn, which is an original
  2 +work of Ryan Tomayko.
  3 +
  4 +Copyright: 2009 Ryan Tomayko <tomayko.com/about>
  5 +License: MIT
  6 +
  7 +Files: bin/ronn.js, lib/ronn.js
  8 +Copyright: 2010 Jérémy Lal <kapouer@melix.org>
  9 +License : MIT
  10 +
  11 +Files: lib/ext/markdown.js
  12 +Copyright: 2009-2010 Dominic Baggott, 2009-2010 Ash Berlin
  13 +License: MIT
  14 +
  15 +Files: lib/ext/opts.js
  16 +Copyright: 2010 Joey Mazzarelli <mazzarelli@gmail.com>. All rights reserved.
  17 +License: Simplified BSD License
  18 + Redistribution and use in source and binary forms, with or without
  19 + modification, are permitted provided that the following conditions are met:
  20 +
  21 + 1. Redistributions of source code must retain the above copyright notice,
  22 + this list of conditions and the following disclaimer.
  23 +
  24 + 2. Redistributions in binary form must reproduce the above copyright notice,
  25 + this list of conditions and the following disclaimer in the documentation
  26 + and/or other materials provided with the distribution.
  27 +
  28 + THIS SOFTWARE IS PROVIDED BY JOEY MAZZARELLI 'AS IS' AND ANY EXPRESS OR IMPLIED
  29 + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  30 + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
  31 + EVENT SHALL JOEY MAZZARELLI OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  32 + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  33 + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  34 + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  35 + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
  36 + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  37 + ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  38 +
  39 + The views and conclusions contained in the software and documentation are those
  40 + of the authors and should not be interpreted as representing official policies,
  41 + either expressed or implied, of Joey Mazzarelli.
  42 +
  43 +License: MIT
  44 + Permission is hereby granted, free of charge, to any person
  45 + obtaining a copy of this software and associated documentation
  46 + files (the "Software"), to deal in the Software without restriction,
  47 + including without limitation the rights to use, copy, modify,
  48 + merge, publish, distribute, sublicense, and/or sell copies of
  49 + the Software, and to permit persons to whom the Software is furnished
  50 + to do so, subject to the following conditions:
  51 +
  52 + The above copyright notice and this permission notice shall be
  53 + included in all copies or substantial portions of the Software.
  54 +
  55 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  56 + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  57 + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  58 + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
  59 + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
  60 + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  61 + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  62 + SOFTWARE.
25 tools/ronnjs/README
... ... @@ -0,0 +1,25 @@
  1 +ronnjs(1) -- markdown to roff converter
  2 +=======================================
  3 +
  4 +## Synopsis
  5 +
  6 +Javascript port of [ronn], using [markdown-js] to produce roff man pages.
  7 +Not fully compatible with [ronn], although it aims to be, wherever possible.
  8 +
  9 +## Usage
  10 +
  11 +This outputs doc.roff from a markdown file :
  12 +
  13 + ronn.js --build --roff doc.md
  14 +
  15 +Command-line options are listed with -h
  16 +
  17 +
  18 +## How it works ?
  19 +
  20 +[markdown-js] parses markdown text to a document model, which in turn is
  21 +used to ouput a man page.
  22 +
  23 +
  24 +[ronn]: http://github.com/rtomayko/ronn
  25 +[markdown-js]: http://github.com/evilstreak/markdown-js
7 tools/ronnjs/TODO
... ... @@ -0,0 +1,7 @@
  1 +# TODO
  2 +
  3 +* show <hr> tags using something like
  4 + \l'\n(.lu*8u/10u'
  5 + and take care of the current indentation.
  6 +
  7 +* tests !
102 tools/ronnjs/bin/ronn.js
... ... @@ -0,0 +1,102 @@
  1 +#!/usr/bin/nodejs
  2 +
  3 +var opts = require(__dirname + '/../lib/ext/opts');
  4 +var ronn = require(__dirname + '/../lib/ronn');
  5 +
  6 +var options = [
  7 + { short : 'V'
  8 + , description : 'Show version and exit'
  9 + , callback : function () { sys.puts('0.1'); process.exit(1); }
  10 + },
  11 + { short : 'b'
  12 + , long : 'build'
  13 + , description : 'Output to files with appropriate extension'
  14 + },
  15 + { short : 'm'
  16 + , long : 'man'
  17 + , description : 'Convert to roff and open with man'
  18 + },
  19 + { short : 'r'
  20 + , long : 'roff'
  21 + , description : 'Convert to roff format'
  22 + },
  23 + { short : '5'
  24 + , long : 'html'
  25 + , description : 'Convert to html format'
  26 + },
  27 + { short : 'f'
  28 + , long : 'fragment'
  29 + , description : 'Convert to html fragment format'
  30 + },
  31 + { long : 'manual'
  32 + , description : 'Set "manual" attribute'
  33 + , value : true
  34 + },
  35 + { long : 'version'
  36 + , description : 'Set "version" attribute'
  37 + , value : true
  38 + },
  39 + { long : 'date'
  40 + , description : 'Set "date" attribute'
  41 + , value : true
  42 + }
  43 +];
  44 +var arguments = [
  45 + { name : 'file'
  46 + , required : true
  47 + , description: 'A ronn file'
  48 + }
  49 +];
  50 +opts.parse(options, arguments, true);
  51 +
  52 +
  53 +var sys = require('sys');
  54 +var fs = require('fs');
  55 +var path = require('path');
  56 +
  57 +var fPath = opts.arg('file');
  58 +var fBase = path.join(path.dirname(fPath), path.basename(fPath, path.extname(fPath)));
  59 +
  60 +var fTxt = fs.readFileSync(fPath, 'utf8');
  61 +var ronn = new ronn.Ronn(fTxt, opts.get("version"), opts.get("manual"), opts.get("date"));
  62 +
  63 +if (opts.get("man") && !opts.get("build")) {
  64 + var spawn = require('child_process').spawn;
  65 + var man = spawn('man', ['--warnings', '-E UTF-8', '-l', '-'], {"LANG":"C"});
  66 + man.stdout.addListener('data', function (data) {
  67 + sys.puts(data);
  68 + });
  69 + man.stderr.addListener('data', function (data) {
  70 + sys.puts(data);
  71 + });
  72 + man.addListener('exit', function() {
  73 + process.exit(0);
  74 + });
  75 + man.stdin.write(ronn.roff(), 'utf8');
  76 + man.stdin.end();
  77 +} else {
  78 + var fRoff = null;
  79 + var fHtml = null;
  80 + var fFrag = null;
  81 + if (!opts.get("html") && !opts.get("fragment")) fRoff = ronn.roff();
  82 + else {
  83 + if (opts.get("roff")) fRoff = ronn.roff();
  84 + if (opts.get("html")) fHtml = ronn.html();
  85 + if (opts.get("fragment")) {
  86 + if (opts.get("html")) {
  87 + sys.debug("Can't use both --fragment and --html");
  88 + process.exit(-1);
  89 + }
  90 + fFrag = ronn.fragment();
  91 + }
  92 + }
  93 + if (opts.get("build")) {
  94 + if (fRoff) fs.writeFileSync(fBase + ".roff", fRoff, 'utf8');
  95 + if (fHtml) fs.writeFileSync(fBase + ".html", fHtml, 'utf8');
  96 + if (fFrag) fs.writeFileSync(fBase + ".fragment", fFrag, 'utf8');
  97 + } else {
  98 + if (fRoff) sys.puts(fRoff);
  99 + if (fHtml) sys.puts(fHtml);
  100 + if (fFrag) sys.puts(fFrag);
  101 + }
  102 +}
1,454 tools/ronnjs/lib/ext/markdown.js
... ... @@ -0,0 +1,1454 @@
  1 +// Released under MIT license
  2 +// Copyright (c) 2009-2010 Dominic Baggott
  3 +// Copyright (c) 2009-2010 Ash Berlin
  4 +
  5 +(function( expose ) {
  6 +
  7 +/**
  8 + * class Markdown
  9 + *
  10 + * Markdown processing in Javascript done right. We have very particular views
  11 + * on what constitutes 'right' which include:
  12 + *
  13 + * - produces well-formed HTML (this means that em and strong nesting is
  14 + * important)
  15 + *
  16 + * - has an intermediate representation to allow processing of parsed data (We
  17 + * in fact have two, both as [JsonML]: a markdown tree and an HTML tree).
  18 + *
  19 + * - is easily extensible to add new dialects without having to rewrite the
  20 + * entire parsing mechanics
  21 + *
  22 + * - has a good test suite
  23 + *
  24 + * This implementation fulfills all of these (except that the test suite could
  25 + * do with expanding to automatically run all the fixtures from other Markdown
  26 + * implementations.)
  27 + *
  28 + * ##### Intermediate Representation
  29 + *
  30 + * *TODO* Talk about this :) Its JsonML, but document the node names we use.
  31 + *
  32 + * [JsonML]: http://jsonml.org/ "JSON Markup Language"
  33 + **/
  34 +var Markdown = expose.Markdown = function Markdown(dialect) {
  35 + switch (typeof dialect) {
  36 + case "undefined":
  37 + this.dialect = Markdown.dialects.Gruber;
  38 + break;
  39 + case "object":
  40 + this.dialect = dialect;
  41 + break;
  42 + default:
  43 + if (dialect in Markdown.dialects) {
  44 + this.dialect = Markdown.dialects[dialect];
  45 + }
  46 + else {
  47 + throw new Error("Unknown Markdown dialect '" + String(dialect) + "'");
  48 + }
  49 + break;
  50 + }
  51 + this.em_state = [];
  52 + this.strong_state = [];
  53 + this.debug_indent = "";
  54 +}
  55 +
  56 +/**
  57 + * parse( markdown, [dialect] ) -> JsonML
  58 + * - markdown (String): markdown string to parse
  59 + * - dialect (String | Dialect): the dialect to use, defaults to gruber
  60 + *
  61 + * Parse `markdown` and return a markdown document as a Markdown.JsonML tree.
  62 + **/
  63 +expose.parse = function( source, dialect ) {
  64 + // dialect will default if undefined
  65 + var md = new Markdown( dialect );
  66 + return md.toTree( source );
  67 +}
  68 +
  69 +/**
  70 + * toHTML( markdown ) -> String
  71 + * toHTML( md_tree ) -> String
  72 + * - markdown (String): markdown string to parse
  73 + * - md_tree (Markdown.JsonML): parsed markdown tree
  74 + *
  75 + * Take markdown (either as a string or as a JsonML tree) and run it through
  76 + * [[toHTMLTree]] then turn it into a well-formated HTML fragment.
  77 + **/
  78 +expose.toHTML = function toHTML( source ) {
  79 + var input = expose.toHTMLTree( source );
  80 +
  81 + return expose.renderJsonML( input );
  82 +}
  83 +
  84 +/**
  85 + * toHTMLTree( markdown, [dialect] ) -> JsonML
  86 + * toHTMLTree( md_tree ) -> JsonML
  87 + * - markdown (String): markdown string to parse
  88 + * - dialect (String | Dialect): the dialect to use, defaults to gruber
  89 + * - md_tree (Markdown.JsonML): parsed markdown tree
  90 + *
  91 + * Turn markdown into HTML, represented as a JsonML tree. If a string is given
  92 + * to this function, it is first parsed into a markdown tree by calling
  93 + * [[parse]].
  94 + **/
  95 +expose.toHTMLTree = function toHTMLTree( input, dialect ) {
  96 + // convert string input to an MD tree
  97 + if ( typeof input ==="string" ) input = this.parse( input, dialect );
  98 +
  99 + // Now convert the MD tree to an HTML tree
  100 +
  101 + // remove references from the tree
  102 + var attrs = extract_attr( input ),
  103 + refs = {};
  104 +
  105 + if ( attrs && attrs.references ) {
  106 + refs = attrs.references;
  107 + }
  108 +
  109 + var html = convert_tree_to_html( input, refs );
  110 + merge_text_nodes( html );
  111 + return html;
  112 +}
  113 +
  114 +var mk_block = Markdown.mk_block = function(block, trail, line) {
  115 + // Be helpful for default case in tests.
  116 + if ( arguments.length == 1 ) trail = "\n\n";
  117 +
  118 + var s = new String(block);
  119 + s.trailing = trail;
  120 + // To make it clear its not just a string
  121 + s.toSource = function() {
  122 + return "Markdown.mk_block( " +
  123 + uneval(block) +
  124 + ", " +
  125 + uneval(trail) +
  126 + ", " +
  127 + uneval(line) +
  128 + " )"
  129 + }
  130 +
  131 + if (line != undefined)
  132 + s.lineNumber = line;
  133 +
  134 + return s;
  135 +}
  136 +
  137 +function count_lines( str ) {
  138 + var n = 0, i = -1;;
  139 + while ( ( i = str.indexOf('\n', i+1) ) != -1) n++;
  140 + return n;
  141 +}
  142 +
  143 +// Internal - split source into rough blocks
  144 +Markdown.prototype.split_blocks = function splitBlocks( input, startLine ) {
  145 + // [\s\S] matches _anything_ (newline or space)
  146 + var re = /([\s\S]+?)($|\n(?:\s*\n|$)+)/g,
  147 + blocks = [],
  148 + m;
  149 +
  150 + var line_no = 1;
  151 +
  152 + if ( ( m = (/^(\s*\n)/)(input) ) != null ) {
  153 + // skip (but count) leading blank lines
  154 + line_no += count_lines( m[0] );
  155 + re.lastIndex = m[0].length;
  156 + }
  157 +
  158 + while ( ( m = re(input) ) != null ) {
  159 + blocks.push( mk_block( m[1], m[2], line_no ) );
  160 + line_no += count_lines( m[0] );
  161 + }
  162 +
  163 + return blocks;
  164 +}
  165 +
  166 +/**
  167 + * Markdown#processBlock( block, next ) -> undefined | [ JsonML, ... ]
  168 + * - block (String): the block to process
  169 + * - next (Array): the following blocks
  170 + *
  171 + * Process `block` and return an array of JsonML nodes representing `block`.
  172 + *
  173 + * It does this by asking each block level function in the dialect to process
  174 + * the block until one can. Succesful handling is indicated by returning an
  175 + * array (with zero or more JsonML nodes), failure by a false value.
  176 + *
  177 + * Blocks handlers are responsible for calling [[Markdown#processInline]]
  178 + * themselves as appropriate.
  179 + *
  180 + * If the blocks were split incorrectly or adjacent blocks need collapsing you
  181 + * can adjust `next` in place using shift/splice etc.
  182 + *
  183 + * If any of this default behaviour is not right for the dialect, you can
  184 + * define a `__call__` method on the dialect that will get invoked to handle
  185 + * the block processing.
  186 + */
  187 +Markdown.prototype.processBlock = function processBlock( block, next ) {
  188 + var cbs = this.dialect.block,
  189 + ord = cbs.__order__;
  190 +
  191 + if ( "__call__" in cbs ) {
  192 + return cvs.__call__.call(this, block, next);
  193 + }
  194 +
  195 + for ( var i = 0; i < ord.length; i++ ) {
  196 + //D:this.debug( "Testing", ord[i] );
  197 + var res = cbs[ ord[i] ].call( this, block, next );
  198 + if ( res ) {
  199 + //D:this.debug(" matched");
  200 + if ( !res instanceof Array || ( res.length > 0 && !( res[0] instanceof Array ) ) )
  201 + this.debug(ord[i], "didn't return a proper array");
  202 + //D:this.debug( "" );
  203 + return res;
  204 + }
  205 + }
  206 +
  207 + // Uhoh! no match! Should we throw an error?
  208 + return [];
  209 +}
  210 +
  211 +Markdown.prototype.processInline = function processInline( block ) {
  212 + return this.dialect.inline.__call__.call( this, String( block ) );
  213 +}
  214 +
  215 +/**
  216 + * Markdown#toTree( source ) -> JsonML
  217 + * - source (String): markdown source to parse
  218 + *
  219 + * Parse `source` into a JsonML tree representing the markdown document.
  220 + **/
  221 +// custom_tree means set this.tree to `custom_tree` and restore old value on return
  222 +Markdown.prototype.toTree = function toTree( source, custom_root ) {
  223 + var blocks = source instanceof Array
  224 + ? source
  225 + : this.split_blocks( source );
  226 +
  227 + // Make tree a member variable so its easier to mess with in extensions
  228 + var old_tree = this.tree;
  229 + try {
  230 + this.tree = custom_root || this.tree || [ "markdown" ];
  231 +
  232 + blocks:
  233 + while ( blocks.length ) {
  234 + var b = this.processBlock( blocks.shift(), blocks );
  235 +
  236 + // Reference blocks and the like won't return any content
  237 + if ( !b.length ) continue blocks;
  238 +
  239 + this.tree.push.apply( this.tree, b );
  240 + }
  241 + return this.tree;
  242 + }
  243 + finally {
  244 + if ( custom_root )
  245 + this.tree = old_tree;
  246 + }
  247 +
  248 +}
  249 +
  250 +// Noop by default
  251 +Markdown.prototype.debug = function () {
  252 + var args = Array.prototype.slice.call( arguments);
  253 + args.unshift(this.debug_indent);
  254 + print.apply( print, args );
  255 +}
  256 +
  257 +Markdown.prototype.loop_re_over_block = function( re, block, cb ) {
  258 + // Dont use /g regexps with this
  259 + var m,
  260 + b = block.valueOf();
  261 +
  262 + while ( b.length && (m = re(b) ) != null) {
  263 + b = b.substr( m[0].length );
  264 + cb.call(this, m);
  265 + }
  266 + return b;
  267 +}
  268 +
  269 +/**
  270 + * Markdown.dialects
  271 + *
  272 + * Namespace of built-in dialects.
  273 + **/
  274 +Markdown.dialects = {};
  275 +
  276 +/**
  277 + * Markdown.dialects.Gruber
  278 + *
  279 + * The default dialect that follows the rules set out by John Gruber's
  280 + * markdown.pl as closely as possible. Well actually we follow the behaviour of
  281 + * that script which in some places is not exactly what the syntax web page
  282 + * says.
  283 + **/
  284 +Markdown.dialects.Gruber = {
  285 + block: {
  286 + atxHeader: function atxHeader( block, next ) {
  287 + var m = block.match( /^(#{1,6})\s*(.*?)\s*#*\s*(?:\n|$)/ );
  288 +
  289 + if ( !m ) return undefined;
  290 +
  291 + var header = [ "header", { level: m[ 1 ].length }, m[ 2 ] ];
  292 +
  293 + if ( m[0].length < block.length )
  294 + next.unshift( mk_block( block.substr( m[0].length ), block.trailing, block.lineNumber + 2 ) );
  295 +
  296 + return [ header ];
  297 + },
  298 +
  299 + setextHeader: function setextHeader( block, next ) {
  300 + var m = block.match( /^(.*)\n([-=])\2\2+(?:\n|$)/ );
  301 +
  302 + if ( !m ) return undefined;
  303 +
  304 + var level = ( m[ 2 ] === "=" ) ? 1 : 2;
  305 + var header = [ "header", { level : level }, m[ 1 ] ];
  306 +
  307 + if ( m[0].length < block.length )
  308 + next.unshift( mk_block( block.substr( m[0].length ), block.trailing, block.lineNumber + 2 ) );
  309 +
  310 + return [ header ];
  311 + },
  312 +
  313 + code: function code( block, next ) {
  314 + // | Foo
  315 + // |bar
  316 + // should be a code block followed by a paragraph. Fun
  317 + //
  318 + // There might also be adjacent code block to merge.
  319 +
  320 + var ret = [],
  321 + re = /^(?: {0,3}\t| {4})(.*)\n?/,
  322 + lines;
  323 +
  324 + // 4 spaces + content
  325 + var m = block.match( re );
  326 +
  327 + if ( !m ) return undefined;
  328 +
  329 + block_search:
  330 + do {
  331 + // Now pull out the rest of the lines
  332 + var b = this.loop_re_over_block(
  333 + re, block.valueOf(), function( m ) { ret.push( m[1] ) } );
  334 +
  335 + if (b.length) {
  336 + // Case alluded to in first comment. push it back on as a new block
  337 + next.unshift( mk_block(b, block.trailing) );
  338 + break block_search;
  339 + }
  340 + else if (next.length) {
  341 + // Check the next block - it might be code too
  342 + var m = next[0].match( re );
  343 +
  344 + if ( !m ) break block_search;
  345 +
  346 + // Pull how how many blanks lines follow - minus two to account for .join
  347 + ret.push ( block.trailing.replace(/[^\n]/g, '').substring(2) );
  348 +
  349 + block = next.shift();
  350 + }
  351 + else
  352 + break block_search;
  353 + } while (true);
  354 +
  355 + return [ [ "code_block", ret.join("\n") ] ];
  356 + },
  357 +
  358 + horizRule: function horizRule( block, next ) {
  359 + // this needs to find any hr in the block to handle abutting blocks
  360 + var m = block.match( /^(?:([\s\S]*?)\n)?[ \t]*([-_*])(?:[ \t]*\2){2,}[ \t]*(?:\n([\s\S]*))?$/ );
  361 +
  362 + if ( !m ) {
  363 + return undefined;
  364 + }
  365 +
  366 + var jsonml = [ [ "hr" ] ];
  367 +
  368 + // if there's a leading abutting block, process it
  369 + if ( m[ 1 ] ) {
  370 + jsonml.unshift.apply( jsonml, this.processBlock( m[ 1 ], [] ) );
  371 + }
  372 +
  373 + // if there's a trailing abutting block, stick it into next
  374 + if ( m[ 3 ] ) {
  375 + next.unshift( mk_block( m[ 3 ] ) );
  376 + }
  377 +
  378 + return jsonml;
  379 + },
  380 +
  381 + // There are two types of lists. Tight and loose. Tight lists have no whitespace
  382 + // between the items (and result in text just in the <li>) and loose lists,
  383 + // which have an empty line between list items, resulting in (one or more)
  384 + // paragraphs inside the <li>.
  385 + //
  386 + // There are all sorts weird edge cases about the original markdown.pl's
  387 + // handling of lists:
  388 + //
  389 + // * Nested lists are supposed to be indented by four chars per level. But
  390 + // if they aren't, you can get a nested list by indenting by less than
  391 + // four so long as the indent doesn't match an indent of an existing list
  392 + // item in the 'nest stack'.
  393 + //
  394 + // * The type of the list (bullet or number) is controlled just by the
  395 + // first item at the indent. Subsequent changes are ignored unless they
  396 + // are for nested lists
  397 + //
  398 + lists: (function( ) {
  399 + // Use a closure to hide a few variables.
  400 + var any_list = "[*+-]|\\d\\.",
  401 + bullet_list = /[*+-]/,
  402 + number_list = /\d+\./,
  403 + // Capture leading indent as it matters for determining nested lists.
  404 + is_list_re = new RegExp( "^( {0,3})(" + any_list + ")[ \t]+" ),
  405 + indent_re = "(?: {0,3}\\t| {4})";
  406 +
  407 + // TODO: Cache this regexp for certain depths.
  408 + // Create a regexp suitable for matching an li for a given stack depth
  409 + function regex_for_depth( depth ) {
  410 +
  411 + return new RegExp(
  412 + // m[1] = indent, m[2] = list_type
  413 + "(?:^(" + indent_re + "{0," + depth + "} {0,3})(" + any_list + ")\\s+)|" +
  414 + // m[3] = cont
  415 + "(^" + indent_re + "{0," + (depth-1) + "}[ ]{0,4})"
  416 + );
  417 + }
  418 + function expand_tab( input ) {
  419 + return input.replace( / {0,3}\t/g, " " );
  420 + }
  421 +
  422 + // Add inline content `inline` to `li`. inline comes from processInline
  423 + // so is an array of content
  424 + function add(li, loose, inline, nl) {
  425 + if (loose) {
  426 + li.push( [ "para" ].concat(inline) );
  427 + return;
  428 + }
  429 + // Hmmm, should this be any block level element or just paras?
  430 + var add_to = li[li.length -1] instanceof Array && li[li.length - 1][0] == "para"
  431 + ? li[li.length -1]
  432 + : li;
  433 +
  434 + // If there is already some content in this list, add the new line in
  435 + if (nl && li.length > 1) inline.unshift(nl);
  436 +
  437 + for (var i=0; i < inline.length; i++) {
  438 + var what = inline[i],
  439 + is_str = typeof what == "string";
  440 + if (is_str && add_to.length > 1 && typeof add_to[add_to.length-1] == "string" )
  441 + {
  442 + add_to[ add_to.length-1 ] += what;
  443 + }
  444 + else {
  445 + add_to.push( what );
  446 + }
  447 + }
  448 + }
  449 +
  450 + // contained means have an indent greater than the current one. On
  451 + // *every* line in the block
  452 + function get_contained_blocks( depth, blocks ) {
  453 +
  454 + var re = new RegExp( "^(" + indent_re + "{" + depth + "}.*?\\n?)*$" ),
  455 + replace = new RegExp("^" + indent_re + "{" + depth + "}", "gm"),
  456 + ret = [];
  457 +
  458 + while ( blocks.length > 0 ) {
  459 + if ( re( blocks[0] ) ) {
  460 + var b = blocks.shift(),
  461 + // Now remove that indent
  462 + x = b.replace( replace, "");
  463 +
  464 + ret.push( mk_block( x, b.trailing, b.lineNumber ) );
  465 + }
  466 + break;
  467 + }
  468 + return ret;
  469 + }
  470 +
  471 + // passed to stack.forEach to turn list items up the stack into paras
  472 + function paragraphify(s, i, stack) {
  473 + var list = s.list;
  474 + var last_li = list[list.length-1];
  475 +
  476 + if (last_li[1] instanceof Array && last_li[1][0] == "para") {
  477 + return;
  478 + }
  479 + if (i+1 == stack.length) {
  480 + // Last stack frame
  481 + // Keep the same array, but replace the contents
  482 + last_li.push( ["para"].concat( last_li.splice(1) ) );
  483 + }
  484 + else {
  485 + var sublist = last_li.pop();
  486 + last_li.push( ["para"].concat( last_li.splice(1) ), sublist );
  487 + }
  488 + }
  489 +
  490 + // The matcher function
  491 + return function( block, next ) {
  492 + var m = block.match( is_list_re );
  493 + if ( !m ) return undefined;
  494 +
  495 + function make_list( m ) {
  496 + var list = bullet_list( m[2] )
  497 + ? ["bulletlist"]
  498 + : ["numberlist"];
  499 +
  500 + stack.push( { list: list, indent: m[1] } );
  501 + return list;
  502 + }
  503 +
  504 +
  505 + var stack = [], // Stack of lists for nesting.
  506 + list = make_list( m ),
  507 + last_li,
  508 + loose = false,
  509 + ret = [ stack[0].list ];
  510 +
  511 + // Loop to search over block looking for inner block elements and loose lists
  512 + loose_search:
  513 + while( true ) {
  514 + // Split into lines preserving new lines at end of line
  515 + var lines = block.split( /(?=\n)/ );
  516 +
  517 + // We have to grab all lines for a li and call processInline on them
  518 + // once as there are some inline things that can span lines.
  519 + var li_accumulate = "";
  520 +
  521 + // Loop over the lines in this block looking for tight lists.
  522 + tight_search:
  523 + for (var line_no=0; line_no < lines.length; line_no++) {
  524 + var nl = "",
  525 + l = lines[line_no].replace(/^\n/, function(n) { nl = n; return "" });
  526 +
  527 + // TODO: really should cache this
  528 + var line_re = regex_for_depth( stack.length );
  529 +
  530 + m = l.match( line_re );
  531 + //print( "line:", uneval(l), "\nline match:", uneval(m) );
  532 +
  533 + // We have a list item
  534 + if ( m[1] !== undefined ) {
  535 + // Process the previous list item, if any
  536 + if ( li_accumulate.length ) {
  537 + add( last_li, loose, this.processInline( li_accumulate ), nl );
  538 + // Loose mode will have been dealt with. Reset it
  539 + loose = false;
  540 + li_accumulate = "";
  541 + }
  542 +
  543 + m[1] = expand_tab( m[1] );
  544 + var wanted_depth = Math.floor(m[1].length/4)+1;
  545 + //print( "want:", wanted_depth, "stack:", stack.length);
  546 + if ( wanted_depth > stack.length ) {
  547 + // Deep enough for a nested list outright
  548 + //print ( "new nested list" );
  549 + list = make_list( m );
  550 + last_li.push( list );
  551 + last_li = list[1] = [ "listitem" ];
  552 + }
  553 + else {
  554 + // We aren't deep enough to be strictly a new level. This is
  555 + // where Md.pl goes nuts. If the indent matches a level in the
  556 + // stack, put it there, else put it one deeper then the
  557 + // wanted_depth deserves.
  558 + var found = stack.some(function(s, i) {
  559 + if ( s.indent != m[1] ) return false;
  560 + list = s.list; // Found the level we want
  561 + stack.splice(i+1); // Remove the others
  562 + //print("found");
  563 + return true; // And stop looping
  564 + });
  565 +
  566 + if (!found) {
  567 + //print("not found. l:", uneval(l));
  568 + wanted_depth++;
  569 + if (wanted_depth <= stack.length) {
  570 + stack.splice(wanted_depth);
  571 + //print("Desired depth now", wanted_depth, "stack:", stack.length);
  572 + list = stack[wanted_depth-1].list;
  573 + //print("list:", uneval(list) );
  574 + }
  575 + else {
  576 + //print ("made new stack for messy indent");
  577 + list = make_list(m);
  578 + last_li.push(list);
  579 + }
  580 + }
  581 +
  582 + //print( uneval(list), "last", list === stack[stack.length-1].list );
  583 + last_li = [ "listitem" ];
  584 + list.push(last_li);
  585 + } // end depth of shenegains
  586 + nl = "";
  587 + }
  588 +
  589 + // Add content
  590 + if (l.length > m[0].length) {
  591 + li_accumulate += nl + l.substr( m[0].length );
  592 + }
  593 + } // tight_search
  594 +
  595 + if ( li_accumulate.length ) {
  596 + add( last_li, loose, this.processInline( li_accumulate ), nl );
  597 + // Loose mode will have been dealt with. Reset it
  598 + loose = false;
  599 + li_accumulate = "";
  600 + }
  601 +
  602 + // Look at the next block - we might have a loose list. Or an extra
  603 + // paragraph for the current li
  604 + var contained = get_contained_blocks( stack.length, next );
  605 +
  606 + // Deal with code blocks or properly nested lists
  607 + if (contained.length > 0) {
  608 + // Make sure all listitems up the stack are paragraphs
  609 + stack.forEach( paragraphify, this );
  610 +
  611 + last_li.push.apply( last_li, this.toTree( contained, [] ) );
  612 + }
  613 +
  614 + var next_block = next[0] && next[0].valueOf() || "";
  615 +
  616 + if ( next_block.match(is_list_re) || next_block.match( /^ / ) ) {
  617 + block = next.shift();
  618 +
  619 + // Check for an HR following a list: features/lists/hr_abutting
  620 + var hr = this.dialect.block.horizRule( block, next );
  621 +
  622 + if (hr) {
  623 + ret.push.apply(ret, hr);
  624 + break;
  625 + }
  626 +
  627 + // Make sure all listitems up the stack are paragraphs
  628 + stack.forEach( paragraphify , this );
  629 +
  630 + loose = true;
  631 + continue loose_search;
  632 + }
  633 + break;
  634 + } // loose_search
  635 +
  636 + return ret;
  637 + }
  638 + })(),
  639 +
  640 + blockquote: function blockquote( block, next ) {
  641 + if ( !block.match( /^>/m ) )
  642 + return undefined;
  643 +
  644 + var jsonml = [];
  645 +
  646 + // separate out the leading abutting block, if any
  647 + if ( block[ 0 ] != ">" ) {
  648 + var lines = block.split( /\n/ ),
  649 + prev = [];
  650 +
  651 + // keep shifting lines until you find a crotchet
  652 + while ( lines.length && lines[ 0 ][ 0 ] != ">" ) {
  653 + prev.push( lines.shift() );
  654 + }
  655 +
  656 + // reassemble!
  657 + block = lines.join( "\n" );
  658 + jsonml.push.apply( jsonml, this.processBlock( prev.join( "\n" ), [] ) );
  659 + }
  660 +
  661 + // if the next block is also a blockquote merge it in
  662 + while ( next.length && next[ 0 ][ 0 ] == ">" ) {
  663 + var b = next.shift();
  664 + block += block.trailing + b;
  665 + block.trailing = b.trailing;
  666 + }
  667 +
  668 + // Strip off the leading "> " and re-process as a block.
  669 + var input = block.replace( /^> ?/gm, '' ),
  670 + old_tree = this.tree;
  671 + jsonml.push( this.toTree( input, [ "blockquote" ] ) );
  672 +
  673 + return jsonml;
  674 + },
  675 +
  676 + referenceDefn: function referenceDefn( block, next) {
  677 + var re = /^\s*\[(.*?)\]:\s*(\S+)(?:\s+(?:(['"])(.*?)\3|\((.*?)\)))?\n?/;
  678 + // interesting matches are [ , ref_id, url, , title, title ]
  679 +
  680 + if ( !block.match(re) )
  681 + return undefined;
  682 +
  683 + // make an attribute node if it doesn't exist
  684 + if ( !extract_attr( this.tree ) ) {
  685 + this.tree.splice( 1, 0, {} );
  686 + }
  687 +
  688 + var attrs = extract_attr( this.tree );
  689 +
  690 + // make a references hash if it doesn't exist
  691 + if ( attrs.references === undefined ) {
  692 + attrs.references = {};
  693 + }
  694 +
  695 + var b = this.loop_re_over_block(re, block, function( m ) {
  696 +
  697 + if ( m[2] && m[2][0] == '<' && m[2][m[2].length-1] == '>' )
  698 + m[2] = m[2].substring( 1, m[2].length - 1 );
  699 +
  700 + var ref = attrs.references[ m[1].toLowerCase() ] = {
  701 + href: m[2]
  702 + };
  703 +
  704 + if (m[4] !== undefined)
  705 + ref.title = m[4];
  706 + else if (m[5] !== undefined)
  707 + ref.title = m[5];
  708 +
  709 + } );
  710 +
  711 + if (b.length)
  712 + next.unshift( mk_block( b, block.trailing ) );
  713 +
  714 + return [];
  715 + },
  716 +
  717 + para: function para( block, next ) {
  718 + // everything's a para!
  719 + return [ ["para"].concat( this.processInline( block ) ) ];
  720 + }
  721 + }
  722 +}
  723 +
  724 +Markdown.dialects.Gruber.inline = {
  725 + __call__: function inline( text, patterns ) {
  726 + // Hmmm - should this function be directly in Md#processInline, or
  727 + // conversely, should Md#processBlock be moved into block.__call__ too
  728 + var out = [ ],
  729 + m,
  730 + // Look for the next occurange of a special character/pattern
  731 + re = new RegExp( "([\\s\\S]*?)(" + (patterns.source || patterns) + ")", "g" ),
  732 + lastIndex = 0;
  733 +
  734 + //D:var self = this;
  735 + //D:self.debug("processInline:", uneval(text) );
  736 + function add(x) {
  737 + //D:self.debug(" adding output", uneval(x));
  738 + if (typeof x == "string" && typeof out[out.length-1] == "string")
  739 + out[ out.length-1 ] += x;
  740 + else
  741 + out.push(x);
  742 + }
  743 +
  744 + while ( ( m = re.exec(text) ) != null) {
  745 + if ( m[1] ) add( m[1] ); // Some un-interesting text matched
  746 + else m[1] = { length: 0 }; // Or there was none, but make m[1].length == 0
  747 +
  748 + var res;
  749 + if ( m[2] in this.dialect.inline ) {
  750 + res = this.dialect.inline[ m[2] ].call(
  751 + this,
  752 + text.substr( m.index + m[1].length ), m, out );
  753 + }
  754 + // Default for now to make dev easier. just slurp special and output it.
  755 + res = res || [ m[2].length, m[2] ];
  756 +
  757 + var len = res.shift();
  758 + // Update how much input was consumed
  759 + re.lastIndex += ( len - m[2].length );
  760 +
  761 + // Add children
  762 + res.forEach(add);
  763 +
  764 + lastIndex = re.lastIndex;
  765 + }
  766 +
  767 + // Add last 'boring' chunk
  768 + if ( text.length > lastIndex )
  769 + add( text.substr( lastIndex ) );
  770 +
  771 + return out;
  772 + },
  773 +
  774 + "\\": function escaped( text ) {
  775 + // [ length of input processed, node/children to add... ]
  776 + // Only esacape: \ ` * _ { } [ ] ( ) # * + - . !
  777 + if ( text.match( /^\\[\\`\*_{}\[\]()#\+.!\-]/ ) )
  778 + return [ 2, text[1] ];
  779 + else
  780 + // Not an esacpe
  781 + return [ 1, "\\" ];
  782 + },
  783 +
  784 + "![": function image( text ) {
  785 + // ![Alt text](/path/to/img.jpg "Optional title")
  786 + // 1 2 3 4 <--- captures
  787 + var m = text.match( /^!\[(.*?)\][ \t]*\([ \t]*(\S*)(?:[ \t]+(["'])(.*?)\3)?[ \t]*\)/ );
  788 +
  789 + if ( m ) {
  790 + if ( m[2] && m[2][0] == '<' && m[2][m[2].length-1] == '>' )
  791 + m[2] = m[2].substring( 1, m[2].length - 1 );
  792 +
  793 + m[2] == this.dialect.inline.__call__.call( this, m[2], /\\/ )[0];
  794 +
  795 + var attrs = { alt: m[1], href: m[2] || "" };
  796 + if ( m[4] !== undefined)
  797 + attrs.title = m[4];
  798 +
  799 + return [ m[0].length, [ "img", attrs ] ];
  800 + }
  801 +
  802 + // ![Alt text][id]
  803 + m = text.match( /^!\[(.*?)\][ \t]*\[(.*?)\]/ );
  804 +
  805 + if ( m ) {
  806 + // We can't check if the reference is known here as it likely wont be
  807 + // found till after. Check it in md tree->hmtl tree conversion
  808 + return [ m[0].length, [ "img_ref", { alt: m[1], ref: m[2].toLowerCase(), text: m[0] } ] ];
  809 + }
  810 +
  811 + // Just consume the '!['
  812 + return [ 2, "![" ];
  813 + },
  814 +
  815 + "[": function link( text ) {
  816 + // [link text](/path/to/img.jpg "Optional title")
  817 + // 1 2 3 4 <--- captures
  818 + var m = text.match( /^\[([\s\S]*?)\][ \t]*\([ \t]*(\S+)(?:[ \t]+(["'])(.*?)\3)?[ \t]*\)/ );
  819 +
  820 + if ( m ) {
  821 + if ( m[2] && m[2][0] == '<' && m[2][m[2].length-1] == '>' )
  822 + m[2] = m[2].substring( 1, m[2].length - 1 );
  823 +
  824 + // Process escapes only
  825 + m[2] = this.dialect.inline.__call__.call( this, m[2], /\\/ )[0];
  826 +
  827 + var attrs = { href: m[2] || "" };
  828 + if ( m[4] !== undefined)
  829 + attrs.title = m[4];
  830 +
  831 + return [ m[0].length, [ "link", attrs, m[1] ] ];
  832 + }
  833 +
  834 + // [Alt text][id]
  835 + // [Alt text] [id]
  836 + // [id]
  837 + m = text.match( /^\[([\s\S]*?)\](?: ?\[(.*?)\])?/ );
  838 +
  839 + if ( m ) {
  840 + // [id] case, text == id
  841 + if ( m[2] === undefined || m[2] === "" ) m[2] = m[1];
  842 +
  843 + // We can't check if the reference is known here as it likely wont be
  844 + // found till after. Check it in md tree->hmtl tree conversion.
  845 + // Store the original so that conversion can revert if the ref isn't found.
  846 + return [
  847 + m[ 0 ].length,
  848 + [
  849 + "link_ref",
  850 + {
  851 + ref: m[ 2 ].toLowerCase(),
  852 + original: m[ 0 ]
  853 + },
  854 + m[ 1 ]
  855 + ]
  856 + ];
  857 + }
  858 +
  859 + // Just consume the '['
  860 + return [ 1, "[" ];
  861 + },
  862 +
  863 +
  864 + "<": function autoLink( text ) {
  865 + var m;
  866 +
  867 + if ( ( m = text.match( /^<(?:((https?|ftp|mailto):[^>]+)|(.*?@.*?\.[a-zA-Z]+))>/ ) ) != null ) {
  868 + if ( m[3] ) {
  869 + return [ m[0].length, [ "link", { href: "mailto:" + m[3] }, m[3] ] ];
  870 +
  871 + }
  872 + else if ( m[2] == "mailto" ) {
  873 + return [ m[0].length, [ "link", { href: m[1] }, m[1].substr("mailto:".length ) ] ];
  874 + }
  875 + else
  876 + return [ m[0].length, [ "link", { href: m[1] }, m[1] ] ];
  877 + }
  878 +
  879 + return [ 1, "<" ];
  880 + },
  881 +
  882 + "`": function inlineCode( text ) {
  883 + // Inline code block. as many backticks as you like to start it
  884 + // Always skip over the opening ticks.
  885 + var m = text.match( /(`+)(([\s\S]*?)\1)/ );
  886 +
  887 + if ( m && m[2] )
  888 + return [ m[1].length + m[2].length, [ "inlinecode", m[3] ] ];
  889 + else {
  890 + // TODO: No matching end code found - warn!
  891 + return [ 1, "`" ];
  892 + }
  893 + },
  894 +
  895 + " \n": function lineBreak( text ) {
  896 + return [ 3, [ "linebreak" ] ];
  897 + }
  898 +
  899 +}
  900 +
  901 +// Meta Helper/generator method for em and strong handling
  902 +function strong_em( tag, md ) {
  903 +
  904 + var state_slot = tag + "_state",
  905 + other_slot = tag == "strong" ? "em_state" : "strong_state";
  906 +
  907 + function CloseTag(len) {
  908 + this.len_after = len;
  909 + this.name = "close_" + md;
  910 + }
  911 +
  912 + return function ( text, orig_match ) {
  913 +
  914 + if (this[state_slot][0] == md) {
  915 + // Most recent em is of this type
  916 + //D:this.debug("closing", md);
  917 + this[state_slot].shift();
  918 +
  919 + // "Consume" everything to go back to the recrusion in the else-block below
  920 + return[ text.length, new CloseTag(text.length-md.length) ];
  921 + }