1 | var fs = require('fs');
|
2 | var path = require('path');
|
3 | var marked = require('marked');
|
4 | var cardinal = require('cardinal');
|
5 | var xtend = require('xtend');
|
6 | var color = require('./color');
|
7 | var table = require('text-table');
|
8 | var addHeader = require('table-header').add;
|
9 | var chalk = require('chalk');
|
10 | var wcstring = require('wcstring');
|
11 | var os = require('os');
|
12 | var entities = require('entities');
|
13 |
|
14 | var 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 |
|
52 | var tokens;
|
53 | var inline;
|
54 | var token;
|
55 | var blockDepth = 0;
|
56 | var ordinal;
|
57 | var orderedList = false;
|
58 |
|
59 | function 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 |
|
73 | if (cap = inline.rules.escape.exec(src)) {
|
74 | src = src.substring(cap[0].length);
|
75 | out += cap[1];
|
76 | continue;
|
77 | }
|
78 |
|
79 |
|
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 |
|
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 |
|
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 |
|
101 | if (cap = inline.rules.tag.exec(src)) {
|
102 | src = src.substring(cap[0].length);
|
103 | out += cap[0];
|
104 | continue;
|
105 | }
|
106 |
|
107 |
|
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 |
|
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 |
|
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 |
|
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 |
|
137 | if (cap = inline.rules.br.exec(src)) {
|
138 | src = src.substring(cap[0].length);
|
139 | out += '\n';
|
140 | continue;
|
141 | }
|
142 |
|
143 |
|
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 |
|
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 |
|
165 | function 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 |
|
306 | function next() {
|
307 | return token = tokens.shift();
|
308 | }
|
309 |
|
310 | function 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 |
|
325 | function 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 |
|
345 |
|
346 | function getStringWidth(str) {
|
347 | return wcstring(str).width();
|
348 | }
|
349 |
|
350 | exports.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 |
|
376 | exports.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 | }
|