From 929e60dd992e803e71bda7f7a7cc7f897524767d Mon Sep 17 00:00:00 2001 From: Jeff Williams Date: Sun, 14 Oct 2012 09:13:53 -0700 Subject: [PATCH] update evilstreak markdown plugin; move to node_modules --- Jake/templates/package.json.tmpl | 3 + node_modules/markdown/README.markdown | 94 +++ node_modules/markdown/lib/index.js | 3 + .../markdown/lib}/markdown.js | 790 +++++++++++------- node_modules/markdown/package.json | 63 ++ package.json | 5 +- plugins/markdown.js | 4 +- rhino_modules/jsdoc/readme.js | 4 +- rhino_modules/jsdoc/tutorial.js | 2 +- 9 files changed, 652 insertions(+), 316 deletions(-) create mode 100644 node_modules/markdown/README.markdown create mode 100644 node_modules/markdown/lib/index.js rename {nodejs_modules/evilstreak => node_modules/markdown/lib}/markdown.js (78%) create mode 100644 node_modules/markdown/package.json diff --git a/Jake/templates/package.json.tmpl b/Jake/templates/package.json.tmpl index 38b0ee93..af030caa 100644 --- a/Jake/templates/package.json.tmpl +++ b/Jake/templates/package.json.tmpl @@ -16,6 +16,9 @@ "url": "https://github.com/jsdoc3/jsdoc" } ], + "dependencies": { + "markdown": "0.4.0" + }, "bugs": "https://github.com/jsdoc3/jsdoc/issues", "contributors" : [ { diff --git a/node_modules/markdown/README.markdown b/node_modules/markdown/README.markdown new file mode 100644 index 00000000..392f943f --- /dev/null +++ b/node_modules/markdown/README.markdown @@ -0,0 +1,94 @@ +markdown-js +=========== + +Yet another markdown parser, this time for JavaScript. There's a few +options that precede this project but they all treat markdown to HTML +conversion as a single step process. You pass markdown in and get HTML +out, end of story. We had some pretty particular views on how the +process should actually look, which include: + + * producing well-formed HTML. This means that em and strong nesting is + important, as is the ability to output as both HTML and XHTML + + * having an intermediate representation to allow processing of parsed + data (we in fact have two, both [JsonML]: a markdown tree and an + HTML tree) + + * being easily extensible to add new dialects without having to + rewrite the entire parsing mechanics + + * having a good test suite. The only test suites we could find tested + massive blocks of input, and passing depended on outputting the HTML + with exactly the same whitespace as the original implementation + +[JsonML]: http://jsonml.org/ "JSON Markup Language" + +## Installation + +Just the `markdown` library: + + npm install markdown + +Also install `md2html` to `/usr/local/bin` (or wherever) + + npm install -g markdown + +## Usage + +The simple way to use it with CommonJS is: + + var input = "# Heading\n\nParagraph"; + var output = require( "markdown" ).markdown.toHTML( input ); + print( output ); + +If you want more control check out the documentation in +[lib/markdown.js] which details all the methods and parameters +available (including examples!). One day we'll get the docs generated +and hosted somewhere for nicer browsing. + +We're yet to try it out in a browser, though it's high up on our list of +things to sort out for this project. + +### md2html + + md2html /path/to/doc.md > /path/to/doc.html + +[lib/markdown.js]: http://github.com/evilstreak/markdown-js/blob/master/lib/markdown.js + +## Intermediate Representation + +Internally the process to convert a chunk of markdown into a chunk of +HTML has three steps: + + 1. Parse the markdown into a JsonML tree. Any references found in the + parsing are stored in the attribute hash of the root node under the + key `references`. + + 2. Convert the markdown tree into an HTML tree. Rename any nodes that + need it (`bulletlist` to `ul` for example) and lookup any references + used by links or images. Remove the references attribute once done. + + 3. Stringify the HTML tree being careful not to wreck whitespace where + whitespace is important (surrounding inline elements for example). + +Each step of this process can be called individually if you need to do +some processing or modification of the data at an intermediate stage. +For example, you may want to grab a list of all URLs linked to in the +document before rendering it to HTML which you could do by recursing +through the HTML tree looking for `a` nodes. + +## Running tests + +To run the tests under node you will need tap installed (it's listed as a +devDependencies so `npm install` from the checkout should be enough), then do + + $ ./node_modules/.bin/tap test/*.t.js + +## Contributing + +Do the usual github fork and pull request dance. Add yourself to the +contributors section of package.json too if you want to. + +## License + +Released under the MIT license. diff --git a/node_modules/markdown/lib/index.js b/node_modules/markdown/lib/index.js new file mode 100644 index 00000000..8bb08734 --- /dev/null +++ b/node_modules/markdown/lib/index.js @@ -0,0 +1,3 @@ +// super simple module for the most common nodejs use case. +exports.markdown = require("./markdown"); +exports.parse = exports.markdown.toHTML; diff --git a/nodejs_modules/evilstreak/markdown.js b/node_modules/markdown/lib/markdown.js similarity index 78% rename from nodejs_modules/evilstreak/markdown.js rename to node_modules/markdown/lib/markdown.js index cdfbeb65..dbf670ee 100644 --- a/nodejs_modules/evilstreak/markdown.js +++ b/node_modules/markdown/lib/markdown.js @@ -1,6 +1,7 @@ // Released under MIT license // Copyright (c) 2009-2010 Dominic Baggott // Copyright (c) 2009-2010 Ash Berlin +// Copyright (c) 2011 Christoph Dorn (http://www.christophdorn.com) (function( expose ) { @@ -51,7 +52,7 @@ var Markdown = expose.Markdown = function Markdown(dialect) { this.em_state = []; this.strong_state = []; this.debug_indent = ""; -} +}; /** * parse( markdown, [dialect] ) -> JsonML @@ -64,10 +65,10 @@ expose.parse = function( source, dialect ) { // dialect will default if undefined var md = new Markdown( dialect ); return md.toTree( source ); -} +}; /** - * toHTML( markdown ) -> String + * toHTML( markdown, [dialect] ) -> String * toHTML( md_tree ) -> String * - markdown (String): markdown string to parse * - md_tree (Markdown.JsonML): parsed markdown tree @@ -75,11 +76,11 @@ expose.parse = function( source, dialect ) { * Take markdown (either as a string or as a JsonML tree) and run it through * [[toHTMLTree]] then turn it into a well-formated HTML fragment. **/ -expose.toHTML = function toHTML( source ) { - var input = expose.toHTMLTree( source ); +expose.toHTML = function toHTML( source , dialect , options ) { + var input = expose.toHTMLTree( source , dialect , options ); return expose.renderJsonML( input ); -} +}; /** * toHTMLTree( markdown, [dialect] ) -> JsonML @@ -92,7 +93,7 @@ expose.toHTML = function toHTML( source ) { * to this function, it is first parsed into a markdown tree by calling * [[parse]]. **/ -expose.toHTMLTree = function toHTMLTree( input, dialect ) { +expose.toHTMLTree = function toHTMLTree( input, dialect , options ) { // convert string input to an MD tree if ( typeof input ==="string" ) input = this.parse( input, dialect ); @@ -106,9 +107,33 @@ expose.toHTMLTree = function toHTMLTree( input, dialect ) { refs = attrs.references; } - var html = convert_tree_to_html( input, refs ); + var html = convert_tree_to_html( input, refs , options ); merge_text_nodes( html ); return html; +}; + +// For Spidermonkey based engines +function mk_block_toSource() { + return "Markdown.mk_block( " + + uneval(this.toString()) + + ", " + + uneval(this.trailing) + + ", " + + uneval(this.lineNumber) + + " )"; +} + +// node +function mk_block_inspect() { + var util = require('util'); + return "Markdown.mk_block( " + + util.inspect(this.toString()) + + ", " + + util.inspect(this.trailing) + + ", " + + util.inspect(this.lineNumber) + + " )"; + } var mk_block = Markdown.mk_block = function(block, trail, line) { @@ -118,25 +143,18 @@ var mk_block = Markdown.mk_block = function(block, trail, line) { var s = new String(block); s.trailing = trail; // To make it clear its not just a string - s.toSource = function() { - return "Markdown.mk_block( " + - uneval(block) + - ", " + - uneval(trail) + - ", " + - uneval(line) + - " )" - } + s.inspect = mk_block_inspect; + s.toSource = mk_block_toSource; if (line != undefined) s.lineNumber = line; return s; -} +}; function count_lines( str ) { - var n = 0, i = -1;; - while ( ( i = str.indexOf('\n', i+1) ) != -1) n++; + var n = 0, i = -1; + while ( ( i = str.indexOf('\n', i+1) ) !== -1) n++; return n; } @@ -149,19 +167,19 @@ Markdown.prototype.split_blocks = function splitBlocks( input, startLine ) { var line_no = 1; - if ( ( m = (/^(\s*\n)/)(input) ) != null ) { + if ( ( m = /^(\s*\n)/.exec(input) ) != null ) { // skip (but count) leading blank lines line_no += count_lines( m[0] ); re.lastIndex = m[0].length; } - while ( ( m = re(input) ) != null ) { + while ( ( m = re.exec(input) ) !== null ) { blocks.push( mk_block( m[1], m[2], line_no ) ); line_no += count_lines( m[0] ); } return blocks; -} +}; /** * Markdown#processBlock( block, next ) -> undefined | [ JsonML, ... ] @@ -189,7 +207,7 @@ Markdown.prototype.processBlock = function processBlock( block, next ) { ord = cbs.__order__; if ( "__call__" in cbs ) { - return cvs.__call__.call(this, block, next); + return cbs.__call__.call(this, block, next); } for ( var i = 0; i < ord.length; i++ ) { @@ -197,7 +215,7 @@ Markdown.prototype.processBlock = function processBlock( block, next ) { var res = cbs[ ord[i] ].call( this, block, next ); if ( res ) { //D:this.debug(" matched"); - if ( !res instanceof Array || ( res.length > 0 && !( res[0] instanceof Array ) ) ) + if ( !isArray(res) || ( res.length > 0 && !( isArray(res[0]) ) ) ) this.debug(ord[i], "didn't return a proper array"); //D:this.debug( "" ); return res; @@ -206,11 +224,11 @@ Markdown.prototype.processBlock = function processBlock( block, next ) { // Uhoh! no match! Should we throw an error? return []; -} +}; Markdown.prototype.processInline = function processInline( block ) { return this.dialect.inline.__call__.call( this, String( block ) ); -} +}; /** * Markdown#toTree( source ) -> JsonML @@ -220,9 +238,7 @@ Markdown.prototype.processInline = function processInline( block ) { **/ // custom_tree means set this.tree to `custom_tree` and restore old value on return Markdown.prototype.toTree = function toTree( source, custom_root ) { - var blocks = source instanceof Array - ? source - : this.split_blocks( source ); + var blocks = source instanceof Array ? source : this.split_blocks( source ); // Make tree a member variable so its easier to mess with in extensions var old_tree = this.tree; @@ -241,17 +257,20 @@ Markdown.prototype.toTree = function toTree( source, custom_root ) { return this.tree; } finally { - if ( custom_root ) + if ( custom_root ) { this.tree = old_tree; + } } - -} +}; // Noop by default Markdown.prototype.debug = function () { var args = Array.prototype.slice.call( arguments); args.unshift(this.debug_indent); - print.apply( print, args ); + if (typeof print !== "undefined") + print.apply( print, args ); + if (typeof console !== "undefined" && typeof console.log !== "undefined") + console.log.apply( null, args ); } Markdown.prototype.loop_re_over_block = function( re, block, cb ) { @@ -259,12 +278,12 @@ Markdown.prototype.loop_re_over_block = function( re, block, cb ) { var m, b = block.valueOf(); - while ( b.length && (m = re(b) ) != null) { + while ( b.length && (m = re.exec(b) ) != null) { b = b.substr( m[0].length ); cb.call(this, m); } return b; -} +}; /** * Markdown.dialects @@ -288,7 +307,8 @@ Markdown.dialects.Gruber = { if ( !m ) return undefined; - var header = [ "header", { level: m[ 1 ].length }, m[ 2 ] ]; + var header = [ "header", { level: m[ 1 ].length } ]; + Array.prototype.push.apply(header, this.processInline(m[ 2 ])); if ( m[0].length < block.length ) next.unshift( mk_block( block.substr( m[0].length ), block.trailing, block.lineNumber + 2 ) ); @@ -322,15 +342,13 @@ Markdown.dialects.Gruber = { lines; // 4 spaces + content - var m = block.match( re ); - - if ( !m ) return undefined; + if ( !block.match( re ) ) return undefined; block_search: do { // Now pull out the rest of the lines var b = this.loop_re_over_block( - re, block.valueOf(), function( m ) { ret.push( m[1] ) } ); + re, block.valueOf(), function( m ) { ret.push( m[1] ); } ); if (b.length) { // Case alluded to in first comment. push it back on as a new block @@ -339,17 +357,16 @@ Markdown.dialects.Gruber = { } else if (next.length) { // Check the next block - it might be code too - var m = next[0].match( re ); - - if ( !m ) break block_search; + if ( !next[0].match( re ) ) break block_search; // Pull how how many blanks lines follow - minus two to account for .join ret.push ( block.trailing.replace(/[^\n]/g, '').substring(2) ); block = next.shift(); } - else + else { break block_search; + } } while (true); return [ [ "code_block", ret.join("\n") ] ]; @@ -397,7 +414,7 @@ Markdown.dialects.Gruber = { // lists: (function( ) { // Use a closure to hide a few variables. - var any_list = "[*+-]|\\d\\.", + var any_list = "[*+-]|\\d+\\.", bullet_list = /[*+-]/, number_list = /\d+\./, // Capture leading indent as it matters for determining nested lists. @@ -423,7 +440,7 @@ Markdown.dialects.Gruber = { // so is an array of content function add(li, loose, inline, nl) { if (loose) { - li.push( [ "para" ].concat(inline) ); + li.push( [ "para" ].concat(inline) ); return; } // Hmmm, should this be any block level element or just paras? @@ -437,8 +454,7 @@ Markdown.dialects.Gruber = { for (var i=0; i < inline.length; i++) { var what = inline[i], is_str = typeof what == "string"; - if (is_str && add_to.length > 1 && typeof add_to[add_to.length-1] == "string" ) - { + if (is_str && add_to.length > 1 && typeof add_to[add_to.length-1] == "string" ) { add_to[ add_to.length-1 ] += what; } else { @@ -456,7 +472,7 @@ Markdown.dialects.Gruber = { ret = []; while ( blocks.length > 0 ) { - if ( re( blocks[0] ) ) { + if ( re.exec( blocks[0] ) ) { var b = blocks.shift(), // Now remove that indent x = b.replace( replace, ""); @@ -493,7 +509,7 @@ Markdown.dialects.Gruber = { if ( !m ) return undefined; function make_list( m ) { - var list = bullet_list( m[2] ) + var list = bullet_list.exec( m[2] ) ? ["bulletlist"] : ["numberlist"]; @@ -506,7 +522,8 @@ Markdown.dialects.Gruber = { list = make_list( m ), last_li, loose = false, - ret = [ stack[0].list ]; + ret = [ stack[0].list ], + i; // Loop to search over block looking for inner block elements and loose lists loose_search: @@ -522,7 +539,7 @@ Markdown.dialects.Gruber = { tight_search: for (var line_no=0; line_no < lines.length; line_no++) { var nl = "", - l = lines[line_no].replace(/^\n/, function(n) { nl = n; return "" }); + l = lines[line_no].replace(/^\n/, function(n) { nl = n; return ""; }); // TODO: really should cache this var line_re = regex_for_depth( stack.length ); @@ -555,13 +572,14 @@ Markdown.dialects.Gruber = { // where Md.pl goes nuts. If the indent matches a level in the // stack, put it there, else put it one deeper then the // wanted_depth deserves. - var found = stack.some(function(s, i) { - if ( s.indent != m[1] ) return false; - list = s.list; // Found the level we want - stack.splice(i+1); // Remove the others - //print("found"); - return true; // And stop looping - }); + var found = false; + for (i = 0; i < stack.length; i++) { + if ( stack[ i ].indent != m[1] ) continue; + list = stack[ i ].list; + stack.splice( i+1 ); + found = true; + break; + } if (!found) { //print("not found. l:", uneval(l)); @@ -606,7 +624,7 @@ Markdown.dialects.Gruber = { // Deal with code blocks or properly nested lists if (contained.length > 0) { // Make sure all listitems up the stack are paragraphs - stack.forEach( paragraphify, this ); + forEach( stack, paragraphify, this); last_li.push.apply( last_li, this.toTree( contained, [] ) ); } @@ -625,7 +643,7 @@ Markdown.dialects.Gruber = { } // Make sure all listitems up the stack are paragraphs - stack.forEach( paragraphify , this ); + forEach( stack, paragraphify, this); loose = true; continue loose_search; @@ -634,7 +652,7 @@ Markdown.dialects.Gruber = { } // loose_search return ret; - } + }; })(), blockquote: function blockquote( block, next ) { @@ -661,7 +679,7 @@ Markdown.dialects.Gruber = { // if the next block is also a blockquote merge it in while ( next.length && next[ 0 ][ 0 ] == ">" ) { var b = next.shift(); - block += block.trailing + b; + block = new String(block + block.trailing + b); block.trailing = b.trailing; } @@ -719,20 +737,44 @@ Markdown.dialects.Gruber = { return [ ["para"].concat( this.processInline( block ) ) ]; } } -} +}; Markdown.dialects.Gruber.inline = { - __call__: function inline( text, patterns ) { - // Hmmm - should this function be directly in Md#processInline, or - // conversely, should Md#processBlock be moved into block.__call__ too - var out = [ ], - m, - // Look for the next occurange of a special character/pattern - re = new RegExp( "([\\s\\S]*?)(" + (patterns.source || patterns) + ")", "g" ), + + __oneElement__: function oneElement( text, patterns_or_re, previous_nodes ) { + var m, + res, lastIndex = 0; - //D:var self = this; - //D:self.debug("processInline:", uneval(text) ); + patterns_or_re = patterns_or_re || this.dialect.inline.__patterns__; + var re = new RegExp( "([\\s\\S]*?)(" + (patterns_or_re.source || patterns_or_re) + ")" ); + + m = re.exec( text ); + if (!m) { + // Just boring text + return [ text.length, text ]; + } + else if ( m[1] ) { + // Some un-interesting text matched. Return that first + return [ m[1].length, m[1] ]; + } + + var res; + if ( m[2] in this.dialect.inline ) { + res = this.dialect.inline[ m[2] ].call( + this, + text.substr( m.index ), m, previous_nodes || [] ); + } + // Default for now to make dev easier. just slurp special and output it. + res = res || [ m[2].length, m[2] ]; + return res; + }, + + __call__: function inline( text, patterns ) { + + var out = [], + res; + function add(x) { //D:self.debug(" adding output", uneval(x)); if (typeof x == "string" && typeof out[out.length-1] == "string") @@ -741,36 +783,20 @@ Markdown.dialects.Gruber.inline = { out.push(x); } - while ( ( m = re.exec(text) ) != null) { - if ( m[1] ) add( m[1] ); // Some un-interesting text matched - else m[1] = { length: 0 }; // Or there was none, but make m[1].length == 0 - - var res; - if ( m[2] in this.dialect.inline ) { - res = this.dialect.inline[ m[2] ].call( - this, - text.substr( m.index + m[1].length ), m, out ); - } - // Default for now to make dev easier. just slurp special and output it. - res = res || [ m[2].length, m[2] ]; - - var len = res.shift(); - // Update how much input was consumed - re.lastIndex += ( len - m[2].length ); - - // Add children - res.forEach(add); - - lastIndex = re.lastIndex; + while ( text.length > 0 ) { + res = this.dialect.inline.__oneElement__.call(this, text, patterns, out ); + text = text.substr( res.shift() ); + forEach(res, add ) } - // Add last 'boring' chunk - if ( text.length > lastIndex ) - add( text.substr( lastIndex ) ); - return out; }, + // These characters are intersting elsewhere, so have rules for them so that + // chunks of plain text blocks don't include them + "]": function () {}, + "}": function () {}, + "\\": function escaped( text ) { // [ length of input processed, node/children to add... ] // Only esacape: \ ` * _ { } [ ] ( ) # * + - . ! @@ -782,6 +808,10 @@ Markdown.dialects.Gruber.inline = { }, "![": function image( text ) { + + // Unlike images, alt text is plain text only. no other elements are + // allowed in there + // ![Alt text](/path/to/img.jpg "Optional title") // 1 2 3 4 <--- captures var m = text.match( /^!\[(.*?)\][ \t]*\([ \t]*(\S*)(?:[ \t]+(["'])(.*?)\3)?[ \t]*\)/ ); @@ -790,7 +820,7 @@ Markdown.dialects.Gruber.inline = { if ( m[2] && m[2][0] == '<' && m[2][m[2].length-1] == '>' ) m[2] = m[2].substring( 1, m[2].length - 1 ); - m[2] == this.dialect.inline.__call__.call( this, m[2], /\\/ )[0]; + m[2] = this.dialect.inline.__call__.call( this, m[2], /\\/ )[0]; var attrs = { alt: m[1], href: m[2] || "" }; if ( m[4] !== undefined) @@ -805,7 +835,7 @@ Markdown.dialects.Gruber.inline = { if ( m ) { // We can't check if the reference is known here as it likely wont be // found till after. Check it in md tree->hmtl tree conversion - return [ m[0].length, [ "img_ref", { alt: m[1], ref: m[2].toLowerCase(), text: m[0] } ] ]; + return [ m[0].length, [ "img_ref", { alt: m[1], ref: m[2].toLowerCase(), original: m[0] } ] ]; } // Just consume the '![' @@ -813,47 +843,92 @@ Markdown.dialects.Gruber.inline = { }, "[": function link( text ) { - // [link text](/path/to/img.jpg "Optional title") - // 1 2 3 4 <--- captures - var m = text.match( /^\[([\s\S]*?)\][ \t]*\([ \t]*(\S+)(?:[ \t]+(["'])(.*?)\3)?[ \t]*\)/ ); + var orig = String(text); + // Inline content is possible inside `link text` + var res = Markdown.DialectHelpers.inline_until_char.call( this, text.substr(1), ']' ); + + // No closing ']' found. Just consume the [ + if ( !res ) return [ 1, '[' ]; + + var consumed = 1 + res[ 0 ], + children = res[ 1 ], + link, + attrs; + + // At this point the first [...] has been parsed. See what follows to find + // out which kind of link we are (reference or direct url) + text = text.substr( consumed ); + + // [link text](/path/to/img.jpg "Optional title") + // 1 2 3 <--- captures + // This will capture up to the last paren in the block. We then pull + // back based on if there a matching ones in the url + // ([here](/url/(test)) + // The parens have to be balanced + var m = text.match( /^\s*\([ \t]*(\S+)(?:[ \t]+(["'])(.*?)\2)?[ \t]*\)/ ); if ( m ) { - if ( m[2] && m[2][0] == '<' && m[2][m[2].length-1] == '>' ) - m[2] = m[2].substring( 1, m[2].length - 1 ); + var url = m[1]; + consumed += m[0].length; + + if ( url && url[0] == '<' && url[url.length-1] == '>' ) + url = url.substring( 1, url.length - 1 ); + + // If there is a title we don't have to worry about parens in the url + if ( !m[3] ) { + var open_parens = 1; // One open that isn't in the capture + for (var len = 0; len < url.length; len++) { + switch ( url[len] ) { + case '(': + open_parens++; + break; + case ')': + if ( --open_parens == 0) { + consumed -= url.length - len; + url = url.substring(0, len); + } + break; + } + } + } // Process escapes only - m[2] = this.dialect.inline.__call__.call( this, m[2], /\\/ )[0]; + url = this.dialect.inline.__call__.call( this, url, /\\/ )[0]; - var attrs = { href: m[2] || "" }; - if ( m[4] !== undefined) - attrs.title = m[4]; + attrs = { href: url || "" }; + if ( m[3] !== undefined) + attrs.title = m[3]; - return [ m[0].length, [ "link", attrs, m[1] ] ]; + link = [ "link", attrs ].concat( children ); + return [ consumed, link ]; } // [Alt text][id] // [Alt text] [id] - // [id] - m = text.match( /^\[([\s\S]*?)\](?: ?\[(.*?)\])?/ ); + m = text.match( /^\s*\[(.*?)\]/ ); if ( m ) { - // [id] case, text == id - if ( m[2] === undefined || m[2] === "" ) m[2] = m[1]; + + consumed += m[ 0 ].length; + + // [links][] uses links as its reference + attrs = { ref: ( m[ 1 ] || String(children) ).toLowerCase(), original: orig.substr( 0, consumed ) }; + + link = [ "link_ref", attrs ].concat( children ); // We can't check if the reference is known here as it likely wont be // found till after. Check it in md tree->hmtl tree conversion. // Store the original so that conversion can revert if the ref isn't found. - return [ - m[ 0 ].length, - [ - "link_ref", - { - ref: m[ 2 ].toLowerCase(), - original: m[ 0 ] - }, - m[ 1 ] - ] - ]; + return [ consumed, link ]; + } + + // [id] + // Only if id is plain (no formatting.) + if ( children.length == 1 && typeof children[0] == "string" ) { + + attrs = { ref: children[0].toLowerCase(), original: orig.substr( 0, consumed ) }; + link = [ "link_ref", attrs, children[0] ]; + return [ consumed, link ]; } // Just consume the '[' @@ -896,7 +971,7 @@ Markdown.dialects.Gruber.inline = { return [ 3, [ "linebreak" ] ]; } -} +}; // Meta Helper/generator method for em and strong handling function strong_em( tag, md ) { @@ -952,7 +1027,7 @@ function strong_em( tag, md ) { return [ md.length, md ]; } } - } // End returned function + }; // End returned function } Markdown.dialects.Gruber.inline["**"] = strong_em("strong", "**"); @@ -969,208 +1044,76 @@ Markdown.buildBlockOrder = function(d) { ord.push( i ); } d.__order__ = ord; -} +}; // Build patterns for inline matcher Markdown.buildInlinePatterns = function(d) { var patterns = []; for ( var i in d ) { - if (i == "__call__") continue; + // __foo__ is reserved and not a pattern + if ( i.match( /^__.*__$/) ) continue; var l = i.replace( /([\\.*+?|()\[\]{}])/g, "\\$1" ) .replace( /\n/, "\\n" ); patterns.push( i.length == 1 ? l : "(?:" + l + ")" ); } patterns = patterns.join("|"); + d.__patterns__ = patterns; //print("patterns:", uneval( patterns ) ); var fn = d.__call__; d.__call__ = function(text, pattern) { - if (pattern != undefined) + if (pattern != undefined) { return fn.call(this, text, pattern); + } else + { return fn.call(this, text, patterns); + } + }; +}; + +Markdown.DialectHelpers = {}; +Markdown.DialectHelpers.inline_until_char = function( text, want ) { + var consumed = 0, + nodes = []; + + while ( true ) { + if ( text[ consumed ] == want ) { + // Found the character we were looking for + consumed++; + return [ consumed, nodes ]; + } + + if ( consumed >= text.length ) { + // No closing char found. Abort. + return null; + } + + res = this.dialect.inline.__oneElement__.call(this, text.substr( consumed ) ); + consumed += res[ 0 ]; + // Add any returned nodes. + nodes.push.apply( nodes, res.slice( 1 ) ); } } // Helper function to make sub-classing a dialect easier Markdown.subclassDialect = function( d ) { - function Block() {}; + function Block() {} Block.prototype = d.block; - function Inline() {}; + function Inline() {} Inline.prototype = d.inline; return { block: new Block(), inline: new Inline() }; -} +}; Markdown.buildBlockOrder ( Markdown.dialects.Gruber.block ); Markdown.buildInlinePatterns( Markdown.dialects.Gruber.inline ); Markdown.dialects.Maruku = Markdown.subclassDialect( Markdown.dialects.Gruber ); -Markdown.dialects.Maruku.block.document_meta = function document_meta( block, next ) { - // we're only interested in the first block - if ( block.lineNumber > 1 ) return undefined; - - // document_meta blocks consist of one or more lines of `Key: Value\n` - if ( ! block.match( /^(?:\w+:.*\n)*\w+:.*$/ ) ) return undefined; - - // make an attribute node if it doesn't exist - if ( !extract_attr( this.tree ) ) { - this.tree.splice( 1, 0, {} ); - } - - var pairs = block.split( /\n/ ); - for ( p in pairs ) { - var m = pairs[ p ].match( /(\w+):\s*(.*)$/ ), - key = m[ 1 ].toLowerCase(), - value = m[ 2 ]; - - this.tree[ 1 ][ key ] = value; - } - - // document_meta produces no content! - return []; -} - -Markdown.dialects.Maruku.block.block_meta = function block_meta( block, next ) { - // check if the last line of the block is an meta hash - var m = block.match( /(^|\n) {0,3}\{:\s*((?:\\\}|[^\}])*)\s*\}$/ ); - if ( !m ) return undefined; - - // process the meta hash - var attr = process_meta_hash( m[ 2 ] ); - - // if we matched ^ then we need to apply meta to the previous block - if ( m[ 1 ] === "" ) { - var node = this.tree[ this.tree.length - 1 ], - hash = extract_attr( node ); - - // if the node is a string (rather than JsonML), bail - if ( typeof node === "string" ) return undefined; - - // create the attribute hash if it doesn't exist - if ( !hash ) { - hash = {}; - node.splice( 1, 0, hash ); - } - - // add the attributes in - for ( a in attr ) { - hash[ a ] = attr[ a ]; - } - - // return nothing so the meta hash is removed - return []; - } - - // pull the meta hash off the block and process what's left - var b = block.replace( /\n.*$/, "" ), - result = this.processBlock( b, [] ); - - // get or make the attributes hash - var hash = extract_attr( result[ 0 ] ); - if ( !hash ) { - hash = {}; - result[ 0 ].splice( 1, 0, hash ); - } - - // attach the attributes to the block - for ( a in attr ) { - hash[ a ] = attr[ a ]; - } - - return result; -} - -Markdown.dialects.Maruku.block.definition_list = function definition_list( block, next ) { - // one or more terms followed by one or more definitions, in a single block - var tight = /^((?:[^\s:].*\n)+):\s+([^]+)$/, - list = [ "dl" ]; - - // see if we're dealing with a tight or loose block - if ( ( m = block.match( tight ) ) ) { - // pull subsequent tight DL blocks out of `next` - var blocks = [ block ]; - while ( next.length && tight.exec( next[ 0 ] ) ) { - blocks.push( next.shift() ); - } - - for ( var b = 0; b < blocks.length; ++b ) { - var m = blocks[ b ].match( tight ), - terms = m[ 1 ].replace( /\n$/, "" ).split( /\n/ ), - defns = m[ 2 ].split( /\n:\s+/ ); - - // print( uneval( m ) ); - - for ( var i = 0; i < terms.length; ++i ) { - list.push( [ "dt", terms[ i ] ] ); - } - - for ( var i = 0; i < defns.length; ++i ) { - // run inline processing over the definition - list.push( [ "dd" ].concat( this.processInline( defns[ i ].replace( /(\n)\s+/, "$1" ) ) ) ); - } - } - } - else { - return undefined; - } - - return [ list ]; -} - -Markdown.dialects.Maruku.inline[ "{:" ] = function inline_meta( text, matches, out ) { - if ( !out.length ) { - return [ 2, "{:" ]; - } - - // get the preceeding element - var before = out[ out.length - 1 ]; - - if ( typeof before === "string" ) { - return [ 2, "{:" ]; - } - - // match a meta hash - var m = text.match( /^\{:\s*((?:\\\}|[^\}])*)\s*\}/ ); - - // no match, false alarm - if ( !m ) { - return [ 2, "{:" ]; - } - - // attach the attributes to the preceeding element - var meta = process_meta_hash( m[ 1 ] ), - attr = extract_attr( before ); - - if ( !attr ) { - attr = {}; - before.splice( 1, 0, attr ); - } - - for ( var k in meta ) { - attr[ k ] = meta[ k ]; - } - - // cut out the string and replace it with nothing - return [ m[ 0 ].length, "" ]; -} - -Markdown.buildBlockOrder ( Markdown.dialects.Maruku.block ); -Markdown.buildInlinePatterns( Markdown.dialects.Maruku.inline ); - -function extract_attr( jsonml ) { - return jsonml instanceof Array - && jsonml.length > 1 - && typeof jsonml[ 1 ] === "object" - && !( jsonml[ 1 ] instanceof Array ) - ? jsonml[ 1 ] - : undefined; -} - -function process_meta_hash( meta_string ) { +Markdown.dialects.Maruku.processMetaHash = function processMetaHash( meta_string ) { var meta = split_meta_hash( meta_string ), attr = {}; @@ -1190,8 +1133,8 @@ function process_meta_hash( meta_string ) { } } // attribute: foo=bar - else if ( /=/.test( meta[ i ] ) ) { - var s = meta[ i ].split( /=/ ); + else if ( /\=/.test( meta[ i ] ) ) { + var s = meta[ i ].split( /\=/ ); attr[ s[ 0 ] ] = s[ 1 ]; } } @@ -1235,6 +1178,191 @@ function split_meta_hash( meta_string ) { return parts; } +Markdown.dialects.Maruku.block.document_meta = function document_meta( block, next ) { + // we're only interested in the first block + if ( block.lineNumber > 1 ) return undefined; + + // document_meta blocks consist of one or more lines of `Key: Value\n` + if ( ! block.match( /^(?:\w+:.*\n)*\w+:.*$/ ) ) return undefined; + + // make an attribute node if it doesn't exist + if ( !extract_attr( this.tree ) ) { + this.tree.splice( 1, 0, {} ); + } + + var pairs = block.split( /\n/ ); + for ( p in pairs ) { + var m = pairs[ p ].match( /(\w+):\s*(.*)$/ ), + key = m[ 1 ].toLowerCase(), + value = m[ 2 ]; + + this.tree[ 1 ][ key ] = value; + } + + // document_meta produces no content! + return []; +}; + +Markdown.dialects.Maruku.block.block_meta = function block_meta( block, next ) { + // check if the last line of the block is an meta hash + var m = block.match( /(^|\n) {0,3}\{:\s*((?:\\\}|[^\}])*)\s*\}$/ ); + if ( !m ) return undefined; + + // process the meta hash + var attr = this.dialect.processMetaHash( m[ 2 ] ); + + var hash; + + // if we matched ^ then we need to apply meta to the previous block + if ( m[ 1 ] === "" ) { + var node = this.tree[ this.tree.length - 1 ]; + hash = extract_attr( node ); + + // if the node is a string (rather than JsonML), bail + if ( typeof node === "string" ) return undefined; + + // create the attribute hash if it doesn't exist + if ( !hash ) { + hash = {}; + node.splice( 1, 0, hash ); + } + + // add the attributes in + for ( a in attr ) { + hash[ a ] = attr[ a ]; + } + + // return nothing so the meta hash is removed + return []; + } + + // pull the meta hash off the block and process what's left + var b = block.replace( /\n.*$/, "" ), + result = this.processBlock( b, [] ); + + // get or make the attributes hash + hash = extract_attr( result[ 0 ] ); + if ( !hash ) { + hash = {}; + result[ 0 ].splice( 1, 0, hash ); + } + + // attach the attributes to the block + for ( a in attr ) { + hash[ a ] = attr[ a ]; + } + + return result; +}; + +Markdown.dialects.Maruku.block.definition_list = function definition_list( block, next ) { + // one or more terms followed by one or more definitions, in a single block + var tight = /^((?:[^\s:].*\n)+):\s+([\s\S]+)$/, + list = [ "dl" ], + i; + + // see if we're dealing with a tight or loose block + if ( ( m = block.match( tight ) ) ) { + // pull subsequent tight DL blocks out of `next` + var blocks = [ block ]; + while ( next.length && tight.exec( next[ 0 ] ) ) { + blocks.push( next.shift() ); + } + + for ( var b = 0; b < blocks.length; ++b ) { + var m = blocks[ b ].match( tight ), + terms = m[ 1 ].replace( /\n$/, "" ).split( /\n/ ), + defns = m[ 2 ].split( /\n:\s+/ ); + + // print( uneval( m ) ); + + for ( i = 0; i < terms.length; ++i ) { + list.push( [ "dt", terms[ i ] ] ); + } + + for ( i = 0; i < defns.length; ++i ) { + // run inline processing over the definition + list.push( [ "dd" ].concat( this.processInline( defns[ i ].replace( /(\n)\s+/, "$1" ) ) ) ); + } + } + } + else { + return undefined; + } + + return [ list ]; +}; + +Markdown.dialects.Maruku.inline[ "{:" ] = function inline_meta( text, matches, out ) { + if ( !out.length ) { + return [ 2, "{:" ]; + } + + // get the preceeding element + var before = out[ out.length - 1 ]; + + if ( typeof before === "string" ) { + return [ 2, "{:" ]; + } + + // match a meta hash + var m = text.match( /^\{:\s*((?:\\\}|[^\}])*)\s*\}/ ); + + // no match, false alarm + if ( !m ) { + return [ 2, "{:" ]; + } + + // attach the attributes to the preceeding element + var meta = this.dialect.processMetaHash( m[ 1 ] ), + attr = extract_attr( before ); + + if ( !attr ) { + attr = {}; + before.splice( 1, 0, attr ); + } + + for ( var k in meta ) { + attr[ k ] = meta[ k ]; + } + + // cut out the string and replace it with nothing + return [ m[ 0 ].length, "" ]; +}; + +Markdown.buildBlockOrder ( Markdown.dialects.Maruku.block ); +Markdown.buildInlinePatterns( Markdown.dialects.Maruku.inline ); + +var isArray = Array.isArray || function(obj) { + return Object.prototype.toString.call(obj) == '[object Array]'; +}; + +var forEach; +// Don't mess with Array.prototype. Its not friendly +if ( Array.prototype.forEach ) { + forEach = function( arr, cb, thisp ) { + return arr.forEach( cb, thisp ); + }; +} +else { + forEach = function(arr, cb, thisp) { + for (var i = 0; i < arr.length; i++) { + cb.call(thisp || arr, arr[i], i, arr); + } + } +} + +function extract_attr( jsonml ) { + return isArray(jsonml) + && jsonml.length > 1 + && typeof jsonml[ 1 ] === "object" + && !( isArray(jsonml[ 1 ]) ) + ? jsonml[ 1 ] + : undefined; +} + + + /** * renderJsonML( jsonml[, options] ) -> String * - jsonml (Array): JsonML array to render to XML @@ -1270,14 +1398,20 @@ expose.renderJsonML = function( jsonml, options ) { } return content.join( "\n\n" ); +}; + +function escapeHTML( text ) { + return text.replace( /&/g, "&" ) + .replace( //g, ">" ) + .replace( /"/g, """ ) + .replace( /'/g, "'" ); } function render_tree( jsonml ) { // basic case if ( typeof jsonml === "string" ) { - return jsonml.replace( /&/g, "&" ) - .replace( //g, ">" ); + return escapeHTML( jsonml ); } var tag = jsonml.shift(), @@ -1294,22 +1428,34 @@ function render_tree( jsonml ) { var tag_attrs = ""; for ( var a in attributes ) { - tag_attrs += " " + a + '="' + attributes[ a ] + '"'; + tag_attrs += " " + a + '="' + escapeHTML( attributes[ a ] ) + '"'; } // be careful about adding whitespace here for inline elements - return "<"+ tag + tag_attrs + ">" + content.join( "" ) + ""; + if ( tag == "img" || tag == "br" || tag == "hr" ) { + return "<"+ tag + tag_attrs + "/>"; + } + else { + return "<"+ tag + tag_attrs + ">" + content.join( "" ) + ""; + } } -function convert_tree_to_html( tree, references ) { +function convert_tree_to_html( tree, references, options ) { + var i; + options = options || {}; + // shallow clone var jsonml = tree.slice( 0 ); - // Clone attributes if the exist + if (typeof options.preprocessTreeNode === "function") { + jsonml = options.preprocessTreeNode(jsonml, references); + } + + // Clone attributes if they exist var attrs = extract_attr( jsonml ); if ( attrs ) { jsonml[ 1 ] = {}; - for ( var i in attrs ) { + for ( i in attrs ) { jsonml[ 1 ][ i ] = attrs[ i ]; } attrs = jsonml[ 1 ]; @@ -1344,7 +1490,7 @@ function convert_tree_to_html( tree, references ) { break; case "code_block": jsonml[ 0 ] = "pre"; - var i = attrs ? 2 : 1; + i = attrs ? 2 : 1; var code = [ "code" ]; code.push.apply( code, jsonml.splice( i ) ); jsonml[ i ] = code; @@ -1357,7 +1503,7 @@ function convert_tree_to_html( tree, references ) { delete jsonml[ 1 ].href; break; case "linebreak": - jsonml[0] = "br"; + jsonml[ 0 ] = "br"; break; case "link": jsonml[ 0 ] = "a"; @@ -1386,10 +1532,34 @@ function convert_tree_to_html( tree, references ) { return attrs.original; } break; + case "img_ref": + jsonml[ 0 ] = "img"; + + // grab this ref and clean up the attribute node + var ref = references[ attrs.ref ]; + + // if the reference exists, make the link + if ( ref ) { + delete attrs.ref; + + // add in the href and title, if present + attrs.src = ref.href; + if ( ref.title ) { + attrs.title = ref.title; + } + + // get rid of the unneeded original text + delete attrs.original; + } + // the reference doesn't exist, so revert to plain text + else { + return attrs.original; + } + break; } // convert all the children - var i = 1; + i = 1; // deal with the attribute node, if it exists if ( attrs ) { @@ -1404,7 +1574,7 @@ function convert_tree_to_html( tree, references ) { } for ( ; i < jsonml.length; ++i ) { - jsonml[ i ] = arguments.callee( jsonml[ i ], references ); + jsonml[ i ] = arguments.callee( jsonml[ i ], references, options ); } return jsonml; diff --git a/node_modules/markdown/package.json b/node_modules/markdown/package.json new file mode 100644 index 00000000..b30c6406 --- /dev/null +++ b/node_modules/markdown/package.json @@ -0,0 +1,63 @@ +{ + "name": "markdown", + "version": "0.4.0", + "description": "A sensible Markdown parser for javascript", + "keywords": [ + "markdown", + "text processing", + "ast" + ], + "maintainers": [ + { + "name": "Dominic Baggott", + "email": "dominic.baggott@gmail.com", + "url": "http://evilstreak.co.uk" + }, + { + "name": "Ash Berlin", + "email": "ash_markdownjs@firemirror.com", + "url": "http://ashberlin.com" + } + ], + "contributors": [ + { + "name": "Dominic Baggott", + "email": "dominic.baggott@gmail.com", + "url": "http://evilstreak.co.uk" + }, + { + "name": "Ash Berlin", + "email": "ash_markdownjs@firemirror.com", + "url": "http://ashberlin.com" + } + ], + "bugs": { + "url": "http://github.com/evilstreak/markdown-js/issues" + }, + "licenses": [ + { + "type": "MIT", + "url": "http://www.opensource.org/licenses/mit-license.php" + } + ], + "repository": { + "type": "git", + "url": "git://github.com/evilstreak/markdown-js.git" + }, + "main": "./lib/index.js", + "bin": { + "md2html": "./bin/md2html.js" + }, + "dependencies": { + "nopt": "1" + }, + "devDependencies": { + "tap": "0" + }, + "scripts": { + "test": "tap test/*.t.js" + }, + "readme": "markdown-js\n===========\n\nYet another markdown parser, this time for JavaScript. There's a few\noptions that precede this project but they all treat markdown to HTML\nconversion as a single step process. You pass markdown in and get HTML\nout, end of story. We had some pretty particular views on how the\nprocess should actually look, which include:\n\n * producing well-formed HTML. This means that em and strong nesting is\n important, as is the ability to output as both HTML and XHTML\n\n * having an intermediate representation to allow processing of parsed\n data (we in fact have two, both [JsonML]: a markdown tree and an\n HTML tree)\n\n * being easily extensible to add new dialects without having to\n rewrite the entire parsing mechanics\n\n * having a good test suite. The only test suites we could find tested\n massive blocks of input, and passing depended on outputting the HTML\n with exactly the same whitespace as the original implementation\n\n[JsonML]: http://jsonml.org/ \"JSON Markup Language\"\n\n## Installation\n\nJust the `markdown` library:\n\n npm install markdown\n\nAlso install `md2html` to `/usr/local/bin` (or wherever)\n\n npm install -g markdown\n\n## Usage\n\nThe simple way to use it with CommonJS is:\n\n var input = \"# Heading\\n\\nParagraph\";\n var output = require( \"markdown\" ).markdown.toHTML( input );\n print( output );\n\nIf you want more control check out the documentation in\n[lib/markdown.js] which details all the methods and parameters\navailable (including examples!). One day we'll get the docs generated\nand hosted somewhere for nicer browsing.\n\nWe're yet to try it out in a browser, though it's high up on our list of\nthings to sort out for this project.\n\n### md2html\n\n md2html /path/to/doc.md > /path/to/doc.html\n\n[lib/markdown.js]: http://github.com/evilstreak/markdown-js/blob/master/lib/markdown.js\n\n## Intermediate Representation\n\nInternally the process to convert a chunk of markdown into a chunk of\nHTML has three steps:\n\n 1. Parse the markdown into a JsonML tree. Any references found in the\n parsing are stored in the attribute hash of the root node under the\n key `references`.\n\n 2. Convert the markdown tree into an HTML tree. Rename any nodes that\n need it (`bulletlist` to `ul` for example) and lookup any references\n used by links or images. Remove the references attribute once done.\n\n 3. Stringify the HTML tree being careful not to wreck whitespace where\n whitespace is important (surrounding inline elements for example).\n\nEach step of this process can be called individually if you need to do\nsome processing or modification of the data at an intermediate stage.\nFor example, you may want to grab a list of all URLs linked to in the\ndocument before rendering it to HTML which you could do by recursing\nthrough the HTML tree looking for `a` nodes.\n\n## Running tests\n\nTo run the tests under node you will need tap installed (it's listed as a\ndevDependencies so `npm install` from the checkout should be enough), then do\n\n $ ./node_modules/.bin/tap test/*.t.js\n\n## Contributing\n\nDo the usual github fork and pull request dance. Add yourself to the\ncontributors section of package.json too if you want to.\n\n## License\n\nReleased under the MIT license.\n", + "_id": "markdown@0.4.0", + "_from": "markdown" +} diff --git a/package.json b/package.json index 5c2a2620..ccb17d08 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "JSDoc", "version": "3.0.0", - "revision": "1345064014682", + "revision": "1350231216174", "description": "An automatic documentation generator for javascript.", "keywords": [ "documentation", "javascript" ], "licenses": [ @@ -16,6 +16,9 @@ "url": "https://github.com/jsdoc3/jsdoc" } ], + "dependencies": { + "markdown": "0.4.0" + }, "bugs": "https://github.com/jsdoc3/jsdoc/issues", "contributors" : [ { diff --git a/plugins/markdown.js b/plugins/markdown.js index 20eab22f..1babf318 100644 --- a/plugins/markdown.js +++ b/plugins/markdown.js @@ -35,10 +35,10 @@ function getParser(parser, conf) { return parser.makeHtml(source); }; } else if (parser === "evilstreak") { - parser = require("evilstreak/markdown"); + parser = require("markdown").markdown; return function(source) { - return parser.renderJsonML(parser.toHTMLTree(source, conf.dialect)); + return parser.toHTML(source, conf.dialect); }; } else { throw "unknown Markdown parser: '" + parser + "'"; diff --git a/rhino_modules/jsdoc/readme.js b/rhino_modules/jsdoc/readme.js index aba292d9..c9d46adc 100644 --- a/rhino_modules/jsdoc/readme.js +++ b/rhino_modules/jsdoc/readme.js @@ -29,10 +29,10 @@ function getParser(parser, conf) { }; } else if (parser === 'evilstreak') { - parser = require('evilstreak/markdown'); + parser = require('markdown').markdown; return function(source) { - return parser.renderJsonML(parser.toHTMLTree(source, conf.dialect)); + return parser.toHTML(source, conf.dialect); }; } else { diff --git a/rhino_modules/jsdoc/tutorial.js b/rhino_modules/jsdoc/tutorial.js index d042ceeb..da0ba751 100644 --- a/rhino_modules/jsdoc/tutorial.js +++ b/rhino_modules/jsdoc/tutorial.js @@ -4,7 +4,7 @@ @license Apache License 2.0 - See file 'LICENSE.md' in this project. */ -var mdParser = require('evilstreak/markdown'); +var mdParser = require('markdown').markdown; /** @module jsdoc/tutorial