123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514 |
- 'use strict';
-
- import chalk from 'chalk';
- import Table from 'cli-table3';
- import cardinal from 'cardinal';
- import 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;
-
- // 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(/&/g, '&')
- .replace(/</g, '<')
- .replace(/>/g, '>')
- .replace(/"/g, '"')
- .replace(/'/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(' ');
- }
- }
|