'use strict';
const common = require('./common.js');
const fs = require('fs');
const marked = require('marked');
const path = require('path');
const preprocess = require('./preprocess.js');
const typeParser = require('./type-parser.js');
module.exports = toHTML;
const STABILITY_TEXT_REG_EXP = /(.*:)\s*(\d)([\s\S]*)/;
// customized heading without id attribute
var renderer = new marked.Renderer();
renderer.heading = function(text, level) {
return '' + text + '\n';
};
marked.setOptions({
renderer: renderer
});
// TODO(chrisdickinson): never stop vomitting / fix this.
var gtocPath = path.resolve(path.join(
__dirname,
'..',
'..',
'doc',
'api',
'_toc.md'
));
var gtocLoading = null;
var gtocData = null;
/**
* opts: input, filename, template, nodeVersion.
*/
function toHTML(opts, cb) {
var template = opts.template;
var nodeVersion = opts.nodeVersion || process.version;
if (gtocData) {
return onGtocLoaded();
}
if (gtocLoading === null) {
gtocLoading = [onGtocLoaded];
return loadGtoc(function(err, data) {
if (err) throw err;
gtocData = data;
gtocLoading.forEach(function(xs) {
xs();
});
});
}
if (gtocLoading) {
return gtocLoading.push(onGtocLoaded);
}
function onGtocLoaded() {
var lexed = marked.lexer(opts.input);
fs.readFile(template, 'utf8', function(er, template) {
if (er) return cb(er);
render({
lexed: lexed,
filename: opts.filename,
template: template,
nodeVersion: nodeVersion,
}, cb);
});
}
}
function loadGtoc(cb) {
fs.readFile(gtocPath, 'utf8', function(err, data) {
if (err) return cb(err);
preprocess(gtocPath, data, function(err, data) {
if (err) return cb(err);
data = marked(data).replace(/' });
}
depth++;
output.push(tok);
return;
}
if (tok.type === 'html' && common.isYAMLBlock(tok.text)) {
tok.text = parseYAML(tok.text);
}
state = null;
output.push(tok);
return;
}
if (state === 'LIST') {
if (tok.type === 'list_start') {
depth++;
output.push(tok);
return;
}
if (tok.type === 'list_end') {
depth--;
output.push(tok);
if (depth === 0) {
state = null;
output.push({ type: 'html', text: '' });
}
return;
}
}
output.push(tok);
});
return output;
}
function parseYAML(text) {
const meta = common.extractAndParseYAML(text);
const html = [''];
if (meta.added) {
html.push(`Added in: ${meta.added.join(', ')}`);
}
if (meta.deprecated) {
html.push(`Deprecated since: ${meta.deprecated.join(', ')} `);
}
html.push('
');
return html.join('\n');
}
// Syscalls which appear in the docs, but which only exist in BSD / OSX
var BSD_ONLY_SYSCALLS = new Set(['lchmod']);
// Handle references to man pages, eg "open(2)" or "lchmod(2)"
// Returns modified text, with such refs replace with HTML links, for example
// 'open(2)'
function linkManPages(text) {
return text.replace(/ ([a-z]+)\((\d)\)/gm, function(match, name, number) {
// name consists of lowercase letters, number is a single digit
var displayAs = name + '(' + number + ')';
if (BSD_ONLY_SYSCALLS.has(name)) {
return ' ' + displayAs + '';
} else {
return ' ' + displayAs + '';
}
});
}
function linkJsTypeDocs(text) {
var parts = text.split('`');
var i;
var typeMatches;
// Handle types, for example the source Markdown might say
// "This argument should be a {Number} or {String}"
for (i = 0; i < parts.length; i += 2) {
typeMatches = parts[i].match(/\{([^\}]+)\}/g);
if (typeMatches) {
typeMatches.forEach(function(typeMatch) {
parts[i] = parts[i].replace(typeMatch, typeParser.toLink(typeMatch));
});
}
}
//XXX maybe put more stuff here?
return parts.join('`');
}
function parseAPIHeader(text) {
text = text.replace(
STABILITY_TEXT_REG_EXP,
'
$1 $2$3
'
);
return text;
}
// section is just the first heading
function getSection(lexed) {
for (var i = 0, l = lexed.length; i < l; i++) {
var tok = lexed[i];
if (tok.type === 'heading') return tok.text;
}
return '';
}
function buildToc(lexed, filename, cb) {
var toc = [];
var depth = 0;
const startIncludeRefRE = /^\s*\s*$/;
const endIncludeRefRE = /^\s*\s*$/;
const realFilenames = [filename];
lexed.forEach(function(tok) {
// Keep track of the current filename along @include directives.
if (tok.type === 'html') {
let match;
if ((match = tok.text.match(startIncludeRefRE)) !== null)
realFilenames.unshift(match[1]);
else if (tok.text.match(endIncludeRefRE))
realFilenames.shift();
}
if (tok.type !== 'heading') return;
if (tok.depth - depth > 1) {
return cb(new Error('Inappropriate heading level\n' +
JSON.stringify(tok)));
}
depth = tok.depth;
const realFilename = path.basename(realFilenames[0], '.md');
const id = getId(realFilename + '_' + tok.text.trim());
toc.push(new Array((depth - 1) * 2 + 1).join(' ') +
'* ' +
'' + tok.text + '');
tok.text += '#';
});
toc = marked.parse(toc.join('\n'));
cb(null, toc);
}
var idCounters = {};
function getId(text) {
text = text.toLowerCase();
text = text.replace(/[^a-z0-9]+/g, '_');
text = text.replace(/^_+|_+$/, '');
text = text.replace(/^([^a-z])/, '_$1');
if (idCounters.hasOwnProperty(text)) {
text += '_' + (++idCounters[text]);
} else {
idCounters[text] = 0;
}
return text;
}