diff --git a/Makefile b/Makefile index 9a5bbf2f79c497..7dd885f02d0791 100644 --- a/Makefile +++ b/Makefile @@ -284,7 +284,7 @@ out/doc/api/%.json: doc/api/%.md [ -x $(NODE) ] && $(NODE) $(gen-json) || node $(gen-json) # check if ./node is actually set, else use user pre-installed binary -gen-html = tools/doc/generate.js --node-version=$(FULLVERSION) --format=html --template=doc/template.html $< > $@ +gen-html = tools/doc/generate.js --node-version=$(FULLVERSION) --format=html --template=doc/template.html $< out/doc/api/%.html: doc/api/%.md [ -x $(NODE) ] && $(NODE) $(gen-html) || node $(gen-html) diff --git a/doc/guides/_toc.md b/doc/guides/_toc.md new file mode 100644 index 00000000000000..3c0c22ede141fd --- /dev/null +++ b/doc/guides/_toc.md @@ -0,0 +1,11 @@ +* [About these Docs](documentation.html) +* [Usage & Example](synopsis.html) + +
+ +* [Building Node With Ninja](building-node-with-ninja.html) + + + +* [GitHub Repo & Issue Tracker](https://github.com/nodejs/node) +* [Mailing List](http://groups.google.com/group/nodejs) diff --git a/test/doctool/test-doctool-html.js b/test/doctool/test-doctool-html.js index a8476b7234c9fa..75360362dcf144 100644 --- a/test/doctool/test-doctool-html.js +++ b/test/doctool/test-doctool-html.js @@ -5,7 +5,10 @@ const assert = require('assert'); const fs = require('fs'); const path = require('path'); -const html = require('../../tools/doc/html.js'); +common.globalCheck = false; + +const processIncludes = require('../../tools/doc/preprocess.js'); +const html = require('../../tools/doc/html.js').toHTML; // Test data is a list of objects with two properties. // The file property is the file path. @@ -53,22 +56,43 @@ const testData = [ 'Describe Something
in more detail here. ' +
'
Look here!
' + + '' + + '' + + 'I exist and am being linked to.
' + + '' + }, ]; testData.forEach(function(item) { // Normalize expected data by stripping whitespace const expected = item.html.replace(/\s/g, ''); - fs.readFile(item.file, 'utf8', common.mustCall(function(err, input) { + fs.readFile(item.file, 'utf8', common.mustCall((err, input) => { assert.ifError(err); - html(input, 'foo', 'doc/template.html', - common.mustCall(function(err, output) { - assert.ifError(err); + processIncludes(item.file, input, common.mustCall((err, preprocessed) => { + assert.ifError(err); + + html( + { + input: preprocessed, + filename: 'foo', + template: 'doc/template.html', + nodeVersion: process.version, + }, + common.mustCall((err, output) => { + assert.ifError(err); - const actual = output.replace(/\s/g, ''); - // Assert that the input stripped of all whitespace contains the - // expected list - assert.notEqual(actual.indexOf(expected), -1); - })); + const actual = output.replace(/\s/g, ''); + // Assert that the input stripped of all whitespace contains the + // expected list + assert.notEqual(actual.indexOf(expected), -1); + })); + })); })); }); diff --git a/tools/doc/generate.js b/tools/doc/generate.js index 7df987e1cf78f9..29bb36442d7572 100644 --- a/tools/doc/generate.js +++ b/tools/doc/generate.js @@ -2,6 +2,8 @@ const processIncludes = require('./preprocess.js'); const fs = require('fs'); +const path = require('path'); +const toHTML = require('./html.js').toHTML; // parse the args. // Don't use nopt or whatever for this. It's simple enough. @@ -48,11 +50,15 @@ function next(er, input) { break; case 'html': - require('./html.js')(input, inputFile, template, nodeVersion, - function(er, html) { - if (er) throw er; - console.log(html); + toHTML(input, inputFile, template, nodeVersion, (er, html) => { + if (er) throw er; + const filename = `./out/doc/api/${path.basename(inputFile, 'md')}html`; + fs.writeFile(filename, html, (err) => { + if (err) + console.log(err); + console.log('generated:', filename); }); + }); break; default: diff --git a/tools/doc/html.js b/tools/doc/html.js index ef7d78d5b70ab3..048fa65be9ec63 100644 --- a/tools/doc/html.js +++ b/tools/doc/html.js @@ -1,312 +1,2 @@ '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; - -// customized heading without id attribute -var renderer = new marked.Renderer(); -renderer.heading = function(text, level) { - return '$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; - lexed.forEach(function(tok) { - if (tok.type !== 'heading') return; - if (tok.depth - depth > 1) { - return cb(new Error('Inappropriate heading level\n' + - JSON.stringify(tok))); - } - - depth = tok.depth; - var id = getId(filename + '_' + 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; -} +module.exports.toHTML = require('./lib/toHTML'); diff --git a/tools/doc/lib/buildToc.js b/tools/doc/lib/buildToc.js new file mode 100644 index 00000000000000..2a62eff7368bd4 --- /dev/null +++ b/tools/doc/lib/buildToc.js @@ -0,0 +1,41 @@ +'use strict'; +const marked = require('marked'); +const getId = require('./getId.js'); + +module.exports = 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); +}; diff --git a/tools/doc/lib/getId.js b/tools/doc/lib/getId.js new file mode 100644 index 00000000000000..0642262a8188dc --- /dev/null +++ b/tools/doc/lib/getId.js @@ -0,0 +1,14 @@ +'use strict'; +var idCounters = {}; +module.exports = 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; +}; diff --git a/tools/doc/lib/getSection.js b/tools/doc/lib/getSection.js new file mode 100644 index 00000000000000..0754766c6af47b --- /dev/null +++ b/tools/doc/lib/getSection.js @@ -0,0 +1,9 @@ +'use strict'; +// section is just the first heading +module.exports = 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 ''; +}; diff --git a/tools/doc/lib/linkJsTypeDocs.js b/tools/doc/lib/linkJsTypeDocs.js new file mode 100644 index 00000000000000..7066333c9006ef --- /dev/null +++ b/tools/doc/lib/linkJsTypeDocs.js @@ -0,0 +1,22 @@ +'use strict'; +const typeParser = require('../type-parser.js'); + +module.exports = 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('`'); +}; diff --git a/tools/doc/lib/linkManPages.js b/tools/doc/lib/linkManPages.js new file mode 100644 index 00000000000000..b221f7fa3a8a02 --- /dev/null +++ b/tools/doc/lib/linkManPages.js @@ -0,0 +1,20 @@ +'use strict'; +// 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)' +module.exports = 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 + ''; + } + }); +}; diff --git a/tools/doc/lib/loadGtoc.js b/tools/doc/lib/loadGtoc.js new file mode 100644 index 00000000000000..44aae27195c73f --- /dev/null +++ b/tools/doc/lib/loadGtoc.js @@ -0,0 +1,34 @@ +'use strict'; +const path = require('path'); +const fs = require('fs'); +const marked = require('marked'); +const preprocess = require('../preprocess.js'); +const toID = require('./toID.js'); +// TODO(chrisdickinson): never stop vomitting / fix this. +var gtocPath = path.resolve(path.join( + __dirname, + '..', + '..', + '..', + 'doc', + 'api', + '_toc.md' +)); +global.global.gtocLoading = null; +global.gtocData = null; + + +exports.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(/$1 $2$3' + ); + return text; +}; diff --git a/tools/doc/lib/parseLists.js b/tools/doc/lib/parseLists.js new file mode 100644 index 00000000000000..f737443afef2d8 --- /dev/null +++ b/tools/doc/lib/parseLists.js @@ -0,0 +1,63 @@ +'use strict'; +const common = require('../common.js'); +const parseAPIHeader = require('./parseAPIHeader.js'); +const parseYAML = require('./parseYAML.js'); +// just update the list item text in-place. +// lists that come right after a heading are what we're after. +module.exports = function parseLists(input) { + var state = null; + var depth = 0; + var output = []; + output.links = input.links; + input.forEach(function(tok) { + if (tok.type === 'code' && tok.text.match(/Stability:.*/g)) { + tok.text = parseAPIHeader(tok.text); + output.push({ type: 'html', text: tok.text }); + return; + } + if (state === null || + (state === 'AFTERHEADING' && tok.type === 'heading')) { + if (tok.type === 'heading') { + state = 'AFTERHEADING'; + } + output.push(tok); + return; + } + if (state === 'AFTERHEADING') { + if (tok.type === 'list_start') { + state = 'LIST'; + if (depth === 0) { + output.push({ type: 'html', text: '