Skip to content

Commit

Permalink
docs: tidy up generation script
Browse files Browse the repository at this point in the history
Conform to the norm.
  • Loading branch information
ethomson committed Dec 1, 2020
1 parent ea6e57d commit 40fc34e
Showing 1 changed file with 172 additions and 181 deletions.
353 changes: 172 additions & 181 deletions docs/dockhand.js
Original file line number Diff line number Diff line change
@@ -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 `<div id="_content">${content}</div>`
case 'path':
return childPath
case 'url_path':
return encodeURI(childPath)

case 'toc':
return '<div id="_table_of_contents"></div>'

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 `<div id="_content">${content}</div>`;
case 'path':
return childPath;
case 'url_path':
return encodeURI(childPath);

case 'toc':
return '<div id="_table_of_contents"></div>';

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
}

0 comments on commit 40fc34e

Please sign in to comment.