dialect_test[ "test_" + feature ] = function() { var test_path = path + feature + "/"; // grab all the test files in this feature var tests = fs.list( test_path ); // filter to only the raw files tests = tests.filter( function( x ) {return x.match( /\.text$/ ) } ); // remove the extensions tests = tests.map( function( x ) {return x.replace( /\.text$/, "" ) } ); for ( var t in tests ) { // load the raw text var test_name = tests[ t ].substring( tests[ t ].lastIndexOf( "/" ) + 1 ), text = slurpFile( test_path + tests[ t ] + ".text" ); // load the target output if ( fs.isFile( test_path + tests[ t ] + ".json" ) ) { try { var json_text = slurpFile( test_path + tests[ t ] + ".json" ); var json = JSON.parse( json_text ); var output = markdown.toHTMLTree( text, dialect ); asserts.same( output, json, test_name ); } catch( e ) { asserts.ok( 0, "Failed with error on " + test_name + ": " + e ); if ( e.stack ) asserts.diag( e.stack ); } } else { asserts.ok( 0, "No target output for " + test_name ); } } }
exports.Ronn = function(text, version, manual, date) { if (!manual) manual = ""; if (!version) version = ""; if (!date) date = new Date(); else date = new Date(date + " GMT"); var gMD = md.parse(text); prepareTree(gMD); var gHtml = md.toHTMLTree(gMD); this.roff = function() { return blockFilter("", gHtml, {parent:null, previous:null, position:null}); }; this.html = function() { return toHTML(gHtml); }; this.fragment = function() { return toHTMLfragment(gHtml); }; function blockFilter(out, node, context) { if (typeof node == "string") { if (!node.match(/^\s*$/m)) sys.debug("unexpected text: " + node); return out; } var tag = node.shift(); var attributes = null; if (node.length && typeof node[0] === "object" && !(node[0] instanceof Array)) { attributes = node.shift(); } var fParent = context.parent; var fPrevious = context.previous; context.previous = null; context.parent = tag; switch (tag) { case "html": out = comment(out, "Generated with Ronnjs 0.3.8"); out = comment(out, "http://github.com/kapouer/ronnjs/"); while (node.length) out = blockFilter(out, node.shift(), context); break; case "h1": var fTagline = node.shift(); var fMatch = /([\w_.\[\]~+=@:-]+)\s*\((\d\w*)\)\s*-+\s*(.*)/.exec(fTagline); var fName, fSection; if (fMatch != null) { fName = fMatch[1]; fSection = fMatch[2]; fTagline = fMatch[3]; } else { fMatch = /([\w_.\[\]~+=@:-]+)\s+-+\s+(.*)/.exec(fTagline); if (fMatch != null) { fName = fMatch[1]; fTagline = fMatch[2]; } } if (fMatch == null) { fName = ""; fSection = ""; fTagline = ""; } if (fName.length == 0) break; out = macro(out, "TH", [ quote(esc(fName.toUpperCase())) , quote(fSection) , quote(manDate(date)) , quote(version) , quote(manual) ]); out = macro(out, "SH", quote("NAME")); out += "\\fB" + fName + "\\fR"; if (fTagline.length > 0) out += " \\-\\- " + esc(fTagline); break; case "h2": out = macro(out, "SH", quote(esc(toHTML(node.shift())))); break; case "h3": out = macro(out, "SS", quote(esc(toHTML(node.shift())))); break; case "h4": out = macro(out, "SS", quote("\\fI" + esc(toHTML(node.shift())) + "\\fR")); break; case "hr": out = macro(out, "HR"); break; case "p": if (fPrevious && fParent && (fParent == "dd" || fParent == "li")) out = macro(out, "IP"); else if (fPrevious && !(fPrevious == "h1" || fPrevious == "h2" || fPrevious == "h3" || fPrevious == "h4")) out = macro(out, "P"); out = callInlineChildren(out, node, context); break; case "pre": var indent = (fPrevious == null || !(fPrevious == "h1" || fPrevious == "h2" || fPrevious == "h3" || fPrevious == "h4")); if (indent) out = macro(out, "IP", [quote(""), 4]); out = macro(out, "nf"); out = callInlineChildren(out, node, context); out = macro(out, "fi"); if (indent) out = macro(out, "IP", [quote(""), 0]); break; case "dl": out = macro(out, "TP"); while (node.length) out = blockFilter(out, node.shift(), context); break; case "dt": if (fPrevious != null) out = macro(out, "TP"); out = callInlineChildren(out, node, context); out += "\n"; break; case "dd": if (containsTag(node, {'p':true})) { while (node.length) out = blockFilter(out, node.shift(), context); } else { out = callInlineChildren(out, node, context); } out += "\n"; break; case "ol": case "ul": context.position = 0; while (node.length) out = blockFilter(out, node.shift(), context); context.position = null; out = macro(out, "IP", [quote(""), 0]); break; case "li": if (fParent == "ol") { context.position += 1; out = macro(out, "IP", [quote(context.position), 4]); } else if (fParent == "ul") { out = macro(out, "IP", [quote("\\(bu"), 4]); } if (containsTag(node, {"p":true, "ol":true, "ul":true, "dl":true, "div":true})) { while (node.length) out = blockFilter(out, node.shift(), context); } else { out = callInlineChildren(out, node, context); } out += "\n"; break; case "span": case "code": case "b": case "strong": case "kbd": case "samp": case "var": case "em": case "i": case "u": case "br": case "a": if (attributes != null) node.unshift(attributes); node.unshift(tag); out = inlineFilter(out, node, context); break; default: sys.debug("unrecognized block tag: " + tag); break; } context.parent = fParent; context.previous = tag; return out; } function callInlineChildren(out, node, context) { while (node.length) { var lChild = node.shift(); if (node.length > 0) context.hasNext = true; else context.hasNext = false; out = inlineFilter(out, lChild, context); } return out; } function inlineFilter(out, node, context) { if (typeof node == "string") { if (context.previous) { if (context.previous == "br") node = node.replace(/^\n+/gm, ''); else if (context.previous == "dt" || context.previous == "dd") { node = node.trim(); } } if (context.parent == "pre") { // do nothing } else if (context.previous == null && !context.hasNext) { node = node.replace(/\n+$/gm, ''); } else { node = node.replace(/\n+$/gm, ' '); } out += esc(node); return out; } var tag = node.shift(); var attributes = null; if (node.length && typeof node[0] === "object" && !(node[0] instanceof Array)) { attributes = node.shift(); } var fParent = context.parent; var fPrevious = context.previous; context.parent = tag; context.previous = null; switch(tag) { case "code": if (fParent == "pre") { out = callInlineChildren(out, node, context); } else { out += '\\fB'; out = callInlineChildren(out, node, context); out += '\\fR'; } break; case "b": case "strong": case "kbd": case "samp": out += '\\fB'; out = callInlineChildren(out, node, context); out += '\\fR'; break; case "var": case "em": case "i": case "u": out += '\\fI'; out = callInlineChildren(out, node, context); out += '\\fR'; break; case "br": out = macro(out, "br"); break; case "a": var fStr = node[0]; var fHref = attributes['href']; if (fHref == fStr || (fHref.length > 0 && fHref[0] == '#') || decodeURI(fHref) == "mailto:" + decodeURI(fStr)) { out += '\\fI'; out = callInlineChildren(out, node, context); out += '\\fR'; } else { out = callInlineChildren(out, node, context); out += " "; out += '\\fI'; out += esc(fHref); out += '\\fR'; } break; default: sys.debug("unrecognized inline tag: " + tag); break; } context.parent = fParent; context.previous = tag; return out; } function containsTag(node, tags) { // browse ml tree searching for tags (hash {tag : true, ...}) if (typeof node == "string") return false; var jml = node.slice(0); if (jml.length == 0) return false; else while (jml.length && jml[0] instanceof Array) { if (containsTag(jml.shift(), tags)) return true; } var tag = jml.shift(); if (tags[tag] === true) return true; if (jml.length && typeof jml[0] === "object" && !(jml[0] instanceof Array)) { // skip attributes jml.shift(); } // children if (jml.length) { if (containsTag(jml.shift(), tags)) return true; } // siblings if (jml.length) return containsTag(jml, tags); } function toHTML(node) { // TODO : check double-escapes of & by & return md.renderJsonML(node, {root:true, xhtml:true}); } function toHTMLfragment(node) { return md.renderJsonML(node, {root:false, xhtml:true}); } function comment(out, str) { return writeln(out, '.\\" ' + str); } function quote(str) { return '"' + str + '"'; } function esc(str) { // TODO : MARKDOWN CONVERTS ONLY &, <, > // so there are no entities to convert, maybe chars if output is not UTF8 ? // HTML_ROFF_ENTITIES = { // • '•' => '\(bu', // < '<' => '<', // > '>' => '>', // ici un nbsp : ' ', ' ' => '\~', // © '©' => '\(co', // ” '”' => '\(rs', // — '—' => '\(em', // ® '®' => '\(rg', // INCONNU '&sec;' => '\(sc', // ≥ '≥' => '\(>=', // ≤ '≤' => '\(<=', // ≠ '≠' => '\(!=', // ≡ '≡' => '\(==' // } // text.gsub!(/&#x([0-9A-Fa-f]+);/) { $1.to_i(16).chr } # hex entities // text.gsub!(/&#(\d+);/) { $1.to_i.chr } # dec entities // text.gsub!('\\', '\e') # backslash // text.gsub!(/['".-]/) { |m| "\\#{m}" } # control chars // text.gsub!(/(&[A-Za-z]+;)/) { ent[$1] || $1 } # named entities // text.gsub!('&', '&') # amps return str .replace(/\\/gm, "\\\\") .replace(/-/gm, "\\-") .replace(/^\./gm, "\\|.") .replace(/\./gm, "\\.") .replace(/'/gm, "\\'") ; } function writeln(out, str) { if (out.length && out[out.length - 1] != "\n") out += "\n"; out += str + "\n"; return out; } function macro(out, name, list) { var fText = ".\n." + name; if (list != null) { if (typeof list == "string") { fText += ' ' + list; } else { for (var i=0, len=list.length; i < len; i++) { var item = list[i]; if (item == null) continue; fText += ' ' + item; } } } return writeln(out, fText); } function manDate(pDate) { var fMonth = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"][pDate.getMonth()]; return fMonth + " " + pDate.getFullYear(); } };