Browse Source

Added a core chain

pull/14/head
Alex Kocharin 11 years ago
parent
commit
d39417e100
  1. 59
      lib/index.js
  2. 42
      lib/linkifier.js
  3. 11
      lib/parser_inline.js
  4. 18
      lib/renderer.js
  5. 11
      lib/rules_block/paragraph.js
  6. 6
      lib/rules_core/block.js
  7. 13
      lib/rules_core/inline.js
  8. 128
      lib/rules_core/linkify.js
  9. 28
      lib/rules_core/references.js
  10. 14
      lib/rules_core/typographer.js
  11. 120
      lib/rules_text/linkify.js
  12. 6
      lib/rules_text/replace.js
  13. 4
      lib/rules_text/smartquotes.js
  14. 4
      lib/typographer.js

59
lib/index.js

@ -9,8 +9,7 @@ var Renderer = require('./renderer');
var ParserBlock = require('./parser_block'); var ParserBlock = require('./parser_block');
var ParserInline = require('./parser_inline'); var ParserInline = require('./parser_inline');
var Typographer = require('./typographer'); var Typographer = require('./typographer');
var Linkifier = require('./linkifier'); var Ruler = require('./ruler');
var config = { var config = {
'default': require('./configs/default'), 'default': require('./configs/default'),
@ -18,6 +17,26 @@ var config = {
commonmark: require('./configs/commonmark') commonmark: require('./configs/commonmark')
}; };
var _rules = [
[ 'block', require('./rules_core/block') ],
[ 'references', require('./rules_core/references') ],
[ 'inline', require('./rules_core/inline') ],
[ 'linkify', require('./rules_core/linkify') ],
[ 'typographer', require('./rules_core/typographer') ]
];
function StateCore(self, src, env) {
this.src = src;
this.env = env;
this.options = self.options;
this.tokens = [];
this.inline = self.inline;
this.block = self.block;
this.renderer = self.renderer;
this.typographer = self.typographer;
}
// Main class // Main class
// //
@ -29,23 +48,19 @@ function Remarkable(presetName, options) {
} }
} }
this.options = {};
this.state = null;
this.inline = new ParserInline(); this.inline = new ParserInline();
this.block = new ParserBlock(); this.block = new ParserBlock();
this.renderer = new Renderer(); this.renderer = new Renderer();
this.ruler = new Ruler();
this.typographer = new Typographer(); this.typographer = new Typographer();
this.linkifier = new Linkifier();
// Cross-references to simplify code (a bit dirty, but easy).
this.block.inline = this.inline;
this.inline.typographer = this.typographer;
this.inline.linkifier = this.linkifier;
this.options = {};
this.configure(config[presetName]); this.configure(config[presetName]);
if (options) { this.set(options); } if (options) { this.set(options); }
for (var i = 0; i < _rules.length; i++) {
this.ruler.push(_rules[i][0], _rules[i][1]);
}
} }
@ -96,19 +111,17 @@ Remarkable.prototype.use = function (plugin, opts) {
// definitions data. // definitions data.
// //
Remarkable.prototype.parse = function (src, env) { Remarkable.prototype.parse = function (src, env) {
var tokens, tok, i, l; var i, len,
// Parse blocks rules = this.ruler.getRules(''),
tokens = this.block.parse(src, this.options, env); state = new StateCore(this, src, env);
// Parse inlines len = rules.length;
for (i = 0, l = tokens.length; i < l; i++) {
tok = tokens[i]; for (i = 0; i < len; i++) {
if (tok.type === 'inline') { rules[i](state);
tok.children = this.inline.parse(tok.content, this.options, env);
}
} }
return tokens; return state.tokens;
}; };
// Main method that does all magic :) // Main method that does all magic :)

42
lib/linkifier.js

@ -1,42 +0,0 @@
// Class of link replacement rules
//
'use strict';
var assign = require('./common/utils').assign;
var Ruler = require('./ruler');
var _rules = [
[ 'linkify', require('./rules_text/linkify') ]
];
function Linkifier() {
this.options = {};
this.ruler = new Ruler();
for (var i = 0; i < _rules.length; i++) {
this.ruler.push(_rules[i][0], _rules[i][1]);
}
}
Linkifier.prototype.set = function (options) {
assign(this.options, options);
};
Linkifier.prototype.process = function (state) {
var i, l, rules;
rules = this.ruler.getRules('');
for (i = 0, l = rules.length; i < l; i++) {
rules[i](this, state);
}
};
module.exports = Linkifier;

11
lib/parser_inline.js

@ -127,16 +127,7 @@ ParserInline.prototype.tokenize = function (state) {
ParserInline.prototype.parse = function (str, options, env) { ParserInline.prototype.parse = function (str, options, env) {
var state = new StateInline(str, this, options, env); var state = new StateInline(str, this, options, env);
this.tokenize(state); return this.tokenize(state);
if (options.linkify) {
this.linkifier.process(state);
}
if (options.typographer) {
this.typographer.process(state);
}
return state.tokens;
}; };

18
lib/renderer.js

@ -42,10 +42,23 @@ function escapeHtml(str) {
return str; return str;
} }
function nextToken(tokens, idx) {
if (++idx >= tokens.length - 2) { return idx; }
if (tokens[idx].type === 'paragraph_open' && tokens[idx].tight) {
if (tokens[idx + 1].type === 'inline' && tokens[idx + 1].content.length === 0) {
if (tokens[idx + 2].type === 'paragraph_close' && tokens[idx + 2].tight) {
return nextToken(tokens, idx + 2);
}
}
}
return idx;
}
// check if we need to hide '\n' before next token // check if we need to hide '\n' before next token
function getBreak(tokens, idx) { function getBreak(tokens, idx) {
if (++idx < tokens.length && idx = nextToken(tokens, idx);
if (idx < tokens.length &&
tokens[idx].type === 'list_item_close') { tokens[idx].type === 'list_item_close') {
return ''; return '';
} }
@ -136,7 +149,8 @@ rules.paragraph_open = function (tokens, idx/*, options*/) {
return tokens[idx].tight ? '' : '<p>'; return tokens[idx].tight ? '' : '<p>';
}; };
rules.paragraph_close = function (tokens, idx /*, options*/) { rules.paragraph_close = function (tokens, idx /*, options*/) {
return (tokens[idx].tight ? '' : '</p>') + getBreak(tokens, idx); var addBreak = !(tokens[idx].tight && idx && tokens[idx - 1].type === 'inline' && !tokens[idx - 1].content);
return (tokens[idx].tight ? '' : '</p>') + (addBreak ? getBreak(tokens, idx) : '');
}; };

11
lib/rules_block/paragraph.js

@ -3,11 +3,8 @@
'use strict'; 'use strict';
var parseRef = require('../parser_ref');
module.exports = function paragraph(state, startLine/*, endLine*/) { module.exports = function paragraph(state, startLine/*, endLine*/) {
var endLine, content, pos, terminate, i, l, var endLine, content, terminate, i, l,
nextLine = startLine + 1, nextLine = startLine + 1,
terminatorRules; terminatorRules;
@ -36,12 +33,6 @@ module.exports = function paragraph(state, startLine/*, endLine*/) {
content = state.getLines(startLine, nextLine, state.blkIndent, false).trim(); content = state.getLines(startLine, nextLine, state.blkIndent, false).trim();
while (content.length) {
pos = parseRef(content, state.parser.inline, state.options, state.env);
if (pos < 0) { break; }
content = content.slice(pos).trim();
}
state.line = nextLine; state.line = nextLine;
if (content.length) { if (content.length) {
state.tokens.push({ state.tokens.push({

6
lib/rules_core/block.js

@ -0,0 +1,6 @@
'use strict';
module.exports = function block(state) {
var tokens = state.block.parse(state.src, state.options, state.env);
state.tokens = state.tokens.concat(tokens);
};

13
lib/rules_core/inline.js

@ -0,0 +1,13 @@
'use strict';
module.exports = function inline(state) {
var tokens = state.tokens, tok, i, l;
// Parse inlines
for (i = 0, l = tokens.length; i < l; i++) {
tok = tokens[i];
if (tok.type === 'inline') {
tok.children = state.inline.parse(tok.content, state.options, state.env);
}
}
};

128
lib/rules_core/linkify.js

@ -0,0 +1,128 @@
// Replace link-like texts with link nodes.
//
// Currently restricted to http/https/ftp
//
'use strict';
var Autolinker = require('autolinker');
var LINK_SCAN_RE = /www|\:\/\//;
var links = [];
var autolinker = new Autolinker({
stripPrefix: false,
replaceFn: function (autolinker, match) {
// Only collect matched strings but don't change anything.
if (match.getType() === 'url') {
links.push({ text: match.matchedText, url: match.getUrl() });
}
return false;
}
});
function isLinkOpen(str) {
return /^<a[>\s]/i.test(str);
}
function isLinkClose(str) {
return /^<\/a\s*>/i.test(str);
}
module.exports = function linkify(state) {
var i, j, l, tokens, token, text, nodes, ln, pos, level, htmlLinkLevel,
blockTokens = state.tokens;
if (!state.options.linkify) { return; }
for (j = 0, l = blockTokens.length; j < l; j++) {
if (blockTokens[j].type !== 'inline') { continue; }
tokens = blockTokens[j].children;
htmlLinkLevel = 0;
// We scan from the end, to keep position when new tags added.
// Use reversed logic in links start/end match
for (i = tokens.length - 1; i >= 0; i--) {
token = tokens[i];
// Skip content of markdown links
if (token.type === 'link_close') {
i--;
while (tokens[i].level !== token.level && tokens[i].type !== 'link_open') {
i--;
}
continue;
}
// Skip content of html tag links
if (token.type === 'htmltag') {
if (isLinkOpen(token.content) && htmlLinkLevel > 0) {
htmlLinkLevel--;
}
if (isLinkClose(token.content)) {
htmlLinkLevel++;
}
}
if (htmlLinkLevel > 0) { continue; }
if (token.type === 'text' && LINK_SCAN_RE.test(token.content)) {
text = token.content;
links.length = 0;
autolinker.link(text);
if (!links.length) { continue; }
// Now split string to nodes
nodes = [];
level = token.level;
for (ln = 0; ln < links.length; ln++) {
if (!state.inline.validateLink(links[ln].url)) { continue; }
pos = text.indexOf(links[ln].text);
if (pos === -1) { continue; }
if (pos) {
level = level;
nodes.push({
type: 'text',
content: text.slice(0, pos),
level: level
});
}
nodes.push({
type: 'link_open',
href: links[ln].url,
title: '',
level: level++
});
nodes.push({
type: 'text',
content: links[ln].text,
level: level
});
nodes.push({
type: 'link_close',
level: --level
});
text = text.slice(pos + links[ln].text.length);
}
if (text.length) {
nodes.push({
type: 'text',
content: text,
level: level
});
}
// replace current node
blockTokens[j].children = tokens = [].concat(tokens.slice(0, i), nodes, tokens.slice(i + 1));
}
}
}
};

28
lib/rules_core/references.js

@ -0,0 +1,28 @@
'use strict';
var parseRef = require('../parser_ref');
module.exports = function references(state) {
var tokens = state.tokens, i, l, content, pos;
// Parse inlines
for (i = 1, l = tokens.length - 1; i < l; i++) {
if (tokens[i - 1].type === 'paragraph_open' &&
tokens[i].type === 'inline' &&
tokens[i + 1].type === 'paragraph_close') {
content = tokens[i].content;
while (content.length) {
pos = parseRef(content, state.inline, state.options, state.env);
if (pos < 0) { break; }
content = content.slice(pos).trim();
}
tokens[i].content = content;
if (!content.length) {
tokens[i - 1].tight = true;
tokens[i + 1].tight = true;
}
}
}
};

14
lib/rules_core/typographer.js

@ -0,0 +1,14 @@
'use strict';
module.exports = function typographer(state) {
if (!state.options.typographer) { return; }
var tokens = state.tokens, tok, i, l;
// Parse inlines
for (i = 0, l = tokens.length; i < l; i++) {
tok = tokens[i];
if (tok.type === 'inline') {
state.typographer.process(tok, state);
}
}
};

120
lib/rules_text/linkify.js

@ -1,120 +0,0 @@
// Replace link-like texts with link nodes.
//
// Currently restricted to http/https/ftp
//
'use strict';
var Autolinker = require('autolinker');
var LINK_SCAN_RE = /www|\:\/\//;
var links = [];
var autolinker = new Autolinker({
stripPrefix: false,
replaceFn: function (autolinker, match) {
// Only collect matched strings but don't change anything.
if (match.getType() === 'url') {
links.push({ text: match.matchedText, url: match.getUrl() });
}
return false;
}
});
function isLinkOpen(str) {
return /^<a[>\s]/i.test(str);
}
function isLinkClose(str) {
return /^<\/a\s*>/i.test(str);
}
module.exports = function linkify(t, state) {
var i, token, text, nodes, ln, pos, level,
htmlLinkLevel = 0,
tokens = state.tokens;
// We scan from the end, to keep position when new tags added.
// Use reversed logic in links start/end match
for (i = tokens.length - 1; i >= 0; i--) {
token = tokens[i];
// Skip content of markdown links
if (token.type === 'link_close') {
i--;
while (tokens[i].level !== token.level && tokens[i].type !== 'link_open') {
i--;
}
continue;
}
// Skip content of html tag links
if (token.type === 'htmltag') {
if (isLinkOpen(token.content) && htmlLinkLevel > 0) {
htmlLinkLevel--;
}
if (isLinkClose(token.content)) {
htmlLinkLevel++;
}
}
if (htmlLinkLevel > 0) { continue; }
if (token.type === 'text' && LINK_SCAN_RE.test(token.content)) {
text = token.content;
links.length = 0;
autolinker.link(text);
if (!links.length) { continue; }
// Now split string to nodes
nodes = [];
level = token.level;
for (ln = 0; ln < links.length; ln++) {
if (!state.parser.validateLink(links[ln].url)) { continue; }
pos = text.indexOf(links[ln].text);
if (pos === -1) { continue; }
if (pos) {
level = level;
nodes.push({
type: 'text',
content: text.slice(0, pos),
level: level
});
}
nodes.push({
type: 'link_open',
href: links[ln].url,
title: '',
level: level++
});
nodes.push({
type: 'text',
content: links[ln].text,
level: level
});
nodes.push({
type: 'link_close',
level: --level
});
text = text.slice(pos + links[ln].text.length);
}
if (text.length) {
nodes.push({
type: 'text',
content: text,
level: level
});
}
// replace cuttent node
state.tokens = tokens = [].concat(tokens.slice(0, i), nodes, tokens.slice(i + 1));
}
}
};

6
lib/rules_text/replace.js

@ -6,10 +6,10 @@
var COPY_RE = /\((c|tm|r|p)\)/i; var COPY_RE = /\((c|tm|r|p)\)/i;
var RARE_RE = /\+-|\.\.|\?\?\?\?|!!!!|,,|--/; var RARE_RE = /\+-|\.\.|\?\?\?\?|!!!!|,,|--/;
module.exports = function replace(t, state) { module.exports = function replace(typographer, blockToken) {
var i, token, text, var i, token, text,
tokens = state.tokens, tokens = blockToken.children,
options = t.options; options = typographer.options;
for (i = tokens.length - 1; i >= 0; i--) { for (i = tokens.length - 1; i >= 0; i--) {
token = tokens[i]; token = tokens[i];

4
lib/rules_text/smartquotes.js

@ -22,11 +22,11 @@ function replaceAt(str, index, ch) {
var stack = []; var stack = [];
module.exports = function smartquotes(typographer, state) { module.exports = function smartquotes(typographer, blockToken) {
/*eslint max-depth:0*/ /*eslint max-depth:0*/
var i, token, text, t, pos, max, thisLevel, lastSpace, nextSpace, item, canOpen, canClose, j, isSingle, chars, var i, token, text, t, pos, max, thisLevel, lastSpace, nextSpace, item, canOpen, canClose, j, isSingle, chars,
options = typographer.options, options = typographer.options,
tokens = state.tokens; tokens = blockToken.children;
stack.length = 0; stack.length = 0;

4
lib/typographer.js

@ -33,13 +33,13 @@ Typographer.prototype.set = function (options) {
}; };
Typographer.prototype.process = function (state) { Typographer.prototype.process = function (token) {
var i, l, rules; var i, l, rules;
rules = this.ruler.getRules(''); rules = this.ruler.getRules('');
for (i = 0, l = rules.length; i < l; i++) { for (i = 0, l = rules.length; i < l; i++) {
rules[i](this, state); rules[i](this, token);
} }
}; };

Loading…
Cancel
Save