index.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  1. #!/usr/bin/env node
  2. /**
  3. * marked tests
  4. * Copyright (c) 2011-2013, Christopher Jeffrey. (MIT Licensed)
  5. * https://github.com/chjj/marked
  6. */
  7. /**
  8. * Modules
  9. */
  10. var fs = require('fs')
  11. , path = require('path')
  12. , marked = require('../');
  13. /**
  14. * Load Tests
  15. */
  16. function load() {
  17. var dir = __dirname + '/tests'
  18. , files = {}
  19. , list
  20. , file
  21. , i
  22. , l;
  23. list = fs
  24. .readdirSync(dir)
  25. .filter(function(file) {
  26. return path.extname(file) !== '.html';
  27. })
  28. .sort(function(a, b) {
  29. a = path.basename(a).toLowerCase().charCodeAt(0);
  30. b = path.basename(b).toLowerCase().charCodeAt(0);
  31. return a > b ? 1 : (a < b ? -1 : 0);
  32. });
  33. i = 0;
  34. l = list.length;
  35. for (; i < l; i++) {
  36. file = path.join(dir, list[i]);
  37. files[path.basename(file)] = {
  38. text: fs.readFileSync(file, 'utf8'),
  39. html: fs.readFileSync(file.replace(/[^.]+$/, 'html'), 'utf8')
  40. };
  41. }
  42. return files;
  43. }
  44. /**
  45. * Test Runner
  46. */
  47. function runTests(engine, options) {
  48. if (typeof engine !== 'function') {
  49. options = engine;
  50. engine = null;
  51. }
  52. var engine = engine || marked
  53. , options = options || {}
  54. , files = options.files || load()
  55. , complete = 0
  56. , failed = 0
  57. , failures = []
  58. , keys = Object.keys(files)
  59. , i = 0
  60. , len = keys.length
  61. , filename
  62. , file
  63. , flags
  64. , text
  65. , html
  66. , j
  67. , l;
  68. if (options.marked) {
  69. marked.setOptions(options.marked);
  70. }
  71. main:
  72. for (; i < len; i++) {
  73. filename = keys[i];
  74. file = files[filename];
  75. if (marked._original) {
  76. marked.defaults = marked._original;
  77. delete marked._original;
  78. }
  79. flags = filename.split('.').slice(1, -1);
  80. if (flags.length) {
  81. marked._original = marked.defaults;
  82. marked.defaults = {};
  83. Object.keys(marked._original).forEach(function(key) {
  84. marked.defaults[key] = marked._original[key];
  85. });
  86. flags.forEach(function(key) {
  87. var val = true;
  88. if (key.indexOf('no') === 0) {
  89. key = key.substring(2);
  90. val = false;
  91. }
  92. if (marked.defaults.hasOwnProperty(key)) {
  93. marked.defaults[key] = val;
  94. }
  95. });
  96. }
  97. try {
  98. text = engine(file.text).replace(/\s/g, '');
  99. html = file.html.replace(/\s/g, '');
  100. } catch(e) {
  101. console.log('%s failed.', filename);
  102. throw e;
  103. }
  104. j = 0;
  105. l = html.length;
  106. for (; j < l; j++) {
  107. if (text[j] !== html[j]) {
  108. failed++;
  109. failures.push(filename);
  110. text = text.substring(
  111. Math.max(j - 30, 0),
  112. Math.min(j + 30, text.length));
  113. html = html.substring(
  114. Math.max(j - 30, 0),
  115. Math.min(j + 30, html.length));
  116. console.log(
  117. '\n#%d. %s failed at offset %d. Near: "%s".\n',
  118. i + 1, filename, j, text);
  119. console.log('\nGot:\n%s\n', text.trim() || text);
  120. console.log('\nExpected:\n%s\n', html.trim() || html);
  121. if (options.stop) {
  122. break main;
  123. }
  124. continue main;
  125. }
  126. }
  127. complete++;
  128. console.log('#%d. %s completed.', i + 1, filename);
  129. }
  130. console.log('%d/%d tests completed successfully.', complete, len);
  131. if (failed) console.log('%d/%d tests failed.', failed, len);
  132. // Tests currently failing.
  133. if (~failures.indexOf('def_blocks.text')) {
  134. failed -= 1;
  135. }
  136. return !failed;
  137. }
  138. /**
  139. * Benchmark a function
  140. */
  141. function bench(name, func) {
  142. var files = bench.files || load();
  143. if (!bench.files) {
  144. bench.files = files;
  145. // Change certain tests to allow
  146. // comparison to older benchmark times.
  147. fs.readdirSync(__dirname + '/new').forEach(function(name) {
  148. if (path.extname(name) === '.html') return;
  149. if (name === 'main.text') return;
  150. delete files[name];
  151. });
  152. files['backslash_escapes.text'] = {
  153. text: 'hello world \\[how](are you) today'
  154. };
  155. files['main.text'].text = files['main.text'].text.replace('* * *\n\n', '');
  156. }
  157. var start = Date.now()
  158. , times = 1000
  159. , keys = Object.keys(files)
  160. , i
  161. , l = keys.length
  162. , filename
  163. , file;
  164. while (times--) {
  165. for (i = 0; i < l; i++) {
  166. filename = keys[i];
  167. file = files[filename];
  168. func(file.text);
  169. }
  170. }
  171. console.log('%s completed in %dms.', name, Date.now() - start);
  172. }
  173. /**
  174. * Benchmark all engines
  175. */
  176. function runBench(options) {
  177. var options = options || {};
  178. // Non-GFM, Non-pedantic
  179. marked.setOptions({
  180. gfm: false,
  181. tables: false,
  182. breaks: false,
  183. pedantic: false,
  184. sanitize: false,
  185. smartLists: false
  186. });
  187. if (options.marked) {
  188. marked.setOptions(options.marked);
  189. }
  190. bench('marked', marked);
  191. // GFM
  192. marked.setOptions({
  193. gfm: true,
  194. tables: false,
  195. breaks: false,
  196. pedantic: false,
  197. sanitize: false,
  198. smartLists: false
  199. });
  200. if (options.marked) {
  201. marked.setOptions(options.marked);
  202. }
  203. bench('marked (gfm)', marked);
  204. // Pedantic
  205. marked.setOptions({
  206. gfm: false,
  207. tables: false,
  208. breaks: false,
  209. pedantic: true,
  210. sanitize: false,
  211. smartLists: false
  212. });
  213. if (options.marked) {
  214. marked.setOptions(options.marked);
  215. }
  216. bench('marked (pedantic)', marked);
  217. // robotskirt
  218. try {
  219. bench('robotskirt', (function() {
  220. var rs = require('robotskirt');
  221. return function(text) {
  222. var parser = rs.Markdown.std();
  223. return parser.render(text);
  224. };
  225. })());
  226. } catch (e) {
  227. console.log('Could not bench robotskirt.');
  228. }
  229. // showdown
  230. try {
  231. bench('showdown (reuse converter)', (function() {
  232. var Showdown = require('showdown');
  233. var convert = new Showdown.converter();
  234. return function(text) {
  235. return convert.makeHtml(text);
  236. };
  237. })());
  238. bench('showdown (new converter)', (function() {
  239. var Showdown = require('showdown');
  240. return function(text) {
  241. var convert = new Showdown.converter();
  242. return convert.makeHtml(text);
  243. };
  244. })());
  245. } catch (e) {
  246. console.log('Could not bench showdown.');
  247. }
  248. // markdown.js
  249. try {
  250. bench('markdown.js', require('markdown').parse);
  251. } catch (e) {
  252. console.log('Could not bench markdown.js.');
  253. }
  254. }
  255. /**
  256. * A simple one-time benchmark
  257. */
  258. function time(options) {
  259. var options = options || {};
  260. if (options.marked) {
  261. marked.setOptions(options.marked);
  262. }
  263. bench('marked', marked);
  264. }
  265. /**
  266. * Markdown Test Suite Fixer
  267. * This function is responsible for "fixing"
  268. * the markdown test suite. There are
  269. * certain aspects of the suite that
  270. * are strange or might make tests
  271. * fail for reasons unrelated to
  272. * conformance.
  273. */
  274. function fix(options) {
  275. ['tests', 'original', 'new'].forEach(function(dir) {
  276. try {
  277. fs.mkdirSync(path.resolve(__dirname, dir), 0755);
  278. } catch (e) {
  279. ;
  280. }
  281. });
  282. // rm -rf tests
  283. fs.readdirSync(path.resolve(__dirname, 'tests')).forEach(function(file) {
  284. fs.unlinkSync(path.resolve(__dirname, 'tests', file));
  285. });
  286. // cp -r original tests
  287. fs.readdirSync(path.resolve(__dirname, 'original')).forEach(function(file) {
  288. var nfile = file;
  289. if (file.indexOf('hard_wrapped_paragraphs_with_list_like_lines.') === 0) {
  290. nfile = file.replace(/\.(text|html)$/, '.nogfm.$1');
  291. }
  292. fs.writeFileSync(path.resolve(__dirname, 'tests', nfile),
  293. fs.readFileSync(path.resolve(__dirname, 'original', file)));
  294. });
  295. // node fix.js
  296. var dir = __dirname + '/tests';
  297. fs.readdirSync(dir).filter(function(file) {
  298. return path.extname(file) === '.html';
  299. }).forEach(function(file) {
  300. var file = path.join(dir, file)
  301. , html = fs.readFileSync(file, 'utf8');
  302. // fix unencoded quotes
  303. html = html
  304. .replace(/='([^\n']*)'(?=[^<>\n]*>)/g, '=&__APOS__;$1&__APOS__;')
  305. .replace(/="([^\n"]*)"(?=[^<>\n]*>)/g, '=&__QUOT__;$1&__QUOT__;')
  306. .replace(/"/g, '&quot;')
  307. .replace(/'/g, '&#39;')
  308. .replace(/&__QUOT__;/g, '"')
  309. .replace(/&__APOS__;/g, '\'');
  310. // add heading id's
  311. html = html.replace(/<(h[1-6])>([^<]+)<\/\1>/g, function(s, h, text) {
  312. var id = text
  313. .replace(/&#39;/g, '\'')
  314. .replace(/&quot;/g, '"')
  315. .replace(/&gt;/g, '>')
  316. .replace(/&lt;/g, '<')
  317. .replace(/&amp;/g, '&');
  318. id = id.toLowerCase().replace(/[^\w]+/g, '-');
  319. return '<' + h + ' id="' + id + '">' + text + '</' + h + '>';
  320. });
  321. fs.writeFileSync(file, html);
  322. });
  323. // turn <hr /> into <hr>
  324. fs.readdirSync(dir).forEach(function(file) {
  325. var file = path.join(dir, file)
  326. , text = fs.readFileSync(file, 'utf8');
  327. text = text.replace(/(<|&lt;)hr\s*\/(>|&gt;)/g, '$1hr$2');
  328. fs.writeFileSync(file, text);
  329. });
  330. // markdown does some strange things.
  331. // it does not encode naked `>`, marked does.
  332. (function() {
  333. var file = dir + '/amps_and_angles_encoding.html';
  334. var html = fs.readFileSync(file, 'utf8')
  335. .replace('6 > 5.', '6 &gt; 5.');
  336. fs.writeFileSync(file, html);
  337. })();
  338. // cp new/* tests/
  339. fs.readdirSync(path.resolve(__dirname, 'new')).forEach(function(file) {
  340. fs.writeFileSync(path.resolve(__dirname, 'tests', file),
  341. fs.readFileSync(path.resolve(__dirname, 'new', file)));
  342. });
  343. }
  344. /**
  345. * Argument Parsing
  346. */
  347. function parseArg(argv) {
  348. var argv = process.argv.slice(2)
  349. , options = {}
  350. , orphans = []
  351. , arg;
  352. function getarg() {
  353. var arg = argv.shift();
  354. if (arg.indexOf('--') === 0) {
  355. // e.g. --opt
  356. arg = arg.split('=');
  357. if (arg.length > 1) {
  358. // e.g. --opt=val
  359. argv.unshift(arg.slice(1).join('='));
  360. }
  361. arg = arg[0];
  362. } else if (arg[0] === '-') {
  363. if (arg.length > 2) {
  364. // e.g. -abc
  365. argv = arg.substring(1).split('').map(function(ch) {
  366. return '-' + ch;
  367. }).concat(argv);
  368. arg = argv.shift();
  369. } else {
  370. // e.g. -a
  371. }
  372. } else {
  373. // e.g. foo
  374. }
  375. return arg;
  376. }
  377. while (argv.length) {
  378. arg = getarg();
  379. switch (arg) {
  380. case '-f':
  381. case '--fix':
  382. case 'fix':
  383. options.fix = true;
  384. break;
  385. case '-b':
  386. case '--bench':
  387. options.bench = true;
  388. break;
  389. case '-s':
  390. case '--stop':
  391. options.stop = true;
  392. break;
  393. case '-t':
  394. case '--time':
  395. options.time = true;
  396. break;
  397. default:
  398. if (arg.indexOf('--') === 0) {
  399. opt = camelize(arg.replace(/^--(no-)?/, ''));
  400. if (!marked.defaults.hasOwnProperty(opt)) {
  401. continue;
  402. }
  403. options.marked = options.marked || {};
  404. if (arg.indexOf('--no-') === 0) {
  405. options.marked[opt] = typeof marked.defaults[opt] !== 'boolean'
  406. ? null
  407. : false;
  408. } else {
  409. options.marked[opt] = typeof marked.defaults[opt] !== 'boolean'
  410. ? argv.shift()
  411. : true;
  412. }
  413. } else {
  414. orphans.push(arg);
  415. }
  416. break;
  417. }
  418. }
  419. return options;
  420. }
  421. /**
  422. * Helpers
  423. */
  424. function camelize(text) {
  425. return text.replace(/(\w)-(\w)/g, function(_, a, b) {
  426. return a + b.toUpperCase();
  427. });
  428. }
  429. /**
  430. * Main
  431. */
  432. function main(argv) {
  433. var opt = parseArg();
  434. if (opt.fix) {
  435. return fix(opt);
  436. }
  437. if (opt.bench) {
  438. return runBench(opt);
  439. }
  440. if (opt.time) {
  441. return time(opt);
  442. }
  443. return runTests(opt);
  444. }
  445. /**
  446. * Execute
  447. */
  448. if (!module.parent) {
  449. process.title = 'marked';
  450. process.exit(main(process.argv.slice()) ? 0 : 1);
  451. } else {
  452. exports = main;
  453. exports.main = main;
  454. exports.runTests = runTests;
  455. exports.runBench = runBench;
  456. exports.load = load;
  457. exports.bench = bench;
  458. module.exports = exports;
  459. }