comparison lib/assets/javascripts/marked.js @ 100:90b59418828a

Three times more fun with tabs (added plain and markdown tab).
author Edho Arief <edho@myconan.net>
date Mon, 12 Nov 2012 10:42:00 +0700
parents
children
comparison
equal deleted inserted replaced
99:e0e99705e79d 100:90b59418828a
1 /**
2 * marked - A markdown parser (https://github.com/chjj/marked)
3 * Copyright (c) 2011-2012, Christopher Jeffrey. (MIT Licensed)
4 */
5
6 ;(function() {
7
8 /**
9 * Block-Level Grammar
10 */
11
12 var block = {
13 newline: /^\n+/,
14 code: /^( {4}[^\n]+\n*)+/,
15 fences: noop,
16 hr: /^( *[-*_]){3,} *(?:\n+|$)/,
17 heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,
18 lheading: /^([^\n]+)\n *(=|-){3,} *\n*/,
19 blockquote: /^( *>[^\n]+(\n[^\n]+)*\n*)+/,
20 list: /^( *)(bull) [^\0]+?(?:hr|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
21 html: /^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/,
22 def: /^ *\[([^\]]+)\]: *([^\s]+)(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,
23 paragraph: /^([^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+\n*/,
24 text: /^[^\n]+/
25 };
26
27 block.bullet = /(?:[*+-]|\d+\.)/;
28 block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;
29 block.item = replace(block.item, 'gm')
30 (/bull/g, block.bullet)
31 ();
32
33 block.list = replace(block.list)
34 (/bull/g, block.bullet)
35 ('hr', /\n+(?=(?: *[-*_]){3,} *(?:\n+|$))/)
36 ();
37
38 block.html = replace(block.html)
39 ('comment', /<!--[^\0]*?-->/)
40 ('closed', /<(tag)[^\0]+?<\/\1>/)
41 ('closing', /<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/)
42 (/tag/g, tag())
43 ();
44
45 block.paragraph = replace(block.paragraph)
46 ('hr', block.hr)
47 ('heading', block.heading)
48 ('lheading', block.lheading)
49 ('blockquote', block.blockquote)
50 ('tag', '<' + tag())
51 ('def', block.def)
52 ();
53
54 block.normal = {
55 fences: block.fences,
56 paragraph: block.paragraph
57 };
58
59 block.gfm = {
60 fences: /^ *(```|~~~) *(\w+)? *\n([^\0]+?)\s*\1 *(?:\n+|$)/,
61 paragraph: /^/
62 };
63
64 block.gfm.paragraph = replace(block.paragraph)
65 ('(?!', '(?!' + block.gfm.fences.source.replace('\\1', '\\2') + '|')
66 ();
67
68 /**
69 * Block Lexer
70 */
71
72 block.lexer = function(src) {
73 var tokens = [];
74
75 tokens.links = {};
76
77 src = src
78 .replace(/\r\n|\r/g, '\n')
79 .replace(/\t/g, ' ');
80
81 return block.token(src, tokens, true);
82 };
83
84 block.token = function(src, tokens, top) {
85 var src = src.replace(/^ +$/gm, '')
86 , next
87 , loose
88 , cap
89 , item
90 , space
91 , i
92 , l;
93
94 while (src) {
95 // newline
96 if (cap = block.newline.exec(src)) {
97 src = src.substring(cap[0].length);
98 if (cap[0].length > 1) {
99 tokens.push({
100 type: 'space'
101 });
102 }
103 }
104
105 // code
106 if (cap = block.code.exec(src)) {
107 src = src.substring(cap[0].length);
108 cap = cap[0].replace(/^ {4}/gm, '');
109 tokens.push({
110 type: 'code',
111 text: !options.pedantic
112 ? cap.replace(/\n+$/, '')
113 : cap
114 });
115 continue;
116 }
117
118 // fences (gfm)
119 if (cap = block.fences.exec(src)) {
120 src = src.substring(cap[0].length);
121 tokens.push({
122 type: 'code',
123 lang: cap[2],
124 text: cap[3]
125 });
126 continue;
127 }
128
129 // heading
130 if (cap = block.heading.exec(src)) {
131 src = src.substring(cap[0].length);
132 tokens.push({
133 type: 'heading',
134 depth: cap[1].length,
135 text: cap[2]
136 });
137 continue;
138 }
139
140 // lheading
141 if (cap = block.lheading.exec(src)) {
142 src = src.substring(cap[0].length);
143 tokens.push({
144 type: 'heading',
145 depth: cap[2] === '=' ? 1 : 2,
146 text: cap[1]
147 });
148 continue;
149 }
150
151 // hr
152 if (cap = block.hr.exec(src)) {
153 src = src.substring(cap[0].length);
154 tokens.push({
155 type: 'hr'
156 });
157 continue;
158 }
159
160 // blockquote
161 if (cap = block.blockquote.exec(src)) {
162 src = src.substring(cap[0].length);
163
164 tokens.push({
165 type: 'blockquote_start'
166 });
167
168 cap = cap[0].replace(/^ *> ?/gm, '');
169
170 // Pass `top` to keep the current
171 // "toplevel" state. This is exactly
172 // how markdown.pl works.
173 block.token(cap, tokens, top);
174
175 tokens.push({
176 type: 'blockquote_end'
177 });
178
179 continue;
180 }
181
182 // list
183 if (cap = block.list.exec(src)) {
184 src = src.substring(cap[0].length);
185
186 tokens.push({
187 type: 'list_start',
188 ordered: isFinite(cap[2])
189 });
190
191 // Get each top-level item.
192 cap = cap[0].match(block.item);
193
194 next = false;
195 l = cap.length;
196 i = 0;
197
198 for (; i < l; i++) {
199 item = cap[i];
200
201 // Remove the list item's bullet
202 // so it is seen as the next token.
203 space = item.length;
204 item = item.replace(/^ *([*+-]|\d+\.) +/, '');
205
206 // Outdent whatever the
207 // list item contains. Hacky.
208 if (~item.indexOf('\n ')) {
209 space -= item.length;
210 item = !options.pedantic
211 ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '')
212 : item.replace(/^ {1,4}/gm, '');
213 }
214
215 // Determine whether item is loose or not.
216 // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
217 // for discount behavior.
218 loose = next || /\n\n(?!\s*$)/.test(item);
219 if (i !== l - 1) {
220 next = item[item.length-1] === '\n';
221 if (!loose) loose = next;
222 }
223
224 tokens.push({
225 type: loose
226 ? 'loose_item_start'
227 : 'list_item_start'
228 });
229
230 // Recurse.
231 block.token(item, tokens);
232
233 tokens.push({
234 type: 'list_item_end'
235 });
236 }
237
238 tokens.push({
239 type: 'list_end'
240 });
241
242 continue;
243 }
244
245 // html
246 if (cap = block.html.exec(src)) {
247 src = src.substring(cap[0].length);
248 tokens.push({
249 type: options.sanitize
250 ? 'paragraph'
251 : 'html',
252 pre: cap[1] === 'pre',
253 text: cap[0]
254 });
255 continue;
256 }
257
258 // def
259 if (top && (cap = block.def.exec(src))) {
260 src = src.substring(cap[0].length);
261 tokens.links[cap[1].toLowerCase()] = {
262 href: cap[2],
263 title: cap[3]
264 };
265 continue;
266 }
267
268 // top-level paragraph
269 if (top && (cap = block.paragraph.exec(src))) {
270 src = src.substring(cap[0].length);
271 tokens.push({
272 type: 'paragraph',
273 text: cap[0]
274 });
275 continue;
276 }
277
278 // text
279 if (cap = block.text.exec(src)) {
280 // Top-level should never reach here.
281 src = src.substring(cap[0].length);
282 tokens.push({
283 type: 'text',
284 text: cap[0]
285 });
286 continue;
287 }
288 }
289
290 return tokens;
291 };
292
293 /**
294 * Inline Processing
295 */
296
297 var inline = {
298 escape: /^\\([\\`*{}\[\]()#+\-.!_>])/,
299 autolink: /^<([^ >]+(@|:\/)[^ >]+)>/,
300 url: noop,
301 tag: /^<!--[^\0]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,
302 link: /^!?\[(inside)\]\(href\)/,
303 reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/,
304 nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,
305 strong: /^__([^\0]+?)__(?!_)|^\*\*([^\0]+?)\*\*(?!\*)/,
306 em: /^\b_((?:__|[^\0])+?)_\b|^\*((?:\*\*|[^\0])+?)\*(?!\*)/,
307 code: /^(`+)([^\0]*?[^`])\1(?!`)/,
308 br: /^ {2,}\n(?!\s*$)/,
309 text: /^[^\0]+?(?=[\\<!\[_*`]| {2,}\n|$)/
310 };
311
312 inline._linkInside = /(?:\[[^\]]*\]|[^\]]|\](?=[^\[]*\]))*/;
313 inline._linkHref = /\s*<?([^\s]*?)>?(?:\s+['"]([^\0]*?)['"])?\s*/;
314
315 inline.link = replace(inline.link)
316 ('inside', inline._linkInside)
317 ('href', inline._linkHref)
318 ();
319
320 inline.reflink = replace(inline.reflink)
321 ('inside', inline._linkInside)
322 ();
323
324 inline.normal = {
325 url: inline.url,
326 strong: inline.strong,
327 em: inline.em,
328 text: inline.text
329 };
330
331 inline.pedantic = {
332 strong: /^__(?=\S)([^\0]*?\S)__(?!_)|^\*\*(?=\S)([^\0]*?\S)\*\*(?!\*)/,
333 em: /^_(?=\S)([^\0]*?\S)_(?!_)|^\*(?=\S)([^\0]*?\S)\*(?!\*)/
334 };
335
336 inline.gfm = {
337 url: /^(https?:\/\/[^\s]+[^.,:;"')\]\s])/,
338 text: /^[^\0]+?(?=[\\<!\[_*`]|https?:\/\/| {2,}\n|$)/
339 };
340
341 /**
342 * Inline Lexer
343 */
344
345 inline.lexer = function(src) {
346 var out = ''
347 , links = tokens.links
348 , link
349 , text
350 , href
351 , cap;
352
353 while (src) {
354 // escape
355 if (cap = inline.escape.exec(src)) {
356 src = src.substring(cap[0].length);
357 out += cap[1];
358 continue;
359 }
360
361 // autolink
362 if (cap = inline.autolink.exec(src)) {
363 src = src.substring(cap[0].length);
364 if (cap[2] === '@') {
365 text = cap[1][6] === ':'
366 ? mangle(cap[1].substring(7))
367 : mangle(cap[1]);
368 href = mangle('mailto:') + text;
369 } else {
370 text = escape(cap[1]);
371 href = text;
372 }
373 out += '<a href="'
374 + href
375 + '">'
376 + text
377 + '</a>';
378 continue;
379 }
380
381 // url (gfm)
382 if (cap = inline.url.exec(src)) {
383 src = src.substring(cap[0].length);
384 text = escape(cap[1]);
385 href = text;
386 out += '<a href="'
387 + href
388 + '">'
389 + text
390 + '</a>';
391 continue;
392 }
393
394 // tag
395 if (cap = inline.tag.exec(src)) {
396 src = src.substring(cap[0].length);
397 out += options.sanitize
398 ? escape(cap[0])
399 : cap[0];
400 continue;
401 }
402
403 // link
404 if (cap = inline.link.exec(src)) {
405 src = src.substring(cap[0].length);
406 out += outputLink(cap, {
407 href: cap[2],
408 title: cap[3]
409 });
410 continue;
411 }
412
413 // reflink, nolink
414 if ((cap = inline.reflink.exec(src))
415 || (cap = inline.nolink.exec(src))) {
416 src = src.substring(cap[0].length);
417 link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
418 link = links[link.toLowerCase()];
419 if (!link || !link.href) {
420 out += cap[0][0];
421 src = cap[0].substring(1) + src;
422 continue;
423 }
424 out += outputLink(cap, link);
425 continue;
426 }
427
428 // strong
429 if (cap = inline.strong.exec(src)) {
430 src = src.substring(cap[0].length);
431 out += '<strong>'
432 + inline.lexer(cap[2] || cap[1])
433 + '</strong>';
434 continue;
435 }
436
437 // em
438 if (cap = inline.em.exec(src)) {
439 src = src.substring(cap[0].length);
440 out += '<em>'
441 + inline.lexer(cap[2] || cap[1])
442 + '</em>';
443 continue;
444 }
445
446 // code
447 if (cap = inline.code.exec(src)) {
448 src = src.substring(cap[0].length);
449 out += '<code>'
450 + escape(cap[2], true)
451 + '</code>';
452 continue;
453 }
454
455 // br
456 if (cap = inline.br.exec(src)) {
457 src = src.substring(cap[0].length);
458 out += '<br>';
459 continue;
460 }
461
462 // text
463 if (cap = inline.text.exec(src)) {
464 src = src.substring(cap[0].length);
465 out += escape(cap[0]);
466 continue;
467 }
468 }
469
470 return out;
471 };
472
473 function outputLink(cap, link) {
474 if (cap[0][0] !== '!') {
475 return '<a href="'
476 + escape(link.href)
477 + '"'
478 + (link.title
479 ? ' title="'
480 + escape(link.title)
481 + '"'
482 : '')
483 + '>'
484 + inline.lexer(cap[1])
485 + '</a>';
486 } else {
487 return '<img src="'
488 + escape(link.href)
489 + '" alt="'
490 + escape(cap[1])
491 + '"'
492 + (link.title
493 ? ' title="'
494 + escape(link.title)
495 + '"'
496 : '')
497 + '>';
498 }
499 }
500
501 /**
502 * Parsing
503 */
504
505 var tokens
506 , token;
507
508 function next() {
509 return token = tokens.pop();
510 }
511
512 function tok() {
513 switch (token.type) {
514 case 'space': {
515 return '';
516 }
517 case 'hr': {
518 return '<hr>\n';
519 }
520 case 'heading': {
521 return '<h'
522 + token.depth
523 + '>'
524 + inline.lexer(token.text)
525 + '</h'
526 + token.depth
527 + '>\n';
528 }
529 case 'code': {
530 if (options.highlight) {
531 token.code = options.highlight(token.text, token.lang);
532 if (token.code != null && token.code !== token.text) {
533 token.escaped = true;
534 token.text = token.code;
535 }
536 }
537
538 if (!token.escaped) {
539 token.text = escape(token.text, true);
540 }
541
542 return '<pre><code'
543 + (token.lang
544 ? ' class="lang-'
545 + token.lang
546 + '"'
547 : '')
548 + '>'
549 + token.text
550 + '</code></pre>\n';
551 }
552 case 'blockquote_start': {
553 var body = '';
554
555 while (next().type !== 'blockquote_end') {
556 body += tok();
557 }
558
559 return '<blockquote>\n'
560 + body
561 + '</blockquote>\n';
562 }
563 case 'list_start': {
564 var type = token.ordered ? 'ol' : 'ul'
565 , body = '';
566
567 while (next().type !== 'list_end') {
568 body += tok();
569 }
570
571 return '<'
572 + type
573 + '>\n'
574 + body
575 + '</'
576 + type
577 + '>\n';
578 }
579 case 'list_item_start': {
580 var body = '';
581
582 while (next().type !== 'list_item_end') {
583 body += token.type === 'text'
584 ? parseText()
585 : tok();
586 }
587
588 return '<li>'
589 + body
590 + '</li>\n';
591 }
592 case 'loose_item_start': {
593 var body = '';
594
595 while (next().type !== 'list_item_end') {
596 body += tok();
597 }
598
599 return '<li>'
600 + body
601 + '</li>\n';
602 }
603 case 'html': {
604 return !token.pre && !options.pedantic
605 ? inline.lexer(token.text)
606 : token.text;
607 }
608 case 'paragraph': {
609 return '<p>'
610 + inline.lexer(token.text)
611 + '</p>\n';
612 }
613 case 'text': {
614 return '<p>'
615 + parseText()
616 + '</p>\n';
617 }
618 }
619 }
620
621 function parseText() {
622 var body = token.text
623 , top;
624
625 while ((top = tokens[tokens.length-1])
626 && top.type === 'text') {
627 body += '\n' + next().text;
628 }
629
630 return inline.lexer(body);
631 }
632
633 function parse(src) {
634 tokens = src.reverse();
635
636 var out = '';
637 while (next()) {
638 out += tok();
639 }
640
641 tokens = null;
642 token = null;
643
644 return out;
645 }
646
647 /**
648 * Helpers
649 */
650
651 function escape(html, encode) {
652 return html
653 .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&amp;')
654 .replace(/</g, '&lt;')
655 .replace(/>/g, '&gt;')
656 .replace(/"/g, '&quot;')
657 .replace(/'/g, '&#39;');
658 }
659
660 function mangle(text) {
661 var out = ''
662 , l = text.length
663 , i = 0
664 , ch;
665
666 for (; i < l; i++) {
667 ch = text.charCodeAt(i);
668 if (Math.random() > 0.5) {
669 ch = 'x' + ch.toString(16);
670 }
671 out += '&#' + ch + ';';
672 }
673
674 return out;
675 }
676
677 function tag() {
678 var tag = '(?!(?:'
679 + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code'
680 + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo'
681 + '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|@)\\b';
682
683 return tag;
684 }
685
686 function replace(regex, opt) {
687 regex = regex.source;
688 opt = opt || '';
689 return function self(name, val) {
690 if (!name) return new RegExp(regex, opt);
691 val = val.source || val;
692 val = val.replace(/(^|[^\[])\^/g, '$1');
693 regex = regex.replace(name, val);
694 return self;
695 };
696 }
697
698 function noop() {}
699 noop.exec = noop;
700
701 /**
702 * Marked
703 */
704
705 function marked(src, opt) {
706 setOptions(opt);
707 return parse(block.lexer(src));
708 }
709
710 /**
711 * Options
712 */
713
714 var options
715 , defaults;
716
717 function setOptions(opt) {
718 if (!opt) opt = defaults;
719 if (options === opt) return;
720 options = opt;
721
722 if (options.gfm) {
723 block.fences = block.gfm.fences;
724 block.paragraph = block.gfm.paragraph;
725 inline.text = inline.gfm.text;
726 inline.url = inline.gfm.url;
727 } else {
728 block.fences = block.normal.fences;
729 block.paragraph = block.normal.paragraph;
730 inline.text = inline.normal.text;
731 inline.url = inline.normal.url;
732 }
733
734 if (options.pedantic) {
735 inline.em = inline.pedantic.em;
736 inline.strong = inline.pedantic.strong;
737 } else {
738 inline.em = inline.normal.em;
739 inline.strong = inline.normal.strong;
740 }
741 }
742
743 marked.options =
744 marked.setOptions = function(opt) {
745 defaults = opt;
746 setOptions(opt);
747 return marked;
748 };
749
750 marked.setOptions({
751 gfm: true,
752 pedantic: false,
753 sanitize: false,
754 highlight: null
755 });
756
757 /**
758 * Expose
759 */
760
761 marked.parser = function(src, opt) {
762 setOptions(opt);
763 return parse(src);
764 };
765
766 marked.lexer = function(src, opt) {
767 setOptions(opt);
768 return block.lexer(src);
769 };
770
771 marked.parse = marked;
772
773 if (typeof module !== 'undefined') {
774 module.exports = marked;
775 } else {
776 this.marked = marked;
777 }
778
779 }).call(function() {
780 return this || (typeof window !== 'undefined' ? window : global);
781 }());