Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support opening and closing delimiters #110

Merged
merged 1 commit into from
Mar 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/command.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ program
'pass options directly to the "node" binary')
.option (' --prefix <prefix>',
'specify Transcribe-style prefix (e.g. ".")')
.option (' --opening-delimiter <delimiter>',
'specify line preceding doctest block (e.g. "```javascript")')
.option (' --closing-delimiter <delimiter>',
'specify line following doctest block (e.g. "```")')
.option ('-p, --print',
'output the rewritten source without running tests')
.option ('-s, --silent',
Expand Down
166 changes: 98 additions & 68 deletions lib/doctest.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ module.exports = function(path, options) {

var source = toModule (
rewriters[options.type == null ? inferType (path) : options.type] (
options.prefix == null ? '' : options.prefix,
{prefix: options.prefix == null ? '' : options.prefix,
openingDelimiter: options.openingDelimiter,
closingDelimiter: options.closingDelimiter},
fs.readFileSync (path, 'utf8')
.replace (/\r\n?/g, '\n')
.replace (/^#!.*/, '')
Expand All @@ -75,13 +77,6 @@ function indentN(n, s) {
return s.replace (/^(?!$)/gm, (Array (n + 1)).join (' '));
}

// matchLine :: (String, String) -> Nullable (Array3 String String String)
function matchLine(prefix, s) {
return s.slice (0, prefix.length) === prefix ?
(s.slice (prefix.length)).match (/^\s*(>|[.]*)[ ]?(.*)$/) :
null;
}

// object :: Array (Array2 String Any) -> Object
function object(pairs) {
return pairs.reduce (function(object, pair) {
Expand All @@ -102,6 +97,20 @@ function show(x) {
_show (x);
}

// stripLeading :: (Number, String, String) -> String
//
// > stripLeading (1, '.', 'xxx')
// 'xxx'
// > stripLeading (1, '.', '...xxx...')
// '..xxx...'
// > stripLeading (Infinity, '.', '...xxx...')
// 'xxx...'
function stripLeading(n, c, s) {
var idx = 0;
while (idx < n && s.charAt (idx) === c) idx += 1;
return s.slice (idx);
}

// unlines :: Array String -> String
function unlines(lines) {
return lines.reduce (function(s, line) { return s + line + '\n'; }, '');
Expand Down Expand Up @@ -144,9 +153,10 @@ function toModule(source, moduleType) {
}
}

var CLOSED = 'closed';
var OPEN = 'open';
var INPUT = 'input';
var OUTPUT = 'output';
var DEFAULT = 'default';

// normalizeTest :: { output :: { value :: String } } -> Undefined
function normalizeTest($test) {
Expand All @@ -160,6 +170,44 @@ function normalizeTest($test) {
}
}

function processLine(
options, // :: { prefix :: String
// , openingDelimiter :: Nullable String
// , closingDelimiter :: Nullable String }
accum, // :: { state :: State, tests :: Array Test }
line, // :: String
input, // :: Test -> Undefined
output, // :: Test -> Undefined
appendToInput, // :: Test -> Undefined
appendToOutput // :: Test -> Undefined
) {
var $test, value;
var prefix = options.prefix;
if (line.slice (0, prefix.length) === prefix) {
var trimmedLine = (line.slice (prefix.length)).replace (/^\s*/, '');
if (accum.state === CLOSED) {
if (trimmedLine === options.openingDelimiter) accum.state = OPEN;
} else if (trimmedLine === options.closingDelimiter) {
accum.state = CLOSED;
} else if (trimmedLine.charAt (0) === '>') {
value = stripLeading (1, ' ', stripLeading (1, '>', trimmedLine));
accum.tests.push ($test = {});
$test[accum.state = INPUT] = {value: value};
input ($test);
} else if (trimmedLine.charAt (0) === '.') {
value = stripLeading (1, ' ', stripLeading (Infinity, '.', trimmedLine));
$test = accum.tests[accum.tests.length - 1];
$test[accum.state].value += '\n' + value;
(accum.state === INPUT ? appendToInput : appendToOutput) ($test);
} else if (accum.state === INPUT) {
value = trimmedLine;
$test = accum.tests[accum.tests.length - 1];
$test[accum.state = OUTPUT] = {value: value};
output ($test);
}
}
}

// Location = { start :: { line :: Integer, column :: Integer }
// , end :: { line :: Integer, column :: Integer } }

Expand All @@ -173,7 +221,7 @@ function normalizeTest($test) {
//
// Returns the doctests present in the given esprima comment objects.
//
// > transformComments ('', [{
// > transformComments ({prefix: ''}, [{
// . type: 'Line',
// . value: ' > 6 * 7',
// . loc: {start: {line: 1, column: 0}, end: {line: 1, column: 10}}
Expand All @@ -192,7 +240,7 @@ function normalizeTest($test) {
// . value: '42',
// . loc: {start: {line: 2, column: 0}, end: {line: 2, column: 5}}}
// . }]
function transformComments(prefix, comments) {
function transformComments(options, comments) {
var result = comments.reduce (function(accum, comment, commentIndex) {
return (comment.value.split ('\n')).reduce (function(accum, line, idx) {
var normalizedLine, start, end;
Expand All @@ -204,34 +252,27 @@ function transformComments(prefix, comments) {
start = comment.loc.start;
end = comment.loc.end;
}

var match = matchLine (prefix, normalizedLine);
if (match != null) {
var $1 = match[1];
var $2 = match[2];
if ($1 === '>') {
accum.state = INPUT;
accum.tests.push (object ([
['commentIndex', commentIndex],
[INPUT, {value: $2, loc: {start: start, end: end}}]
]));
} else if ($1 !== '' || accum.state === INPUT) {
var last = accum.tests[accum.tests.length - 1];
last.commentIndex = commentIndex;
if ($1 !== '') {
last[accum.state].value += '\n' + $2;
last[accum.state].loc.end = end;
} else {
accum.state = OUTPUT;
last[accum.state] = {value: $2, loc: {start: start, end: end}};
}
} else {
accum.state = DEFAULT;
processLine (
options,
accum,
normalizedLine,
function($test) {
$test[INPUT].loc = {start: start, end: end};
},
function($test) {
$test.commentIndex = commentIndex;
$test[OUTPUT].loc = {start: start, end: end};
},
function($test) {
$test[INPUT].loc.end = end;
},
function($test) {
$test[OUTPUT].loc.end = end;
}
}
);
return accum;
}, accum);
}, {state: DEFAULT, tests: []});
}, {state: options.openingDelimiter == null ? OPEN : CLOSED, tests: []});

var $tests = result.tests;
$tests.forEach (normalizeTest);
Expand Down Expand Up @@ -308,7 +349,7 @@ function wrap$coffee(test) {
]).join ('\n');
}

function rewrite$js(prefix, input) {
function rewrite$js(options, input) {
// 1. Locate block comments and line comments within the input text.
//
// 2. Create a list of comment chunks from the list of line comments
Expand Down Expand Up @@ -346,8 +387,8 @@ function rewrite$js(prefix, input) {
return comments;
}, {Block: [], Line: []});

var blockTests = transformComments (prefix, comments.Block);
var lineTests = transformComments (prefix, comments.Line);
var blockTests = transformComments (options, comments.Block);
var lineTests = transformComments (options, comments.Line);

var chunks = lineTests
.concat ([object ([[INPUT, bookend]])])
Expand Down Expand Up @@ -382,7 +423,7 @@ function rewrite$js(prefix, input) {
.join ('');
}

function rewrite$coffee(prefix, input) {
function rewrite$coffee(options, input) {
var lines = input.match (/^.*(?=\n)/gm);
var chunks = lines.reduce (function(accum, line, idx) {
var isComment = /^[ \t]*#(?!##)/.test (line);
Expand All @@ -402,35 +443,24 @@ function rewrite$coffee(prefix, input) {

var testChunks = chunks.commentChunks.map (function(commentChunk) {
var result = commentChunk.lines.reduce (function(accum, line, idx) {
var fullMatch = line.match (/^([ \t]*)#[ \t]*(.*)$/);
var indent = fullMatch[1];
var match = matchLine (prefix, fullMatch[2]);
if (match != null) {
var $1 = match[1];
var $2 = match[2];
if ($1 === '>') {
accum.state = INPUT;
accum.tests.push (object ([
['indent', indent],
[INPUT, {value: $2}]
]));
} else if ($1 !== '' || accum.state === INPUT) {
var last = accum.tests[accum.tests.length - 1];
if ($1 !== '') {
last[accum.state].value += '\n' + $2;
} else {
accum.state = OUTPUT;
last[accum.state] = {
value: $2,
loc: {start: {line: commentChunk.loc.start.line + idx}}
};
}
} else {
accum.state = DEFAULT;
}
}
var match = line.match (/^([ \t]*)#[ \t]*(.*)$/);
processLine (
options,
accum,
match[2],
function($test) {
$test.indent = match[1];
},
function($test) {
$test[OUTPUT].loc = {
start: {line: commentChunk.loc.start.line + idx}
};
},
function() {},
function() {}
);
return accum;
}, {state: DEFAULT, tests: []});
}, {state: options.openingDelimiter == null ? OPEN : CLOSED, tests: []});

return result.tests.map (function($test) {
normalizeTest ($test);
Expand Down
6 changes: 3 additions & 3 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ testModule ('test/line-endings/LF.coffee', {silent: true});
testModule ('test/exceptions/index.js', {silent: true});
testModule ('test/statements/index.js', {silent: true});
testModule ('test/fantasy-land/index.js', {silent: true});
testModule ('test/transcribe/index.js', {prefix: '.', silent: true});
testModule ('test/transcribe/index.coffee', {prefix: '.', silent: true});
testModule ('test/transcribe/index.js', {prefix: '.', openingDelimiter: '```javascript', closingDelimiter: '```', silent: true});
testModule ('test/transcribe/index.coffee', {prefix: '.', openingDelimiter: '```coffee', closingDelimiter: '```', silent: true});
testModule ('test/amd/index.js', {module: 'amd', silent: true});
testModule ('test/commonjs/require/index.js', {module: 'commonjs', silent: true});
testModule ('test/commonjs/exports/index.js', {module: 'commonjs', silent: true});
Expand Down Expand Up @@ -190,7 +190,7 @@ testCommand ('bin/doctest --module commonjs lib/doctest.js', {
status: 0,
stdout: unlines ([
'running doctests in lib/doctest.js...',
'...'
'......'
]),
stderr: ''
});
Expand Down
4 changes: 4 additions & 0 deletions test/transcribe/index.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
#. Transforms a list of elements of type `a` into a list of elements
#. of type `b` using the provided function of type `a -> b`.
#.
#. > This is a Markdown `<blockquote>` element. If the `--opening-delimiter`
#. > and `--closing-delimiter` options are set to <code>```coffee</code> and
#. > <code>```</code> respectively, these lines will not be evaluated.
#.
#. ```coffee
#. > map(Math.sqrt)([1, 4, 9])
#. [1, 2, 3]
Expand Down
4 changes: 4 additions & 0 deletions test/transcribe/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
//. Transforms a list of elements of type `a` into a list of elements
//. of type `b` using the provided function of type `a -> b`.
//.
//. > This is a Markdown `<blockquote>` element. If the `--opening-delimiter`
//. > and `--closing-delimiter` options are set to <code>```javascript</code>
//. > and <code>```</code> respectively, these lines will not be evaluated.
//.
//. ```javascript
//. > map(Math.sqrt)([1, 4, 9])
//. [1, 2, 3]
Expand Down
2 changes: 1 addition & 1 deletion test/transcribe/results.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
true,
"[1, 2, 3]",
"[1, 2, 3]",
8
12
]
]
]