UNPKG

10.4 kBJavaScriptView Raw
1var fs = require('fs');
2var path = require('path');
3var marked = require('marked');
4var cardinal = require('cardinal');
5var xtend = require('xtend');
6var color = require('./color');
7var table = require('text-table');
8var addHeader = require('table-header').add;
9var chalk = require('chalk');
10var wcstring = require('wcstring');
11var os = require('os');
12var entities = require('entities');
13
14var defaultOptions = {
15 collapseNewlines: true,
16 space: '',
17 hrStart: '',
18 hrChar: '-',
19 hrEnd: '',
20 headingStart: '\n',
21 headingEnd: '\n\n',
22 headingIndentChar: '#',
23 headingIndent: function (token) {
24 return Array(token.depth + 1).join(this.headingIndentChar)
25 },
26 codeStart: '\n',
27 codeEnd: '\n\n',
28 codePad: ' ',
29 codeTheme: os.platform() === 'win32' ? require('./syntaxColor_win') : require('./syntaxColor'),
30 blockquoteStart: '\n',
31 blockquoteEnd: '\n\n',
32 blockquoteColor: 'blockquote',
33 blockquotePad: ' > ',
34 blockquotePadColor: 'syntax',
35 listStart: '\n',
36 listEnd: '\n',
37 listItemStart: '',
38 listItemEnd: '\n',
39 listItemColor: 'ul',
40 listItemPad: {first: ' * ', regular: ' '},
41 listItemPadColor: 'syntax',
42 orderedListItemPadTemplate: ' x. ',
43 paragraphStart: '',
44 paragraphEnd: '\n\n',
45 width: process.stdout.columns || 80,
46 maxWidth: -1,
47 tableStart: '\n',
48 tableSeparator: ' ',
49 tableEnd: '\n\n'
50};
51
52var tokens;
53var inline;
54var token;
55var blockDepth = 0;
56var ordinal;
57var orderedList = false;
58
59function processInline(src, options) {
60 var out = '';
61 var cap;
62
63 function outLink (title, href) {
64 if (title) {
65 out += '[' + color(title, 'strong') + '](' + color(href, 'link') + ')';
66 } else {
67 out += '(' + color(href, 'link') + ')';
68 }
69 }
70
71 while (src) {
72 // escape
73 if (cap = inline.rules.escape.exec(src)) {
74 src = src.substring(cap[0].length);
75 out += cap[1];
76 continue;
77 }
78
79 // code
80 if (cap = inline.rules.code.exec(src)) {
81 src = src.substring(cap[0].length);
82 out += color(cap[2], 'code');
83 continue;
84 }
85
86 // autolink
87 if (cap = inline.rules.autolink.exec(src)) {
88 src = src.substring(cap[0].length);
89 out += color(cap[0], 'link');
90 continue;
91 }
92
93 // url (gfm)
94 if (cap = inline.rules.url.exec(src)) {
95 src = src.substring(cap[0].length);
96 outLink(null, cap[1]);
97 continue;
98 }
99
100 // tag
101 if (cap = inline.rules.tag.exec(src)) {
102 src = src.substring(cap[0].length);
103 out += cap[0];
104 continue;
105 }
106
107 // link
108 if (cap = inline.rules.link.exec(src)) {
109 src = src.substring(cap[0].length);
110 outLink(cap[1], cap[2]);
111 continue;
112 }
113
114 // reflink, nolink
115 if ((cap = inline.rules.reflink.exec(src))
116 || (cap = inline.rules.nolink.exec(src))) {
117 src = src.substring(cap[0].length);
118 out += cap[0];
119 continue;
120 }
121
122 // strong
123 if (cap = inline.rules.strong.exec(src)) {
124 src = src.substring(cap[0].length);
125 out += color(processInline(cap[2] || cap[1]), 'strong');
126 continue;
127 }
128
129 // em
130 if (cap = inline.rules.em.exec(src)) {
131 src = src.substring(cap[0].length);
132 out += color(processInline(cap[2] || cap[1]), 'em');
133 continue;
134 }
135
136 // br
137 if (cap = inline.rules.br.exec(src)) {
138 src = src.substring(cap[0].length);
139 out += '\n';
140 continue;
141 }
142
143 // del (gfm)
144 if (cap = inline.rules.del.exec(src)) {
145 src = src.substring(cap[0].length);
146 out += color(processInline(cap[1]), 'del');
147 continue;
148 }
149
150 // text
151 if (cap = inline.rules.text.exec(src)) {
152 src = src.substring(cap[0].length);
153 out += cap[0];
154 continue;
155 }
156
157 if (src) {
158 throw new Error('Infinite loop on byte: ' + src.charCodeAt(0));
159 }
160 }
161
162 return out;
163}
164
165function processToken(options) {
166 var type = token.type;
167 var text = token.text;
168 var content;
169
170 function getOption(key) {
171 var value = options[key];
172 return typeof value === 'function' ? value(token) : value;
173 }
174
175 if (text && type != 'code') {
176 text = entities.decodeHTML(text);
177 }
178
179 switch (type) {
180 case 'space': {
181 return options.space;
182 }
183 case 'hr': {
184 var hrStr = new Array(options.width).join(options.hrChar) + '\n';
185 return options.hrStart + color(hrStr, type) + options.hrEnd;
186 }
187 case 'heading': {
188 text = blockFormat(processInline(text), {
189 block_color: type,
190 pad: options.headingIndent(token) + ' ',
191 pad_color: 'syntax',
192 width: options.width
193 })
194
195 return options.headingStart + text + options.headingEnd;
196 }
197 case 'code': {
198 content = '';
199
200 if (token.lang === 'raw') {
201 return text + '\n';
202 }
203
204 try {
205 content = cardinal.highlight(text, {
206 theme: chalk.supportsColor
207 ? options.codeTheme
208 : require('cardinal/themes/empty')
209 });
210 }
211 catch (e) {
212 content = color(text, type);
213 }
214
215 content = content.replace(/^/gm, getOption('codePad'));
216
217 return options.codeStart + content + options.codeEnd;
218 }
219 case 'table': {
220 content = tableFormat(token, options);
221 return options.tableStart + content + options.tableEnd;
222 }
223 case 'blockquote_start': {
224 content = '';
225 blockDepth++;
226
227 while (next().type !== 'blockquote_end') {
228 content += processToken(options);
229 }
230 content = blockFormat(content, {
231 block_color: options.blockquoteColor,
232 pad: options.blockquotePad,
233 pad_color: options.blockquotePadColor,
234 width: options.width
235 });
236
237 blockDepth--;
238 return options.blockquoteStart + content + options.blockquoteEnd;
239 }
240 case 'list_start': {
241 content = '';
242 orderedList = token.ordered;
243 ordinal = 1;
244
245 while (next().type !== 'list_end') {
246 content += processToken(options);
247 }
248
249 return options.listStart + content + options.listEnd;
250 }
251 case 'loose_item_start':
252 case 'list_item_start': {
253 content = '';
254
255 while (next().type !== 'list_item_end') {
256 if (type === 'text') {
257 content += text;
258 } else {
259 content += processToken(options);
260 }
261 }
262
263 var pad = options.listItemPad;
264 if (orderedList) {
265 var first = options.orderedListItemPadTemplate.replace('x', String(ordinal));
266 var regular = ' '.repeat(first.length);
267 pad = { first: first, regular: regular };
268 ++ordinal;
269 }
270
271 content = blockFormat(
272 processInline(content),
273 {
274 block_color: options.listItemColor,
275 pad: pad,
276 pad_color: options.listItemPadColor,
277 width: options.width
278 }
279 );
280
281 return options.listItemStart + content + options.listItemEnd;
282 }
283 case 'paragraph': {
284 if (blockDepth > 0) {
285 return text;
286 }
287 text = blockFormat(
288 processInline(text),
289 {
290 block_color: type,
291 pad: options.paragraphPad,
292 pad_color: options.paragraphPadColor,
293 width: options.width
294 }
295 );
296 return options.paragraphStart + text + options.paragraphEnd;
297 }
298 default: {
299 if (text) {
300 return text;
301 }
302 }
303 }
304}
305
306function next() {
307 return token = tokens.shift();
308}
309
310function blockFormat(src, opts) {
311 opts = opts || {};
312
313 var retLines = [];
314
315 src = wcstring(src).wrap(opts.width, opts.pad, function (padStr) {
316 if (opts.pad_color) {
317 return color(padStr, opts.pad_color)
318 }
319 return padStr
320 });
321
322 return color(src, opts.block_color);
323}
324
325function tableFormat (token, options) {
326 var aligns = token.align.map(function (a) {
327 return (a===null) ? 'l' : a[0];
328 });
329 var rows = token.cells.map(function (row) {
330 return row.map(processInline);
331 });
332 var headers = token.header.map(function (s) {
333 return processInline('**'+s+'**');
334 });
335 addHeader(rows, headers, { stringLength: getStringWidth });
336 return table(rows, {
337 align: aligns,
338 stringLength: getStringWidth,
339 hsep: options.tableSeparator
340 });
341}
342
343/**
344 * Returns the number of columns required to display the given string.
345 */
346function getStringWidth(str) {
347 return wcstring(str).width();
348}
349
350exports.parse = function(text, options) {
351 tokens = marked.lexer(text);
352 inline = new marked.InlineLexer(tokens.links);
353 options = xtend(defaultOptions, options);
354
355 var outputArr = [];
356 var output;
357
358 if (options.maxWidth !== -1 && options.width > options.maxWidth) {
359 options.width = options.maxWidth
360 }
361
362 while (next()) {
363 outputArr.push(processToken(options));
364 }
365
366 if (options.collapseNewlines) {
367 output = outputArr.join('').replace(/\n\n\n/g, '\n\n');
368 }
369
370 tokens = null;
371 token = null;
372
373 return output;
374}
375
376exports.parseFile = function(file, options) {
377 var filePath = path.resolve(__dirname, file);
378 var ret = '';
379
380 try {
381 var text = fs.readFileSync(filePath).toString();
382 ret = exports.parse(text, options);
383 }
384 catch (e) {
385 throw e;
386 }
387
388 return ret;
389}