Comparing version 0.0.5 to 0.0.6
@@ -14,9 +14,9 @@ /** | ||
newline: /^\n+/, | ||
block: /^ {4,}[^\n]*(?:\n {4,}[^\n]*)*/, | ||
block: /^ {4,}[^\n]*(?:\n {4,}[^\n]*|\n)*(?=\n| *$)/, | ||
hr: /^( *[\-*_]){3,} *\n/, | ||
heading: /^ *(#{1,6}) *([^\n#]*) *#*/, | ||
lheading: /^([^\n]+)\n *(=|-){3,}/, | ||
hr: /^( ?[\-*_]){3,}/, | ||
blockquote: /^ *>[^\n]*(?:\n *>[^\n]*)*/, | ||
list: /^(?:( *)(\*|\+|-|\d+\.)[^\n]+(?:\n(?:\1 )+[^\n]+)*(?:\n+|$)){2,}/g, | ||
html: /^<([^\/\s>]+)[^\n>]*>[^\n]*(?:\n[^\n]+)*\n?<\/\1>/, | ||
list: /^( *)([*+-]|\d+\.) [^\0]+?(?:\n{2,}(?! )|\s*$)(?!\1\2|\1\d+\.)/, | ||
html: /^(?:<!--[^\0]*?-->|<(\w+)[^\0]+?<\/\1>|<[^<>\n]+>) *(?:\n{2,}|\s*$)/, | ||
text: /^[^\n]+/ | ||
@@ -50,9 +50,6 @@ }; | ||
// experimental | ||
//str = str.replace(/(^|\n) +(\n|$)/g, '$1$2'); | ||
// grab link definitons | ||
str = str.replace( | ||
/^ {0,3}\[([^\]]+)\]: *([^ ]+)(?: +"([^"]+)")? *(?:\n|$)/gm, | ||
function(_, id, href, title) { | ||
/^ {0,3}\[([^\]]+)\]: *([^ ]+)(?: +"([^\n]+)")? *$/gm, | ||
function(__, id, href, title) { | ||
links[id] = { | ||
@@ -72,2 +69,4 @@ href: href, | ||
block.token = function(str, tokens) { | ||
str = str.replace(/^ +$/gm, ''); | ||
var rules = block | ||
@@ -77,3 +76,4 @@ , keys = block.keys | ||
, key | ||
, cap; | ||
, cap | ||
, loose; | ||
@@ -131,11 +131,11 @@ var scan = function() { | ||
}); | ||
// get each top-level | ||
// item in the list | ||
loose = /\n *\n *(?:[*+-]|\d+\.)/.test(cap[0]); | ||
// get each top-level item | ||
cap = cap[0].match( | ||
/^( *)(\*|\+|-|\d+\.)[^\n]+(?:\n(?:\1 )+[^\n]+)*/gm | ||
); | ||
/^( *)([*+-]|\d+\.)[^\n]*(?:\n(?!\1(?:\2|\d+\.))[^\n]*)*/gm | ||
); | ||
each(cap, function(item) { | ||
// remove the list items sigil | ||
// so its seen as the next token | ||
item = item.replace(/^ *(\*|\+|-|\d+\.) */, ''); | ||
item = item.replace(/^ *([*+-]|\d+\.) */, ''); | ||
// outdent whatever the | ||
@@ -149,3 +149,5 @@ // list item contains, hacky | ||
tokens.push({ | ||
type: 'list_item_start' | ||
type: loose | ||
? 'loose_item_start' | ||
: 'list_item_start' | ||
}); | ||
@@ -189,10 +191,12 @@ block.token(item, tokens); | ||
var inline = { | ||
escape: /^\\([\\`*{}\[\]()#+\-.!])/, | ||
escape: /^\\([\\`*{}\[\]()#+\-.!_])/, | ||
autolink: /^<([^ >]+(@|:\/)[^ >]+)>/, | ||
tag: /^<[^\n>]+>/, | ||
link: /^!?\[([^\]]+)\]\(([^\)]+)\)/, | ||
reflink: /^!?\[([^\]]+)\]\[([^\]]+)\]/, | ||
strong: /^__([\s\S]+?)__|^\*\*([\s\S]+?)\*\*/, | ||
tag: /^<!--[^\0]*?-->|^<[^\n>]+>/, | ||
link: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]\s*\(([^\)]*)\)/, | ||
reflink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]\s*\[([^\]]*)\]/, | ||
nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/, | ||
strong: /^__([^\0]+?)__(?!_)|^\*\*([^\0]+?)\*\*(?!\*)/, | ||
em: /^_([^_]+)_|^\*([^*]+)\*/, | ||
code: /^`([^`]+)`|^``([\s\S]+?)``/ | ||
code: /^`([^`]+)`|^``([^\0]+?)``/, | ||
text: /^/ | ||
}; | ||
@@ -206,28 +210,29 @@ | ||
'reflink', | ||
'nolink', | ||
'strong', | ||
'em', | ||
'code' | ||
'code', | ||
'text' | ||
]; | ||
// hacky, but performant | ||
inline.text = (function(rules) { | ||
var keys = rules.keys | ||
, i = 0 | ||
, l = keys.length | ||
, body = []; | ||
inline.text = (function() { | ||
var body = []; | ||
for (; i < l; i++) { | ||
body.push(rules[keys[i]].source | ||
.replace(/(^|[^\[])\^/g, '$1')); | ||
} | ||
(function push(rule) { | ||
rule = inline[rule].source; | ||
body.push(rule.replace(/(^|[^\[])\^/g, '$1')); | ||
return push; | ||
}) | ||
('escape') | ||
('tag') | ||
('nolink') | ||
('strong') | ||
('em') | ||
('code'); | ||
keys.push('text'); | ||
return new | ||
RegExp('^[^\\0]+?(?=' + body.join('|') + '|$)'); | ||
})(); | ||
return new RegExp( | ||
'^([\\s\\S]+?)(?=' | ||
+ body.join('|') | ||
+ '|$)' | ||
); | ||
})(inline); | ||
/** | ||
@@ -271,13 +276,19 @@ * Inline Lexer | ||
case 'reflink': | ||
case 'nolink': | ||
if (key !== 'link') { | ||
link = (cap[2] || cap[1]).replace(/\s+/g, ' '); | ||
link = links[link]; | ||
if (!link) { | ||
out += cap[0][0]; | ||
str = cap[0].substring(1) + str; | ||
break; | ||
} | ||
} else { | ||
text = /^\s*<?([^\s]*?)>?(?:\s+"([^\n]+)")?\s*$/.exec(cap[2]); | ||
link = { | ||
href: text[1], | ||
title: text[2] | ||
}; | ||
} | ||
if (cap[0][0] !== '!') { | ||
if (key === 'reflink') { | ||
link = links[cap[2]]; | ||
if (!link) throw new | ||
Error('Undefined Reference: ' + cap[2]); | ||
} else { | ||
link = { | ||
href: cap[2], | ||
title: cap[3] | ||
}; | ||
} | ||
out += '<a href="' | ||
@@ -295,13 +306,2 @@ + escape(link.href) | ||
} else { | ||
if (key === 'reflink') { | ||
link = links[cap[2]]; | ||
if (!link) throw new | ||
Error('Undefined Reference: ' + cap[2]); | ||
} else { | ||
text = /^([^\s]+)\s*(.+)?$/.exec(cap[2]); | ||
link = { | ||
href: text[1], | ||
title: text[2] | ||
}; | ||
} | ||
out += '<img src="' | ||
@@ -348,3 +348,3 @@ + escape(link.href) | ||
case 'text': | ||
out += escape(cap[1]); | ||
out += escape(cap[0]); | ||
break; | ||
@@ -404,5 +404,3 @@ default: | ||
while (next().type !== 'list_item_end') { | ||
// TODO incorporate paragraph | ||
// list items here | ||
body.push(token.type === 'text' | ||
body.push(token.type === 'text' | ||
? inline.lexer(token.text) | ||
@@ -414,2 +412,10 @@ : tok()); | ||
+ '</li>'; | ||
case 'loose_item_start': | ||
var body = []; | ||
while (next().type !== 'list_item_end') { | ||
body.push(tok()); | ||
} | ||
return '<li>' | ||
+ body.join(' ') | ||
+ '</li>'; | ||
case 'html': | ||
@@ -416,0 +422,0 @@ return inline.lexer(token.text); |
@@ -5,3 +5,3 @@ { | ||
"author": "Christopher Jeffrey", | ||
"version": "0.0.5", | ||
"version": "0.0.6", | ||
"main": "./lib/marked.js", | ||
@@ -8,0 +8,0 @@ "bin": { "marked": "./bin/marked" }, |
@@ -9,3 +9,3 @@ # marked | ||
``` bash | ||
$ node test/bench | ||
$ node test --old_bench | ||
marked: 6260ms | ||
@@ -16,2 +16,4 @@ showdown: 21665ms | ||
(Above is the old benchmark, try `node test --bench` for the new ones.) | ||
The point of marked was to create a markdown compiler where it was possible to | ||
@@ -24,2 +26,6 @@ frequently parse huge chunks of markdown without having to worry about | ||
marked more or less passes the official markdown test suite in its | ||
entirety. This is important because a surprising number of markdown compilers | ||
cannot pass more than a few tests. | ||
## Install | ||
@@ -47,5 +53,2 @@ | ||
This parser was written in one night, so there's still a lot on the todo list. | ||
There may also be some bugs. | ||
- Implement GFM features. | ||
@@ -58,10 +61,2 @@ - Possibly add some | ||
be possible. | ||
- Find a better way of testing. Create a test suite from scratch because most | ||
markdown compilers don't appear to be working properly in every aspect (but | ||
it's hard to tell because the markdown "spec" is so vague). | ||
- Recognize and parse paragraph list items better. This may be the most | ||
important task. Currently, list items are parsed strictly, it's not possible | ||
to have "loose list items" as they're known. | ||
- Add an explicit pretty printing and minification feature. | ||
I've still just begun to write this. I expect I will be updating it frequently. |
@@ -0,22 +1,324 @@ | ||
#!/usr/bin/env node | ||
var fs = require('fs') | ||
, path = require('path') | ||
, marked = require('marked') | ||
, dir = __dirname + '/tests'; | ||
var breakOnError = true; | ||
var files; | ||
var load = function() { | ||
files = {}; | ||
var list = fs | ||
.readdirSync(dir) | ||
//.concat('/../main.md') | ||
.filter(function(file) { | ||
return path.extname(file) !== '.html'; | ||
}) | ||
.sort(function(a, b) { | ||
a = path.basename(a).toLowerCase().charCodeAt(0); | ||
b = path.basename(b).toLowerCase().charCodeAt(0); | ||
return a > b ? 1 : (a < b ? -1 : 0); | ||
}); | ||
var i = 0 | ||
, l = list.length | ||
, file; | ||
for (; i < l; i++) { | ||
file = path.join(dir, list[i]); | ||
files[path.basename(file)] = { | ||
text: fs.readFileSync(file, 'utf8'), | ||
html: fs.readFileSync(file.replace(/[^.]+$/, 'html'), 'utf8') | ||
}; | ||
} | ||
}; | ||
var main = function() { | ||
if (!files) load(); | ||
var complete = 0 | ||
, keys = Object.keys(files) | ||
, i_ = 0 | ||
, l_ = keys.length | ||
, filename | ||
, file | ||
, text | ||
, html; | ||
main: | ||
for (; i_ < l_; i_++) { | ||
filename = keys[i_]; | ||
file = files[filename]; | ||
// this was messing with | ||
// `node test | less` on sakura | ||
try { | ||
text = marked(file.text).replace(/\s/g, ''); | ||
html = file.html.replace(/\s/g, ''); | ||
} catch(e) { | ||
console.log('%s failed.', filename); | ||
throw e; | ||
} | ||
var i = 0 | ||
, l = html.length; | ||
for (; i < l; i++) { | ||
if (text[i] !== html[i]) { | ||
text = text.substring( | ||
Math.max(i - 30, 0), | ||
Math.min(i + 30, text.length)); | ||
html = html.substring( | ||
Math.max(i - 30, 0), | ||
Math.min(i + 30, html.length)); | ||
console.log( | ||
'\n#%d. %s failed at offset %d. Near: "%s".\n', | ||
i_ + 1, filename, i, text); | ||
console.log('\nGot:\n%s\n', | ||
pretty(text).trim() || text); | ||
console.log('\nExpected:\n%s\n', | ||
pretty(html).trim() || html); | ||
if (breakOnError) { | ||
break main; | ||
} else { | ||
break; | ||
} | ||
} | ||
} | ||
if (i === l) { | ||
complete++; | ||
console.log('#%d. %s completed.', i_ + 1, filename); | ||
} | ||
} | ||
console.log('%d/%d tests completed successfully.', complete, l_); | ||
}; | ||
main.bench = function(name, func) { | ||
if (!files) load(); | ||
var start = Date.now() | ||
, times = 1000 | ||
, keys = Object.keys(files) | ||
, i = 0 | ||
, l = keys.length | ||
, filename | ||
, file; | ||
while (times--) { | ||
for (i = 0; i < l; i++) { | ||
filename = keys[i]; | ||
file = files[filename]; | ||
func(file.text); | ||
} | ||
} | ||
console.log('%s completed in %dms.', name, Date.now() - start); | ||
}; | ||
var bench = function() { | ||
var marked = require('../'); | ||
main.bench('marked', marked); | ||
/** | ||
* There's two ways to benchmark showdown here. | ||
* The first way is to create a new converter | ||
* every time, this will renew any closured | ||
* variables. It is the "proper" way of using | ||
* showdown. However, for this benchmark, | ||
* I will use the completely improper method | ||
* which is must faster, just to be fair. | ||
*/ | ||
var showdown = (function() { | ||
var Showdown = require('showdown').Showdown; | ||
var convert = new Showdown.converter(); | ||
return function(text) { | ||
return convert.makeHtml(text); | ||
}; | ||
})(); | ||
main.bench('showdown', showdown); | ||
var markdownjs = require('markdown-js'); | ||
main.bench('markdown-js', function(text) { | ||
markdownjs.toHTML(text); | ||
}); | ||
}; | ||
var old_bench = function() { | ||
var text = fs.readFileSync(__dirname + '/main.md', 'utf8'); | ||
var benchmark = function(func, t) { | ||
var start = new Date() | ||
, i = t || 10000; | ||
while (i--) func(); | ||
console.log('%s: %sms', func.name, new Date() - start); | ||
}; | ||
var marked_ = require('../'); | ||
benchmark(function marked() { | ||
marked_(text); | ||
}); | ||
var showdown_ = (function() { | ||
var Showdown = require('showdown').Showdown; | ||
var convert = new Showdown.converter(); | ||
return function(str) { | ||
return convert.makeHtml(str); | ||
}; | ||
})(); | ||
benchmark(function showdown() { | ||
showdown_(text); | ||
}); | ||
var markdownjs_ = require('markdown-js'); | ||
benchmark(function markdownjs() { | ||
markdownjs_.toHTML(text); | ||
}); | ||
}; | ||
var old_test = function() { | ||
var assert = require('assert') | ||
, text = fs.readFileSync(__dirname + '/main.md', 'utf8'); | ||
var a = markdown(text) | ||
, b = fs.readFileSync(__dirname + '/main.html', 'utf8'); | ||
console.log(a); | ||
console.log('--------------------------------------------------------------'); | ||
console.log(b); | ||
console.log('--------------------------------------------------------------'); | ||
a = a.replace(/\s+/g, ''); | ||
b = b.replace(/\s+/g, ''); | ||
assert.ok(a === b, 'Failed.'); | ||
console.log('Complete.'); | ||
}; | ||
/** | ||
* Test | ||
* Pretty print HTML | ||
* Copyright (c) 2011, Christopher Jeffrey | ||
*/ | ||
var marked = require('../') | ||
, assert = require('assert') | ||
, fs = require('fs') | ||
, text = fs.readFileSync(__dirname + '/in.md', 'utf8'); | ||
var pretty = (function() { | ||
var indent = function(num) { | ||
return Array((num >= 0 ? num : 0) + 1).join(' '); | ||
}; | ||
var a = marked(text) | ||
, b = fs.readFileSync(__dirname + '/out.html', 'utf8'); | ||
var closing = { | ||
base: true, | ||
link: true, | ||
meta: true, | ||
hr: true, | ||
br: true, | ||
wbr: true, | ||
img: true, | ||
embed: true, | ||
param: true, | ||
source: true, | ||
track: true, | ||
area: true, | ||
col: true, | ||
input: true, | ||
keygen: true, | ||
command: true | ||
}; | ||
console.log(a); | ||
console.log('----------------------------------------------------------------'); | ||
console.log(b); | ||
console.log('----------------------------------------------------------------'); | ||
var remove = /<(pre|textarea|title|p|li|a)(?:\s[^>]+)?>[\s\S]+?<\/\1>/g | ||
, replace = /<!(\d+)%*\/>/g | ||
, wrap = /([ \t]*)<p>([\s\S]+?)<\/p>/g; | ||
a = a.replace(/\s+/g, ''); | ||
b = b.replace(/\s+/g, ''); | ||
return function(str) { | ||
var hash = [] | ||
, out = [] | ||
, cap | ||
, depth = 0 | ||
, text | ||
, full | ||
, tag | ||
, name; | ||
assert.ok(a === b, 'Failed.'); | ||
console.log('Complete.'); | ||
// temporarily remove elements before | ||
// processing, also remove whitespace | ||
str = str.replace(remove, function(element, name) { | ||
element = element | ||
.replace(/(<[^\/][^>]*>)\s+|\s+(<\/)/g, '$1$2') | ||
.replace(/[\r\n]/g, ''); | ||
return '<!' + (hash.push(element) - 1) | ||
+ (Array(element.length - 3).join('%')) + '/>'; | ||
}); | ||
// indent elements | ||
str = str | ||
.replace(/(>)\s+|\s+(<)/g, '$1$2') | ||
.replace(/[\r\n]/g, ''); | ||
while (cap = /^([\s\S]*?)(<([^>]+)>)/.exec(str)) { | ||
str = str.substring(cap[0].length); | ||
text = cap[1]; | ||
full = cap[2]; | ||
tag = cap[3]; | ||
name = tag.split(' ')[0]; | ||
if (text) { | ||
out.push(indent(depth) + text); | ||
} | ||
if (name[0] !== '/') { | ||
out.push(indent(depth) + full); | ||
if (!closing[name] | ||
&& name[0] !== '!' | ||
&& name[0] !== '?' | ||
&& tag[tag.length-1] !== '/') { | ||
depth++; | ||
} | ||
} else { | ||
depth--; | ||
out.push(indent(depth) + full); | ||
} | ||
} | ||
str = out.join('\n'); | ||
// restore the elements to | ||
// their original locations | ||
str = str.replace(replace, function($0, $1) { | ||
return hash[$1]; | ||
}); | ||
// wrap paragraphs | ||
str = str.replace(wrap, function($0, $1, $2) { | ||
var indent = $1 + ' ' | ||
, text = indent + $2; | ||
text = text | ||
.replace(/[\t\r\n]+/g, '') | ||
.replace(/(<\/[^>]+>|\/>)(?=\s*<\w)/g, '$1\n' + indent) | ||
.replace(/(.{75,}?\s+(?![^<]+>))/g, '$1\n' + indent) | ||
.replace(/([^<>\n]{50,}?)(<[^<]{15,}>)/g, '$1\n' + indent + '$2'); | ||
return $1 + '<p>\n' + text + '\n' + $1 + '</p>'; | ||
}); | ||
return str; | ||
}; | ||
})(); | ||
if (!module.parent) { | ||
if (~process.argv.indexOf('--bench')) { | ||
bench(); | ||
} else if (~process.argv.indexOf('--old_bench')) { | ||
old_bench(); | ||
} else if (~process.argv.indexOf('--old_test')) { | ||
old_test(); | ||
} else { | ||
main(); | ||
} | ||
} else { | ||
module.exports = main; | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
212743
102
809
3
58