diff --git a/docs/dockhand.js b/docs/dockhand.js index 41d01b1cff685..b66ec609d4fde 100644 --- a/docs/dockhand.js +++ b/docs/dockhand.js @@ -1,227 +1,218 @@ #!/usr/bin/env node -const path = require('path'); -const fs = require('fs'); -const yaml = require('yaml'); -const cmark = require('cmark-gfm'); -const mkdirp = require('mkdirp'); -const jsdom = require('jsdom'); +const path = require('path') +const fs = require('fs') +const yaml = require('yaml') +const cmark = require('cmark-gfm') +const mkdirp = require('mkdirp') +const jsdom = require('jsdom') const npm = require('../lib/npm.js') -const config = require('./config.json'); +const config = require('./config.json') -const docsRoot = __dirname; -const inputRoot = path.join(docsRoot, 'content'); -const outputRoot = path.join(docsRoot, 'output'); +const docsRoot = __dirname +const inputRoot = path.join(docsRoot, 'content') +const outputRoot = path.join(docsRoot, 'output') -const template = fs.readFileSync('template.html').toString(); +const template = fs.readFileSync('template.html').toString() -walk(inputRoot); +walk(inputRoot) -function walk(root, dirRelative) { - const dirPath = dirRelative ? path.join(root, dirRelative) : root; +function walk (root, dirRelative) { + const dirPath = dirRelative ? path.join(root, dirRelative) : root - fs.readdirSync(dirPath).forEach((childFilename) => { - const childRelative = dirRelative ? path.join(dirRelative, childFilename) : childFilename; - const childPath = path.join(root, childRelative); + fs.readdirSync(dirPath).forEach((childFilename) => { + const childRelative = dirRelative ? path.join(dirRelative, childFilename) : childFilename + const childPath = path.join(root, childRelative) - if (fs.lstatSync(childPath).isDirectory()) { - walk(root, childRelative); - } - else { - translate(childRelative); - } - }); + if (fs.lstatSync(childPath).isDirectory()) { + walk(root, childRelative) + } else { + translate(childRelative) + } + }) } -function translate(childPath) { - const inputPath = path.join(inputRoot, childPath); - - if (!inputPath.match(/\.md$/)) { - console.log(`warning: unknown file type ${inputPath}, ignored`); - return; +function translate (childPath) { + const inputPath = path.join(inputRoot, childPath) + + if (!inputPath.match(/\.md$/)) { + console.log(`warning: unknown file type ${inputPath}, ignored`) + return + } + + const outputPath = path.join(outputRoot, childPath.replace(/\.md$/, '.html')) + + let md = fs.readFileSync(inputPath).toString() + let frontmatter = { } + + // Take the leading frontmatter out of the markdown + md = md.replace(/^---\n([\s\S]+)\n---\n/, (header, fm) => { + frontmatter = yaml.parse(fm, 'utf8') + return '' + }) + + // Replace any tokens in the source + md = md.replace(/@VERSION@/, npm.version) + + // Render the markdown into an HTML snippet using a GFM renderer. + const content = cmark.renderHtmlSync(md, { + 'smart': true, + 'githubPreLang': true, + 'strikethroughDoubleTilde': true, + 'unsafe': false, + extensions: { + 'table': true, + 'strikethrough': true, + 'tagfilter': true, + 'autolink': true } + }) + + // Inject this data into the template, using a mustache-like + // replacement scheme. + const html = template.replace(/\{\{\s*([\w.]+)\s*\}\}/g, (token, key) => { + switch (key) { + case 'content': + return `
${content}
` + case 'path': + return childPath + case 'url_path': + return encodeURI(childPath) + + case 'toc': + return '
' + + case 'title': + case 'section': + case 'description': + return frontmatter[key] + + case 'config.github_repo': + case 'config.github_branch': + case 'config.github_path': + return config[key.replace(/^config\./, '')] + + default: + console.log(`warning: unknown token '${token}' in ${inputPath}`) + return '' + } + }) - const outputPath = path.join(outputRoot, childPath.replace(/\.md$/, '.html')); - - let md = fs.readFileSync(inputPath).toString(); - let frontmatter = { }; - - // Take the leading frontmatter out of the markdown - md = md.replace(/^---\n([\s\S]+)\n---\n/, (header, fm) => { - frontmatter = yaml.parse(fm, 'utf8'); - return ''; - }); - - // Replace any tokens in the source - md = md.replace(/@VERSION@/, npm.version); - - // Render the markdown into an HTML snippet using a GFM renderer. - const content = cmark.renderHtmlSync(md, { - 'smart': true, - 'githubPreLang': true, - 'strikethroughDoubleTilde': true, - 'unsafe': false, - extensions: { - 'table': true, - 'strikethrough': true, - 'tagfilter': true, - 'autolink': true - } - }); - - // Inject this data into the template, using a mustache-like - // replacement scheme. - const html = template.replace(/\{\{\s*([\w\.]+)\s*\}\}/g, (token, key) => { - switch (key) { - case 'content': - return `
${content}
`; - case 'path': - return childPath; - case 'url_path': - return encodeURI(childPath); - - case 'toc': - return '
'; - - case 'title': - case 'section': - case 'description': - return frontmatter[key]; - - case 'config.github_repo': - case 'config.github_branch': - case 'config.github_path': - return config[key.replace(/^config\./, '')]; - - default: - console.log(`warning: unknown token '${token}' in ${inputPath}`); - return ''; - } - console.log(key); - return key; - }); - - const dom = new jsdom.JSDOM(html); - const document = dom.window.document; - - // Rewrite relative URLs in links and image sources to be relative to - // this file; this is for supporting `file://` links. HTML pages need - // suffix appended. - const links = [ - { tag: 'a', attr: 'href', suffix: '.html' }, - { tag: 'img', attr: 'src' } - ]; - - for (let linktype of links) { - for (let tag of document.querySelectorAll(linktype.tag)) { - let url = tag.getAttribute(linktype.attr); + const dom = new jsdom.JSDOM(html) + const document = dom.window.document - if (url.startsWith('/')) { - const childDepth = childPath.split('/').length - 1; - const prefix = childDepth > 0 ? '../'.repeat(childDepth) : './'; + // Rewrite relative URLs in links and image sources to be relative to + // this file; this is for supporting `file://` links. HTML pages need + // suffix appended. + const links = [ + { tag: 'a', attr: 'href', suffix: '.html' }, + { tag: 'img', attr: 'src' } + ] - url = url.replace(/^\//, prefix); + for (let linktype of links) { + for (let tag of document.querySelectorAll(linktype.tag)) { + let url = tag.getAttribute(linktype.attr) - if (linktype.suffix) { - url += linktype.suffix; - } + if (url.startsWith('/')) { + const childDepth = childPath.split('/').length - 1 + const prefix = childDepth > 0 ? '../'.repeat(childDepth) : './' - tag.setAttribute(linktype.attr, url); - } - } - } + url = url.replace(/^\//, prefix) - // Give headers a unique id so that they can be linked within the doc - const headerIds = [ ]; - for (let header of document.querySelectorAll('h1, h2, h3, h4, h5, h6')) { - if (header.getAttribute('id')) { - headerIds.push(header.getAttribute('id')); - continue; + if (linktype.suffix) { + url += linktype.suffix } - const headerText = header.textContent.replace(/[A-Z]/g, x => x.toLowerCase()).replace(/ /g, '-').replace(/[^a-z0-9\-]/g, ''); - let headerId = headerText; - let headerIncrement = 1; + tag.setAttribute(linktype.attr, url) + } + } + } + + // Give headers a unique id so that they can be linked within the doc + const headerIds = [ ] + for (let header of document.querySelectorAll('h1, h2, h3, h4, h5, h6')) { + if (header.getAttribute('id')) { + headerIds.push(header.getAttribute('id')) + continue + } - while (document.getElementById(headerId) !== null) { - headerId = headerText + (++headerIncrement); - } + const headerText = header.textContent.replace(/[A-Z]/g, x => x.toLowerCase()).replace(/ /g, '-').replace(/[^a-z0-9-]/g, '') + let headerId = headerText + let headerIncrement = 1 - headerIds.push(headerId); - header.setAttribute('id', headerId); + while (document.getElementById(headerId) !== null) { + headerId = headerText + (++headerIncrement) } - // Walk the dom and build a table of contents - const toc = document.getElementById('_table_of_contents'); + headerIds.push(headerId) + header.setAttribute('id', headerId) + } - if (toc) { - toc.appendChild(generateTableOfContents(document)); - } + // Walk the dom and build a table of contents + const toc = document.getElementById('_table_of_contents') - // Write the final output - const output = dom.serialize(); + if (toc) { + toc.appendChild(generateTableOfContents(document)) + } - mkdirp.sync(path.dirname(outputPath)); - fs.writeFileSync(outputPath, output); -} + // Write the final output + const output = dom.serialize() -function generateTableOfContents(document) { - const headers = [ ]; - walkHeaders(document.getElementById('_content'), headers); + mkdirp.sync(path.dirname(outputPath)) + fs.writeFileSync(outputPath, output) +} - let parent = null; +function generateTableOfContents (document) { + const headers = [ ] + walkHeaders(document.getElementById('_content'), headers) - // The nesting depth of headers are not necessarily the header level. - // (eg, h1 > h3 > h5 is a depth of three even though there's an h5.) - const hierarchy = [ ]; - for (let header of headers) { - const level = headerLevel(header); + // The nesting depth of headers are not necessarily the header level. + // (eg, h1 > h3 > h5 is a depth of three even though there's an h5.) + const hierarchy = [ ] + for (let header of headers) { + const level = headerLevel(header) - while (hierarchy.length && hierarchy[hierarchy.length - 1].headerLevel > level) { - hierarchy.pop(); - } + while (hierarchy.length && hierarchy[hierarchy.length - 1].headerLevel > level) { + hierarchy.pop() + } - if (!hierarchy.length || hierarchy[hierarchy.length - 1].headerLevel < level) { - const newList = document.createElement('ul'); - newList.headerLevel = level; + if (!hierarchy.length || hierarchy[hierarchy.length - 1].headerLevel < level) { + const newList = document.createElement('ul') + newList.headerLevel = level - if (hierarchy.length) { - hierarchy[hierarchy.length - 1].appendChild(newList); - } + if (hierarchy.length) { + hierarchy[hierarchy.length - 1].appendChild(newList) + } - hierarchy.push(newList); - } + hierarchy.push(newList) + } - const element = document.createElement('li'); + const element = document.createElement('li') - const link = document.createElement('a'); - link.setAttribute('href', `#${header.getAttribute('id')}`); - link.innerHTML = header.innerHTML; - element.appendChild(link); + const link = document.createElement('a') + link.setAttribute('href', `#${header.getAttribute('id')}`) + link.innerHTML = header.innerHTML + element.appendChild(link) - const list = hierarchy[hierarchy.length - 1]; - list.appendChild(element); - } + const list = hierarchy[hierarchy.length - 1] + list.appendChild(element) + } - return hierarchy[0]; + return hierarchy[0] } -function walkHeaders(element, headers) { - for (let child of element.childNodes) { - if (headerLevel(child)) { - headers.push(child); - } - - walkHeaders(child, headers); +function walkHeaders (element, headers) { + for (let child of element.childNodes) { + if (headerLevel(child)) { + headers.push(child) } -} -function headerLevel(node) { - const level = node.tagName ? node.tagName.match(/^[Hh]([123456])$/) : null; - return level ? level[1] : 0; + walkHeaders(child, headers) + } } -function debug(str) { - console.log(str); +function headerLevel (node) { + const level = node.tagName ? node.tagName.match(/^[Hh]([123456])$/) : null + return level ? level[1] : 0 }