UNPKG

8.98 kBJavaScriptView Raw
1const Lexer = require('./Lexer.js');
2const Parser = require('./Parser.js');
3const Tokenizer = require('./Tokenizer.js');
4const Renderer = require('./Renderer.js');
5const TextRenderer = require('./TextRenderer.js');
6const Slugger = require('./Slugger.js');
7const {
8 merge,
9 checkSanitizeDeprecation,
10 escape
11} = require('./helpers.js');
12const {
13 getDefaults,
14 changeDefaults,
15 defaults
16} = require('./defaults.js');
17
18/**
19 * Marked
20 */
21function marked(src, opt, callback) {
22 // throw error in case of non string input
23 if (typeof src === 'undefined' || src === null) {
24 throw new Error('marked(): input parameter is undefined or null');
25 }
26 if (typeof src !== 'string') {
27 throw new Error('marked(): input parameter is of type '
28 + Object.prototype.toString.call(src) + ', string expected');
29 }
30
31 if (typeof opt === 'function') {
32 callback = opt;
33 opt = null;
34 }
35
36 opt = merge({}, marked.defaults, opt || {});
37 checkSanitizeDeprecation(opt);
38
39 if (callback) {
40 const highlight = opt.highlight;
41 let tokens;
42
43 try {
44 tokens = Lexer.lex(src, opt);
45 } catch (e) {
46 return callback(e);
47 }
48
49 const done = function(err) {
50 let out;
51
52 if (!err) {
53 try {
54 if (opt.walkTokens) {
55 marked.walkTokens(tokens, opt.walkTokens);
56 }
57 out = Parser.parse(tokens, opt);
58 } catch (e) {
59 err = e;
60 }
61 }
62
63 opt.highlight = highlight;
64
65 return err
66 ? callback(err)
67 : callback(null, out);
68 };
69
70 if (!highlight || highlight.length < 3) {
71 return done();
72 }
73
74 delete opt.highlight;
75
76 if (!tokens.length) return done();
77
78 let pending = 0;
79 marked.walkTokens(tokens, function(token) {
80 if (token.type === 'code') {
81 pending++;
82 setTimeout(() => {
83 highlight(token.text, token.lang, function(err, code) {
84 if (err) {
85 return done(err);
86 }
87 if (code != null && code !== token.text) {
88 token.text = code;
89 token.escaped = true;
90 }
91
92 pending--;
93 if (pending === 0) {
94 done();
95 }
96 });
97 }, 0);
98 }
99 });
100
101 if (pending === 0) {
102 done();
103 }
104
105 return;
106 }
107
108 try {
109 const tokens = Lexer.lex(src, opt);
110 if (opt.walkTokens) {
111 marked.walkTokens(tokens, opt.walkTokens);
112 }
113 return Parser.parse(tokens, opt);
114 } catch (e) {
115 e.message += '\nPlease report this to https://github.com/markedjs/marked.';
116 if (opt.silent) {
117 return '<p>An error occurred:</p><pre>'
118 + escape(e.message + '', true)
119 + '</pre>';
120 }
121 throw e;
122 }
123}
124
125/**
126 * Options
127 */
128
129marked.options =
130marked.setOptions = function(opt) {
131 merge(marked.defaults, opt);
132 changeDefaults(marked.defaults);
133 return marked;
134};
135
136marked.getDefaults = getDefaults;
137
138marked.defaults = defaults;
139
140/**
141 * Use Extension
142 */
143
144marked.use = function(...args) {
145 const opts = merge({}, ...args);
146 const extensions = marked.defaults.extensions || { renderers: {}, childTokens: {} };
147 let hasExtensions;
148
149 args.forEach((pack) => {
150 // ==-- Parse "addon" extensions --== //
151 if (pack.extensions) {
152 hasExtensions = true;
153 pack.extensions.forEach((ext) => {
154 if (!ext.name) {
155 throw new Error('extension name required');
156 }
157 if (ext.renderer) { // Renderer extensions
158 const prevRenderer = extensions.renderers ? extensions.renderers[ext.name] : null;
159 if (prevRenderer) {
160 // Replace extension with func to run new extension but fall back if false
161 extensions.renderers[ext.name] = function(...args) {
162 let ret = ext.renderer.apply(this, args);
163 if (ret === false) {
164 ret = prevRenderer.apply(this, args);
165 }
166 return ret;
167 };
168 } else {
169 extensions.renderers[ext.name] = ext.renderer;
170 }
171 }
172 if (ext.tokenizer) { // Tokenizer Extensions
173 if (!ext.level || (ext.level !== 'block' && ext.level !== 'inline')) {
174 throw new Error("extension level must be 'block' or 'inline'");
175 }
176 if (extensions[ext.level]) {
177 extensions[ext.level].unshift(ext.tokenizer);
178 } else {
179 extensions[ext.level] = [ext.tokenizer];
180 }
181 if (ext.start) { // Function to check for start of token
182 if (ext.level === 'block') {
183 if (extensions.startBlock) {
184 extensions.startBlock.push(ext.start);
185 } else {
186 extensions.startBlock = [ext.start];
187 }
188 } else if (ext.level === 'inline') {
189 if (extensions.startInline) {
190 extensions.startInline.push(ext.start);
191 } else {
192 extensions.startInline = [ext.start];
193 }
194 }
195 }
196 }
197 if (ext.childTokens) { // Child tokens to be visited by walkTokens
198 extensions.childTokens[ext.name] = ext.childTokens;
199 }
200 });
201 }
202
203 // ==-- Parse "overwrite" extensions --== //
204 if (pack.renderer) {
205 const renderer = marked.defaults.renderer || new Renderer();
206 for (const prop in pack.renderer) {
207 const prevRenderer = renderer[prop];
208 // Replace renderer with func to run extension, but fall back if false
209 renderer[prop] = (...args) => {
210 let ret = pack.renderer[prop].apply(renderer, args);
211 if (ret === false) {
212 ret = prevRenderer.apply(renderer, args);
213 }
214 return ret;
215 };
216 }
217 opts.renderer = renderer;
218 }
219 if (pack.tokenizer) {
220 const tokenizer = marked.defaults.tokenizer || new Tokenizer();
221 for (const prop in pack.tokenizer) {
222 const prevTokenizer = tokenizer[prop];
223 // Replace tokenizer with func to run extension, but fall back if false
224 tokenizer[prop] = (...args) => {
225 let ret = pack.tokenizer[prop].apply(tokenizer, args);
226 if (ret === false) {
227 ret = prevTokenizer.apply(tokenizer, args);
228 }
229 return ret;
230 };
231 }
232 opts.tokenizer = tokenizer;
233 }
234
235 // ==-- Parse WalkTokens extensions --== //
236 if (pack.walkTokens) {
237 const walkTokens = marked.defaults.walkTokens;
238 opts.walkTokens = (token) => {
239 pack.walkTokens.call(this, token);
240 if (walkTokens) {
241 walkTokens(token);
242 }
243 };
244 }
245
246 if (hasExtensions) {
247 opts.extensions = extensions;
248 }
249
250 marked.setOptions(opts);
251 });
252};
253
254/**
255 * Run callback for every token
256 */
257
258marked.walkTokens = function(tokens, callback) {
259 for (const token of tokens) {
260 callback(token);
261 switch (token.type) {
262 case 'table': {
263 for (const cell of token.tokens.header) {
264 marked.walkTokens(cell, callback);
265 }
266 for (const row of token.tokens.cells) {
267 for (const cell of row) {
268 marked.walkTokens(cell, callback);
269 }
270 }
271 break;
272 }
273 case 'list': {
274 marked.walkTokens(token.items, callback);
275 break;
276 }
277 default: {
278 if (marked.defaults.extensions && marked.defaults.extensions.childTokens && marked.defaults.extensions.childTokens[token.type]) { // Walk any extensions
279 marked.defaults.extensions.childTokens[token.type].forEach(function(childTokens) {
280 marked.walkTokens(token[childTokens], callback);
281 });
282 } else if (token.tokens) {
283 marked.walkTokens(token.tokens, callback);
284 }
285 }
286 }
287 }
288};
289
290/**
291 * Parse Inline
292 */
293marked.parseInline = function(src, opt) {
294 // throw error in case of non string input
295 if (typeof src === 'undefined' || src === null) {
296 throw new Error('marked.parseInline(): input parameter is undefined or null');
297 }
298 if (typeof src !== 'string') {
299 throw new Error('marked.parseInline(): input parameter is of type '
300 + Object.prototype.toString.call(src) + ', string expected');
301 }
302
303 opt = merge({}, marked.defaults, opt || {});
304 checkSanitizeDeprecation(opt);
305
306 try {
307 const tokens = Lexer.lexInline(src, opt);
308 if (opt.walkTokens) {
309 marked.walkTokens(tokens, opt.walkTokens);
310 }
311 return Parser.parseInline(tokens, opt);
312 } catch (e) {
313 e.message += '\nPlease report this to https://github.com/markedjs/marked.';
314 if (opt.silent) {
315 return '<p>An error occurred:</p><pre>'
316 + escape(e.message + '', true)
317 + '</pre>';
318 }
319 throw e;
320 }
321};
322
323/**
324 * Expose
325 */
326
327marked.Parser = Parser;
328marked.parser = Parser.parse;
329
330marked.Renderer = Renderer;
331marked.TextRenderer = TextRenderer;
332
333marked.Lexer = Lexer;
334marked.lexer = Lexer.lex;
335
336marked.Tokenizer = Tokenizer;
337
338marked.Slugger = Slugger;
339
340marked.parse = marked;
341
342module.exports = marked;