-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
127 lines (103 loc) · 3.67 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
var spawn = require('child_process').spawn
, COMMAND = 'source-highlight';
/**
* For each code block with an info string call source-highlight(1) and
* rewrite the output nodes to include the highlighted response.
*
* @function highlight
*
* @param through module for subclassing streams.
* @param ast module for working with ast nodes.
* @param opts options passed to the `transform` function.
*
* @option {String} src source language, overrides info string.
* @option {String} out output format.
* @option {Object} alias map of info string languages to source languages.
* @option {Boolean} lines number lines in highlighted output.
* @option {Boolean} colons remove colons from line numbers.
* @option {Boolean} preserve Keep a `<code>` element in the result.
*/
function highlight(through, ast, opts) {
var Node = ast.Node;
opts.colons = opts.colons === undefined ? true : opts.colons
function transform(chunk, encoding, cb) {
var scope = this;
if(Node.is(chunk, Node.CODE_BLOCK) && (opts.src || chunk.info)) {
var src = opts.src || chunk.info.split(/\s+/)[0]
, literal = chunk.literal
, out = opts.out || 'html-css'
, args
, result = new Buffer(0);
// lookup source language aliases when available
if(!opts.src && opts.alias && opts.alias[src]) {
src = opts.alias[src];
}
args = [
'--src-lang',
src,
'--out-format',
out
];
// number source code lines
if(opts.lines) {
literal = literal.replace(/\n$/, '');
args.push('--line-number= ');
}
var ps = spawn(COMMAND, args);
// get response data from the process
ps.stdout.on('data', function(data) {
result = Buffer.concat(
[result, data], result.length + data.length);
})
ps.once('close', function(code) {
var doc
, next;
if(code === 0) {
// handle as html output
if(out === 'html' || out === 'html-css') {
doc = ast.parse('' + result);
// remove the generated comment
doc.firstChild.unlink();
// add chunks to the stream
next = doc.firstChild;
// strip obsolete tags, breaks HTML5 validation
next.literal = next.literal.replace(/<\/?tt>/g, '')
// rewrite the <pre> element to preserve an inner <code>
// element with a class attribute, this is in keeping with
// how a code block is rendered by the default HTML renderer
if(opts.preserve) {
next.literal = next.literal.replace(
/^<pre>/, '<pre class="source"><code class="language-' + src + '">');
next.literal = next.literal.replace(/<\/pre>/, '</code></pre>');
}
if(opts.lines && opts.colons) {
// remove colons from line numbers
next.literal = next.literal
.replace(
/(<span class="linenum">\s*[0-9]+):(<\/span>)/g, '$1$2')
}
// write the parsed HTML blocks to the stream
while(next) {
scope.push(Node.serialize(next));
next = next.next;
}
// preserve as a code block
}else{
chunk.literal = '' + result;
scope.push(Node.serialize(chunk));
}
cb();
}else{
cb(null, chunk);
}
})
// write the code block data to the process
ps.stdin.end(literal);
// pass through on non-zero exit code
}else{
cb(null, chunk);
}
}
return through.transform(transform);
}
module.exports = highlight;