tasq/node_modules/marked-terminal/index.js

553 lines
14 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
import chalk from 'chalk';
import Table from 'cli-table3';
import cardinal from 'cardinal';
import * as emoji from 'node-emoji';
import ansiEscapes from 'ansi-escapes';
import supportsHyperlinks from 'supports-hyperlinks';
var TABLE_CELL_SPLIT = '^*||*^';
var TABLE_ROW_WRAP = '*|*|*|*';
var TABLE_ROW_WRAP_REGEXP = new RegExp(escapeRegExp(TABLE_ROW_WRAP), 'g');
var COLON_REPLACER = '*#COLON|*';
var COLON_REPLACER_REGEXP = new RegExp(escapeRegExp(COLON_REPLACER), 'g');
var TAB_ALLOWED_CHARACTERS = ['\t'];
// HARD_RETURN holds a character sequence used to indicate text has a
// hard (no-reflowing) line break. Previously \r and \r\n were turned
// into \n in marked's lexer- preprocessing step. So \r is safe to use
// to indicate a hard (non-reflowed) return.
var HARD_RETURN = '\r',
HARD_RETURN_RE = new RegExp(HARD_RETURN),
HARD_RETURN_GFM_RE = new RegExp(HARD_RETURN + '|<br />');
var defaultOptions = {
code: chalk.yellow,
blockquote: chalk.gray.italic,
html: chalk.gray,
heading: chalk.green.bold,
firstHeading: chalk.magenta.underline.bold,
hr: chalk.reset,
listitem: chalk.reset,
list: list,
table: chalk.reset,
paragraph: chalk.reset,
strong: chalk.bold,
em: chalk.italic,
codespan: chalk.yellow,
del: chalk.dim.gray.strikethrough,
link: chalk.blue,
href: chalk.blue.underline,
text: identity,
unescape: true,
emoji: true,
width: 80,
showSectionPrefix: true,
reflowText: false,
tab: 4,
tableOptions: {}
};
function Renderer(options, highlightOptions) {
this.o = Object.assign({}, defaultOptions, options);
this.tab = sanitizeTab(this.o.tab, defaultOptions.tab);
this.tableSettings = this.o.tableOptions;
this.emoji = this.o.emoji ? insertEmojis : identity;
this.unescape = this.o.unescape ? unescapeEntities : identity;
this.highlightOptions = highlightOptions || {};
this.transform = compose(undoColon, this.unescape, this.emoji);
}
// Compute length of str not including ANSI escape codes.
// See http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
function textLength(str) {
return str.replace(/\u001b\[(?:\d{1,3})(?:;\d{1,3})*m/g, '').length;
}
Renderer.prototype.textLength = textLength;
function fixHardReturn(text, reflow) {
return reflow ? text.replace(HARD_RETURN, /\n/g) : text;
}
Renderer.prototype.text = function (text) {
return this.o.text(text);
};
Renderer.prototype.code = function (code, lang, escaped) {
return section(
indentify(this.tab, highlight(code, lang, this.o, this.highlightOptions))
);
};
Renderer.prototype.blockquote = function (quote) {
return section(this.o.blockquote(indentify(this.tab, quote.trim())));
};
Renderer.prototype.html = function (html) {
return this.o.html(html);
};
Renderer.prototype.heading = function (text, level, raw) {
text = this.transform(text);
var prefix = this.o.showSectionPrefix
? new Array(level + 1).join('#') + ' '
: '';
text = prefix + text;
if (this.o.reflowText) {
text = reflowText(text, this.o.width, this.options.gfm);
}
return section(
level === 1 ? this.o.firstHeading(text) : this.o.heading(text)
);
};
Renderer.prototype.hr = function () {
return section(this.o.hr(hr('-', this.o.reflowText && this.o.width)));
};
Renderer.prototype.list = function (body, ordered) {
body = this.o.list(body, ordered, this.tab);
return section(fixNestedLists(indentLines(this.tab, body), this.tab));
};
Renderer.prototype.listitem = function (text) {
var transform = compose(this.o.listitem, this.transform);
var isNested = text.indexOf('\n') !== -1;
if (isNested) text = text.trim();
// Use BULLET_POINT as a marker for ordered or unordered list item
return '\n' + BULLET_POINT + transform(text);
};
Renderer.prototype.checkbox = function (checked) {
return '[' + (checked ? 'X' : ' ') + '] ';
};
Renderer.prototype.paragraph = function (text) {
var transform = compose(this.o.paragraph, this.transform);
text = transform(text);
if (this.o.reflowText) {
text = reflowText(text, this.o.width, this.options.gfm);
}
return section(text);
};
Renderer.prototype.table = function (header, body) {
var table = new Table(
Object.assign(
{},
{
head: generateTableRow(header)[0]
},
this.tableSettings
)
);
generateTableRow(body, this.transform).forEach(function (row) {
table.push(row);
});
return section(this.o.table(table.toString()));
};
Renderer.prototype.tablerow = function (content) {
return TABLE_ROW_WRAP + content + TABLE_ROW_WRAP + '\n';
};
Renderer.prototype.tablecell = function (content, flags) {
return content + TABLE_CELL_SPLIT;
};
// span level renderer
Renderer.prototype.strong = function (text) {
return this.o.strong(text);
};
Renderer.prototype.em = function (text) {
text = fixHardReturn(text, this.o.reflowText);
return this.o.em(text);
};
Renderer.prototype.codespan = function (text) {
text = fixHardReturn(text, this.o.reflowText);
return this.o.codespan(text.replace(/:/g, COLON_REPLACER));
};
Renderer.prototype.br = function () {
return this.o.reflowText ? HARD_RETURN : '\n';
};
Renderer.prototype.del = function (text) {
return this.o.del(text);
};
Renderer.prototype.link = function (href, title, text) {
if (this.options.sanitize) {
try {
var prot = decodeURIComponent(unescape(href))
.replace(/[^\w:]/g, '')
.toLowerCase();
} catch (e) {
return '';
}
if (prot.indexOf('javascript:') === 0) {
return '';
}
}
var hasText = text && text !== href;
var out = '';
if (supportsHyperlinks.stdout) {
let link = '';
if (text) {
link = this.o.href(this.emoji(text));
} else {
link = this.o.href(href);
}
out = ansiEscapes.link(link, href);
} else {
if (hasText) out += this.emoji(text) + ' (';
out += this.o.href(href);
if (hasText) out += ')';
}
return this.o.link(out);
};
Renderer.prototype.image = function (href, title, text) {
if (typeof this.o.image === 'function') {
return this.o.image(href, title, text);
}
var out = '![' + text;
if (title) out += ' ' + title;
return out + '](' + href + ')\n';
};
export default Renderer;
export function markedTerminal(options, highlightOptions) {
const r = new Renderer(options, highlightOptions);
const funcs = [
'text',
'code',
'blockquote',
'html',
'heading',
'hr',
'list',
'listitem',
'checkbox',
'paragraph',
'table',
'tablerow',
'tablecell',
'strong',
'em',
'codespan',
'br',
'del',
'link',
'image'
];
return funcs.reduce(
(extension, func) => {
extension.renderer[func] = function (...args) {
r.options = this.options;
return r[func](...args);
};
return extension;
},
{ renderer: {} }
);
}
// Munge \n's and spaces in "text" so that the number of
// characters between \n's is less than or equal to "width".
function reflowText(text, width, gfm) {
// Hard break was inserted by Renderer.prototype.br or is
// <br /> when gfm is true
var splitRe = gfm ? HARD_RETURN_GFM_RE : HARD_RETURN_RE,
sections = text.split(splitRe),
reflowed = [];
sections.forEach(function (section) {
// Split the section by escape codes so that we can
// deal with them separately.
var fragments = section.split(/(\u001b\[(?:\d{1,3})(?:;\d{1,3})*m)/g);
var column = 0;
var currentLine = '';
var lastWasEscapeChar = false;
while (fragments.length) {
var fragment = fragments[0];
if (fragment === '') {
fragments.splice(0, 1);
lastWasEscapeChar = false;
continue;
}
// This is an escape code - leave it whole and
// move to the next fragment.
if (!textLength(fragment)) {
currentLine += fragment;
fragments.splice(0, 1);
lastWasEscapeChar = true;
continue;
}
var words = fragment.split(/[ \t\n]+/);
for (var i = 0; i < words.length; i++) {
var word = words[i];
var addSpace = column != 0;
if (lastWasEscapeChar) addSpace = false;
// If adding the new word overflows the required width
if (column + word.length + addSpace > width) {
if (word.length <= width) {
// If the new word is smaller than the required width
// just add it at the beginning of a new line
reflowed.push(currentLine);
currentLine = word;
column = word.length;
} else {
// If the new word is longer than the required width
// split this word into smaller parts.
var w = word.substr(0, width - column - addSpace);
if (addSpace) currentLine += ' ';
currentLine += w;
reflowed.push(currentLine);
currentLine = '';
column = 0;
word = word.substr(w.length);
while (word.length) {
var w = word.substr(0, width);
if (!w.length) break;
if (w.length < width) {
currentLine = w;
column = w.length;
break;
} else {
reflowed.push(w);
word = word.substr(width);
}
}
}
} else {
if (addSpace) {
currentLine += ' ';
column++;
}
currentLine += word;
column += word.length;
}
lastWasEscapeChar = false;
}
fragments.splice(0, 1);
}
if (textLength(currentLine)) reflowed.push(currentLine);
});
return reflowed.join('\n');
}
function indentLines(indent, text) {
return text.replace(/(^|\n)(.+)/g, '$1' + indent + '$2');
}
function indentify(indent, text) {
if (!text) return text;
return indent + text.split('\n').join('\n' + indent);
}
var BULLET_POINT_REGEX = '\\*';
var NUMBERED_POINT_REGEX = '\\d+\\.';
var POINT_REGEX =
'(?:' + [BULLET_POINT_REGEX, NUMBERED_POINT_REGEX].join('|') + ')';
// Prevents nested lists from joining their parent list's last line
function fixNestedLists(body, indent) {
var regex = new RegExp(
'' +
'(\\S(?: | )?)' + // Last char of current point, plus one or two spaces
// to allow trailing spaces
'((?:' +
indent +
')+)' + // Indentation of sub point
'(' +
POINT_REGEX +
'(?:.*)+)$',
'gm'
); // Body of subpoint
return body.replace(regex, '$1\n' + indent + '$2$3');
}
var isPointedLine = function (line, indent) {
return line.match('^(?:' + indent + ')*' + POINT_REGEX);
};
function toSpaces(str) {
return ' '.repeat(str.length);
}
var BULLET_POINT = '* ';
function bulletPointLine(indent, line) {
return isPointedLine(line, indent) ? line : toSpaces(BULLET_POINT) + line;
}
function bulletPointLines(lines, indent) {
var transform = bulletPointLine.bind(null, indent);
return lines.split('\n').filter(identity).map(transform).join('\n');
}
var numberedPoint = function (n) {
return n + '. ';
};
function numberedLine(indent, line, num) {
return isPointedLine(line, indent)
? {
num: num + 1,
line: line.replace(BULLET_POINT, numberedPoint(num + 1))
}
: {
num: num,
line: toSpaces(numberedPoint(num)) + line
};
}
function numberedLines(lines, indent) {
var transform = numberedLine.bind(null, indent);
let num = 0;
return lines
.split('\n')
.filter(identity)
.map((line) => {
const numbered = transform(line, num);
num = numbered.num;
return numbered.line;
})
.join('\n');
}
function list(body, ordered, indent) {
body = body.trim();
body = ordered ? numberedLines(body, indent) : bulletPointLines(body, indent);
return body;
}
function section(text) {
return text + '\n\n';
}
function highlight(code, lang, opts, hightlightOpts) {
if (chalk.level === 0) return code;
var style = opts.code;
code = fixHardReturn(code, opts.reflowText);
if (lang !== 'javascript' && lang !== 'js') {
return style(code);
}
try {
return cardinal.highlight(code, hightlightOpts);
} catch (e) {
return style(code);
}
}
function insertEmojis(text) {
return text.replace(/:([A-Za-z0-9_\-\+]+?):/g, function (emojiString) {
var emojiSign = emoji.get(emojiString);
if (!emojiSign) return emojiString;
return emojiSign + ' ';
});
}
function hr(inputHrStr, length) {
length = length || process.stdout.columns;
return new Array(length).join(inputHrStr);
}
function undoColon(str) {
return str.replace(COLON_REPLACER_REGEXP, ':');
}
function generateTableRow(text, escape) {
if (!text) return [];
escape = escape || identity;
var lines = escape(text).split('\n');
var data = [];
lines.forEach(function (line) {
if (!line) return;
var parsed = line
.replace(TABLE_ROW_WRAP_REGEXP, '')
.split(TABLE_CELL_SPLIT);
data.push(parsed.splice(0, parsed.length - 1));
});
return data;
}
function escapeRegExp(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
}
function unescapeEntities(html) {
return html
.replace(/&amp;/g, '&')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'");
}
function identity(str) {
return str;
}
function compose() {
var funcs = arguments;
return function () {
var args = arguments;
for (var i = funcs.length; i-- > 0; ) {
args = [funcs[i].apply(this, args)];
}
return args[0];
};
}
function isAllowedTabString(string) {
return TAB_ALLOWED_CHARACTERS.some(function (char) {
return string.match('^(' + char + ')+$');
});
}
function sanitizeTab(tab, fallbackTab) {
if (typeof tab === 'number') {
return new Array(tab + 1).join(' ');
} else if (typeof tab === 'string' && isAllowedTabString(tab)) {
return tab;
} else {
return new Array(fallbackTab + 1).join(' ');
}
}