From d39417e100bca193b4e5e9010b8f4fd5c03859a9 Mon Sep 17 00:00:00 2001
From: Alex Kocharin
Date: Fri, 31 Oct 2014 23:20:18 +0300
Subject: [PATCH] Added a core chain
---
lib/index.js | 59 ++++++++++------
lib/linkifier.js | 42 -----------
lib/parser_inline.js | 11 +--
lib/renderer.js | 18 ++++-
lib/rules_block/paragraph.js | 11 +--
lib/rules_core/block.js | 6 ++
lib/rules_core/inline.js | 13 ++++
lib/rules_core/linkify.js | 128 ++++++++++++++++++++++++++++++++++
lib/rules_core/references.js | 28 ++++++++
lib/rules_core/typographer.js | 14 ++++
lib/rules_text/linkify.js | 120 -------------------------------
lib/rules_text/replace.js | 6 +-
lib/rules_text/smartquotes.js | 4 +-
lib/typographer.js | 4 +-
14 files changed, 250 insertions(+), 214 deletions(-)
delete mode 100644 lib/linkifier.js
create mode 100644 lib/rules_core/block.js
create mode 100644 lib/rules_core/inline.js
create mode 100644 lib/rules_core/linkify.js
create mode 100644 lib/rules_core/references.js
create mode 100644 lib/rules_core/typographer.js
delete mode 100644 lib/rules_text/linkify.js
diff --git a/lib/index.js b/lib/index.js
index dce8cb3..acb8bf7 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -9,8 +9,7 @@ var Renderer = require('./renderer');
var ParserBlock = require('./parser_block');
var ParserInline = require('./parser_inline');
var Typographer = require('./typographer');
-var Linkifier = require('./linkifier');
-
+var Ruler = require('./ruler');
var config = {
'default': require('./configs/default'),
@@ -18,6 +17,26 @@ var config = {
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
//
@@ -29,23 +48,19 @@ function Remarkable(presetName, options) {
}
}
- this.options = {};
- this.state = null;
-
this.inline = new ParserInline();
this.block = new ParserBlock();
this.renderer = new Renderer();
+ this.ruler = new Ruler();
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]);
-
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.
//
Remarkable.prototype.parse = function (src, env) {
- var tokens, tok, i, l;
- // Parse blocks
- tokens = this.block.parse(src, this.options, env);
-
- // Parse inlines
- for (i = 0, l = tokens.length; i < l; i++) {
- tok = tokens[i];
- if (tok.type === 'inline') {
- tok.children = this.inline.parse(tok.content, this.options, env);
- }
+ var i, len,
+ rules = this.ruler.getRules(''),
+ state = new StateCore(this, src, env);
+
+ len = rules.length;
+
+ for (i = 0; i < len; i++) {
+ rules[i](state);
}
- return tokens;
+ return state.tokens;
};
// Main method that does all magic :)
diff --git a/lib/linkifier.js b/lib/linkifier.js
deleted file mode 100644
index e28c196..0000000
--- a/lib/linkifier.js
+++ /dev/null
@@ -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;
diff --git a/lib/parser_inline.js b/lib/parser_inline.js
index 5384d85..1e73998 100644
--- a/lib/parser_inline.js
+++ b/lib/parser_inline.js
@@ -127,16 +127,7 @@ ParserInline.prototype.tokenize = function (state) {
ParserInline.prototype.parse = function (str, options, env) {
var state = new StateInline(str, this, options, env);
- this.tokenize(state);
-
- if (options.linkify) {
- this.linkifier.process(state);
- }
- if (options.typographer) {
- this.typographer.process(state);
- }
-
- return state.tokens;
+ return this.tokenize(state);
};
diff --git a/lib/renderer.js b/lib/renderer.js
index 35a49e7..89d104f 100644
--- a/lib/renderer.js
+++ b/lib/renderer.js
@@ -42,10 +42,23 @@ function escapeHtml(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
function getBreak(tokens, idx) {
- if (++idx < tokens.length &&
+ idx = nextToken(tokens, idx);
+ if (idx < tokens.length &&
tokens[idx].type === 'list_item_close') {
return '';
}
@@ -136,7 +149,8 @@ rules.paragraph_open = function (tokens, idx/*, options*/) {
return tokens[idx].tight ? '' : '';
};
rules.paragraph_close = function (tokens, idx /*, options*/) {
- return (tokens[idx].tight ? '' : '
') + getBreak(tokens, idx);
+ var addBreak = !(tokens[idx].tight && idx && tokens[idx - 1].type === 'inline' && !tokens[idx - 1].content);
+ return (tokens[idx].tight ? '' : '
') + (addBreak ? getBreak(tokens, idx) : '');
};
diff --git a/lib/rules_block/paragraph.js b/lib/rules_block/paragraph.js
index 2ebac81..d25e5da 100644
--- a/lib/rules_block/paragraph.js
+++ b/lib/rules_block/paragraph.js
@@ -3,11 +3,8 @@
'use strict';
-var parseRef = require('../parser_ref');
-
-
module.exports = function paragraph(state, startLine/*, endLine*/) {
- var endLine, content, pos, terminate, i, l,
+ var endLine, content, terminate, i, l,
nextLine = startLine + 1,
terminatorRules;
@@ -36,12 +33,6 @@ module.exports = function paragraph(state, startLine/*, endLine*/) {
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;
if (content.length) {
state.tokens.push({
diff --git a/lib/rules_core/block.js b/lib/rules_core/block.js
new file mode 100644
index 0000000..01769bc
--- /dev/null
+++ b/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);
+};
diff --git a/lib/rules_core/inline.js b/lib/rules_core/inline.js
new file mode 100644
index 0000000..a019de9
--- /dev/null
+++ b/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);
+ }
+ }
+};
diff --git a/lib/rules_core/linkify.js b/lib/rules_core/linkify.js
new file mode 100644
index 0000000..acb2f36
--- /dev/null
+++ b/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 /^\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));
+ }
+ }
+ }
+};
diff --git a/lib/rules_core/references.js b/lib/rules_core/references.js
new file mode 100644
index 0000000..cb03c59
--- /dev/null
+++ b/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;
+ }
+ }
+ }
+};
diff --git a/lib/rules_core/typographer.js b/lib/rules_core/typographer.js
new file mode 100644
index 0000000..54c8f4a
--- /dev/null
+++ b/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);
+ }
+ }
+};
diff --git a/lib/rules_text/linkify.js b/lib/rules_text/linkify.js
deleted file mode 100644
index 414294f..0000000
--- a/lib/rules_text/linkify.js
+++ /dev/null
@@ -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 /^\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));
- }
- }
-};
diff --git a/lib/rules_text/replace.js b/lib/rules_text/replace.js
index f311631..88d5c39 100644
--- a/lib/rules_text/replace.js
+++ b/lib/rules_text/replace.js
@@ -6,10 +6,10 @@
var COPY_RE = /\((c|tm|r|p)\)/i;
var RARE_RE = /\+-|\.\.|\?\?\?\?|!!!!|,,|--/;
-module.exports = function replace(t, state) {
+module.exports = function replace(typographer, blockToken) {
var i, token, text,
- tokens = state.tokens,
- options = t.options;
+ tokens = blockToken.children,
+ options = typographer.options;
for (i = tokens.length - 1; i >= 0; i--) {
token = tokens[i];
diff --git a/lib/rules_text/smartquotes.js b/lib/rules_text/smartquotes.js
index 3482412..8febc38 100644
--- a/lib/rules_text/smartquotes.js
+++ b/lib/rules_text/smartquotes.js
@@ -22,11 +22,11 @@ function replaceAt(str, index, ch) {
var stack = [];
-module.exports = function smartquotes(typographer, state) {
+module.exports = function smartquotes(typographer, blockToken) {
/*eslint max-depth:0*/
var i, token, text, t, pos, max, thisLevel, lastSpace, nextSpace, item, canOpen, canClose, j, isSingle, chars,
options = typographer.options,
- tokens = state.tokens;
+ tokens = blockToken.children;
stack.length = 0;
diff --git a/lib/typographer.js b/lib/typographer.js
index e2107c9..dd549e1 100644
--- a/lib/typographer.js
+++ b/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;
rules = this.ruler.getRules('');
for (i = 0, l = rules.length; i < l; i++) {
- rules[i](this, state);
+ rules[i](this, token);
}
};