diff --git a/bower.json b/bower.json index 4d8f5c97..8bf44ea0 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "doctest", - "version": "0.6.1", + "version": "0.7.0", "main": "./lib/doctest", "ignore": [ "/Makefile", diff --git a/lib/doctest.js b/lib/doctest.js index 80cc0494..d9508b9d 100644 --- a/lib/doctest.js +++ b/lib/doctest.js @@ -12,23 +12,25 @@ */ (function() { - var CoffeeScript, commonjsEval, doctest, esprima, fetch, fs, functionEval, log, noop, pathlib, repr, rewrite, run, substring, toModule, transformComments, validators, _, + var CoffeeScript, R, commonjsEval, doctest, esprima, fetch, fs, functionEval, log, noop, pathlib, repr, rewrite, run, substring, toModule, transformComments, wrap, _, __slice = [].slice; doctest = function(path, options, callback) { - var type; + var type, validateOption, _ref; if (options == null) { options = {}; } if (callback == null) { callback = noop; } - _.each(_.keys(validators).sort(), function(key) { - if (!validators[key](options[key])) { - throw new Error("Invalid " + key + " `" + options[key] + "'"); + validateOption = function(name, validValues) { + if (name in options && !R.contains(options[name], validValues)) { + throw new Error("Invalid " + name + " `" + options[name] + "'"); } - }); - type = options.type || (function() { + }; + validateOption('module', ['amd', 'commonjs']); + validateOption('type', ['coffee', 'js']); + type = (_ref = options.type) != null ? _ref : (function() { var match; match = /[.](coffee|js)$/.exec(path); if (match === null) { @@ -56,10 +58,10 @@ }); }; - doctest.version = '0.6.1'; + doctest.version = '0.7.0'; if (typeof window !== 'undefined') { - _ = window._, CoffeeScript = window.CoffeeScript, esprima = window.esprima; + _ = window._, CoffeeScript = window.CoffeeScript, esprima = window.esprima, R = window.ramda; window.doctest = doctest; } else { fs = require('fs'); @@ -67,21 +69,16 @@ _ = require('underscore'); CoffeeScript = require('coffee-script'); esprima = require('esprima'); + R = require('ramda'); module.exports = doctest; } - validators = { - module: _.partial(_.contains, [void 0, 'amd', 'commonjs']), - silent: _.constant(true), - type: _.partial(_.contains, [void 0, 'coffee', 'js']) - }; - fetch = function(path, options, callback) { var silent, wrapper; silent = options.silent || options.print; wrapper = function(text) { var name; - name = _.last(path.split('/')); + name = R.last(R.split('/', path)); if (!silent) { console.log("running doctests in " + name + "..."); } @@ -115,71 +112,89 @@ } }; - transformComments = function(comments) { - return _.last(_.reduce(comments, function(_arg, comment, commentIndex) { - var accum, state; - state = _arg[0], accum = _arg[1]; - return _.reduce(comment.value.split('\n'), function(_arg1, line, idx) { - var accum, end, normalizedLine, prefix, start, state, value, _i, _ref, _ref1; - state = _arg1[0], accum = _arg1[1]; - switch (comment.type) { - case 'Block': - normalizedLine = line.replace(/^\s*[*]?\s*/, ''); - start = end = { - line: comment.loc.start.line + idx - }; - break; - case 'Line': - normalizedLine = line.replace(/^\s*/, ''); - _ref = comment.loc, start = _ref.start, end = _ref.end; - } - _ref1 = /^(>|[.]*)[ ]?(.*)$/.exec(normalizedLine), _i = _ref1.length - 2, prefix = _ref1[_i++], value = _ref1[_i++]; - if (prefix === '>') { + transformComments = R.pipe(R.reduce.idx(function(_arg, comment, commentIndex) { + var accum, state; + state = _arg[0], accum = _arg[1]; + return R.reduce.idx(function(_arg1, line, idx) { + var accum, end, normalizedLine, prefix, start, state, value, _i, _ref, _ref1; + state = _arg1[0], accum = _arg1[1]; + switch (comment.type) { + case 'Block': + normalizedLine = line.replace(/^\s*[*]?\s*/, ''); + start = end = { + line: comment.loc.start.line + idx + }; + break; + case 'Line': + normalizedLine = line.replace(/^\s*/, ''); + _ref = comment.loc, start = _ref.start, end = _ref.end; + } + _ref1 = /^(>|[.]*)[ ]?(.*)$/.exec(normalizedLine), _i = _ref1.length - 2, prefix = _ref1[_i++], value = _ref1[_i++]; + if (prefix === '>') { + return [ + 'input', accum.concat({ + commentIndex: commentIndex, + input: { + loc: { + start: start, + end: end + }, + value: value + } + }) + ]; + } else if (state === 'default') { + return ['default', accum]; + } else if (state === 'input') { + if (prefix) { return [ - 1, accum.concat({ + 'input', R.slice(0, -1, accum).concat({ commentIndex: commentIndex, input: { loc: { - start: start, + start: R.last(accum).input.loc.start, end: end }, - value: value + value: "" + (R.last(accum).input.value) + "\n" + value } }) ]; - } else if (state === 0) { - return [0, accum]; - } else if (prefix) { + } else { return [ - 1, _.initial(accum).concat({ + 'output', R.slice(0, -1, accum).concat({ commentIndex: commentIndex, - input: { + input: R.last(accum).input, + output: { loc: { - start: _.last(accum).input.loc.start, + start: start, end: end }, - value: "" + (_.last(accum).input.value) + "\n" + value + value: value } }) ]; - } else { + } + } else if (state === 'output') { + if (prefix) { return [ - 0, _.initial(accum).concat({ + 'output', R.slice(0, -1, accum).concat({ commentIndex: commentIndex, - input: _.last(accum).input, + input: R.last(accum).input, output: { loc: { - start: start, + start: R.last(accum).output.loc.start, end: end }, - value: value + value: "" + (R.last(accum).output.value) + "\n" + value } }) ]; + } else { + return ['default', accum]; } - }, [state, accum]); - }, [0, []])); - }; + } + }, [state, accum], comment.value.split('\n')); + }, ['default', []]), R.last); substring = function(input, start, end) { var combine; @@ -189,11 +204,11 @@ combine = function(a, b) { return ["" + a[0] + b[0], b[1]]; }; - return _.first(_.reduce(input.split(/^/m), function(accum, line, idx) { + return R.pipe(R.split(/^/m), R.reduce.idx(function(accum, line, idx) { var isEndLine, isStartLine; isStartLine = idx + 1 === start.line; isEndLine = idx + 1 === end.line; - return combine(accum, _.reduce(line, function(_arg, chr, column) { + return combine(accum, R.reduce.idx(function(_arg, chr, column) { var chrs, inComment; chrs = _arg[0], inComment = _arg[1]; if ((isStartLine && column === start.column) || inComment && !(isEndLine && column === end.column)) { @@ -201,23 +216,40 @@ } else { return ["" + chrs, false]; } - }, ['', _.last(accum)])); - }, ['', false])); + }, ['', R.last(accum)], line)); + }, ['', false]), R.first)(input); + }; + + wrap = function(type, test) { + return R.pipe(R.filter(function(dir) { + return Object.prototype.hasOwnProperty.call(test, dir); + }), R.map(function(dir) { + return wrap[type][dir](test); + }), R.join('\n'))(['input', 'output']); + }; + + wrap.js = R.lPartial(wrap, 'js'); + + wrap.js.input = function(test) { + return "__doctest.input(function() {\n return " + test.input.value + ";\n});"; + }; + + wrap.js.output = function(test) { + return "__doctest.output(" + test.output.loc.start.line + ", function() {\n return " + test.output.value + ";\n});"; + }; + + wrap.coffee = R.lPartial(wrap, 'coffee'); + + wrap.coffee.input = function(test) { + return "__doctest.input ->\n" + (test.input.value.replace(/^/gm, ' ')); + }; + + wrap.coffee.output = function(test) { + return "__doctest.output " + test.output.loc.start.line + ", ->\n" + (test.output.value.replace(/^/gm, ' ')); }; rewrite.js = function(input) { - var blockTests, bookend, getComments, lineTests, source, wrap, _ref; - wrap = function(test) { - return _.chain(['input', 'output']).filter(_.partial(_.has, test)).map(function(type) { - return wrap[type](test); - }).value().join('\n'); - }; - wrap.input = function(test) { - return "__doctest.input(function() {\n return " + test.input.value + ";\n});"; - }; - wrap.output = function(test) { - return "__doctest.output(" + test.output.loc.start.line + ", function() {\n return " + test.output.value + ";\n});"; - }; + var blockTests, bookend, getComments, lineTests, source, _ref; bookend = { value: '', loc: { @@ -227,18 +259,12 @@ } } }; - getComments = function(s) { - return esprima.parse(s, { - comment: true, - loc: true - }).comments; - }; - _ref = _.chain(getComments(input)).partition(function(c) { - return c.type === 'Block'; - }).map(transformComments).value(), blockTests = _ref[0], lineTests = _ref[1]; - source = _.chain(lineTests).concat({ - input: bookend - }).reduce(function(_arg, test) { + getComments = R.pipe(R.rPartial(esprima.parse, { + comment: true, + loc: true + }), R.prop('comments')); + _ref = R.pipe(getComments, R.partition(R.pipe(R.prop('type'), R.eq('Block'))), R.map(transformComments))(input), blockTests = _ref[0], lineTests = _ref[1]; + source = R.pipe(R.concat, R.reduce(function(_arg, test) { var chunks, start, _ref1; chunks = _arg[0], start = _arg[1]; return [__slice.call(chunks).concat([substring(input, start, test.input.loc.start)]), ((_ref1 = test.output) != null ? _ref1 : test.input).loc.end]; @@ -247,78 +273,91 @@ line: 1, column: 0 } - ]).first().zip(_.map(lineTests, wrap)).flatten().value().join(''); - return _.chain(getComments(source)).filter(function(c) { - return c.type === 'Block'; - }).concat(bookend).reduce(function(_arg, comment, idx) { + ]), R.first, R.rPartial(R.zip, R.concat(R.map(wrap.js, lineTests), [''])), R.flatten, R.join(''))(lineTests, [ + { + input: bookend + } + ]); + return R.pipe(getComments, R.filter(R.pipe(R.prop('type'), R.eq('Block'))), R.rPartial(R.concat, [bookend]), R.reduce.idx(function(_arg, comment, idx) { var chunks, s, start; chunks = _arg[0], start = _arg[1]; - s = _.chain(blockTests).filter(function(t) { - return t.commentIndex === idx; - }).map(wrap).value().join('\n'); + s = R.pipe(R.filter(R.pipe(R.prop('commentIndex'), R.eq(idx))), R.map(wrap.js), R.join('\n'))(blockTests); return [__slice.call(chunks).concat([substring(source, start, comment.loc.start)], [s]), comment.loc.end]; }, [ [], { line: 1, column: 0 } - ]).first().value().join(''); + ]), R.first, R.join(''))(source); }; rewrite.coffee = function(input) { - var source, wrap; - wrap = { - input: function(test) { - return "__doctest.input -> " + test.input.value; - }, - output: function(test) { - return "__doctest.output " + test.output.loc.start.line + ", -> " + test.output.value; + var commentChunks, literalChunks, testChunks, _ref; + _ref = R.pipe(R.match(/.*\n/gm), R.reduce.idx(function(_arg, line, idx) { + var commentChunks, current, inCommentChunk, isComment, literalChunks; + literalChunks = _arg[0], commentChunks = _arg[1], inCommentChunk = _arg[2]; + isComment = /^[ \t]*#(?!##)/.test(line); + current = isComment ? commentChunks : literalChunks; + if (isComment === inCommentChunk) { + current[current.length - 1].value += line; + } else { + current[current.length] = { + value: line, + loc: { + start: { + line: idx + 1 + } + } + }; } - }; - source = _.chain(input.split('\n')).reduce(function(_arg, line, idx) { - var expr, indent, lines, match, prefix, value, _i; - expr = _arg[0], lines = _arg[1]; - if (match = /^([ \t]*)#(?!##)[ \t]*(>|[.]*)(.*)$/.exec(line)) { - _i = match.length - 3, indent = match[_i++], prefix = match[_i++], value = match[_i++]; - if (prefix === '>' && expr) { - return [ - value, lines.concat("" + indent + (wrap.input({ - input: { - value: expr - } - }))) - ]; - } else if (prefix === '>') { - return [value, lines]; + return [literalChunks, commentChunks, isComment]; + }, [ + [ + { + value: '', + loc: { + start: { + line: 1 + } + } + } + ], [], false + ]))(input), literalChunks = _ref[0], commentChunks = _ref[1]; + testChunks = R.map(R.pipe(function(commentChunk) { + return R.reduce.idx(function(_arg, line, idx) { + var indent, loc, prefix, state, tests, value, _i, _ref1; + state = _arg[0], tests = _arg[1]; + _ref1 = /^([ \t]*)#[ \t]*(>|[.]*)(.*\n)/.exec(line), _i = _ref1.length - 3, indent = _ref1[_i++], prefix = _ref1[_i++], value = _ref1[_i++]; + if (prefix === '>') { + tests[tests.length] = { + indent: indent, + input: { + value: value + } + }; + return ['input', tests]; } else if (prefix) { - return ["" + expr + "\n" + indent + " " + value, lines]; - } else if (expr) { - return [ - '', lines.concat([ - "" + indent + (wrap.input({ - input: { - value: expr - } - })), "" + indent + (wrap.output({ - output: { - value: value, - loc: { - start: { - line: idx + 1 - } - } - } - })) - ]) - ]; + tests[tests.length - 1][state].value += value; + return [state, tests]; + } else if (state === 'input') { + loc = { + start: { + line: commentChunk.loc.start.line + idx + } + }; + tests[tests.length - 1].output = { + loc: loc, + value: value + }; + return ['output', tests]; } else { - return [expr, lines]; + return ['default', tests]; } - } else { - return [expr, lines.concat(line)]; - } - }, ['', []]).last().value().join('\n'); - return CoffeeScript.compile(source); + }, ['default', []], commentChunk.value.match(/.*\n/gm)); + }, R.last, R.map(function(test) { + return wrap.coffee(test).replace(/^/gm, test.indent); + }), R.join('\n')), commentChunks); + return R.pipe(R.zip, R.flatten, R.join('\n'), CoffeeScript.compile)(R.pluck('value', literalChunks), R.concat(testChunks, [''])); }; functionEval = function(source) { @@ -378,7 +417,7 @@ log = function(results) { var actual, expected, num, pass, _i, _len, _ref; - console.log(((function() { + console.log(R.join('', (function() { var _i, _len, _results; _results = []; for (_i = 0, _len = results.length; _i < _len; _i++) { @@ -386,7 +425,7 @@ _results.push(pass ? '.' : 'x'); } return _results; - })()).join('')); + })())); for (_i = 0, _len = results.length; _i < _len; _i++) { _ref = results[_i], pass = _ref[0], expected = _ref[1], actual = _ref[2], num = _ref[3]; if (!pass) { diff --git a/package.json b/package.json index d6c9a228..90fd3e2b 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,24 @@ { "name": "doctest", - "version": "0.6.1", + "version": "0.7.0", "description": "Doctests for JavaScript and CoffeeScript", "contributors": [ "David Chambers ", "Francesco Occhipinti " ], - "keywords": ["doctests", "test", "browser"], + "keywords": [ + "doctests", + "test", + "browser" + ], "main": "./lib/doctest", "bin": "./bin/doctest", - "licenses": [{ - "type": "WTFPL", - "url": "https://raw.github.com/davidchambers/doctest/master/LICENSE" - }], + "licenses": [ + { + "type": "WTFPL", + "url": "https://raw.github.com/davidchambers/doctest/master/LICENSE" + } + ], "repository": { "type": "git", "url": "git://github.com/davidchambers/doctest.git" diff --git a/src/doctest.coffee b/src/doctest.coffee index 81eb1638..4a75c692 100644 --- a/src/doctest.coffee +++ b/src/doctest.coffee @@ -40,7 +40,7 @@ doctest = (path, options = {}, callback = noop) -> callback results results -doctest.version = '0.6.1' +doctest.version = '0.7.0' if typeof window isnt 'undefined'