Skip to content

Commit

Permalink
Merge pull request #110 from davidchambers/delimiters
Browse files Browse the repository at this point in the history
support opening and closing delimiters
  • Loading branch information
davidchambers authored Mar 9, 2019
2 parents 06a21e6 + b298ce9 commit 8b17fb2
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 72 deletions.
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
]
]
]

0 comments on commit 8b17fb2

Please sign in to comment.