diff --git a/package.json b/package.json index 096b55a0569a4ce24467970318e290ffdd2116d8..c20bebfb9fab1116be01b4c09b23a2ab50fa7d5e 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "postinstall": "node ./scripts/postinstall.js", "start": "cross-env NODE_ENV=development webpack-dev-server", "build": "rimraf ./public && cross-env NODE_ENV=production webpack --mode=production", + "doc": "node ./scripts/doc/build.js", "prettier": "prettier --write '*.js' 'src/**/*.js'", "lint:js": "eslint '*.js' 'scripts/**/*.js' 'src/**/*.js'", "lint:html": "node ./scripts/lint-html.js 'src/**/*.html'", diff --git a/scripts/doc/build.js b/scripts/doc/build.js new file mode 100644 index 0000000000000000000000000000000000000000..88822f37c95ee79ec5db4421265e9667d8e69359 --- /dev/null +++ b/scripts/doc/build.js @@ -0,0 +1,60 @@ +const fs = require("fs"); +const { promisify } = require("util"); +const readFile = promisify(fs.readFile); +const writeFile = promisify(fs.writeFile); +const shell = require("shelljs"); +const flatten = require("lodash/flatten"); +const indexTemplate = require("./index.js"); + +async function extractDocs(file) { + const contents = (await readFile(file)).toString(); + // Find all the doc strings in the file. + const matches = contents.match(/\/\*\*.+?\*\//gs); + if (matches) { + return matches.map(match => { + return { doc: match, file }; + }); + } else { + return null; + } +} + +function parseDocs(doc) { + const _doc = doc; + // Capture the description and tags in the doc string + const matches = _doc.doc.match(/\/\*\*([^@]+)(.+)\*\//s); + // Trim whitespace and asterisks from a line + const trimLine = line => line.trim().replace(/^\*\s*|\s*\*$/g, ""); + + _doc.doc = { + desc: matches[1] + .split("\n") + .map(trimLine) + .filter(x => x) + .join(" "), + tags: matches[2] + .split(/[\r\n]/) + .map(trimLine) + .filter(x => x.startsWith("@")) + .reduce((a, x) => { + const tag = x.split(" "); + a[tag[0].substring(1)] = tag.slice(1).join(); + return a; + }, {}) + }; + return _doc; +} + +function aframeDocs(doc) { + const keys = Object.keys(doc.doc.tags); + return keys.includes("component") || keys.includes("system"); +} + +(async function() { + const files = shell.ls("src/components/*.js", "src/systems/*.js"); + const parsedDocs = flatten(await Promise.all(files.map(extractDocs))) + .filter(x => x) + .map(parseDocs) + .filter(aframeDocs); + writeFile("doc/index.html", indexTemplate(parsedDocs)); +})(); diff --git a/scripts/doc/index.js b/scripts/doc/index.js new file mode 100644 index 0000000000000000000000000000000000000000..5ab6c68d9588d5ad147789176fcc024e745537ec --- /dev/null +++ b/scripts/doc/index.js @@ -0,0 +1,87 @@ +module.exports = function(docs) { + const systems = docs.filter(doc => Object.keys(doc.doc.tags).includes("system")); + const components = docs.filter(doc => Object.keys(doc.doc.tags).includes("component")).reduce((acc, doc) => { + const namespace = doc.doc.tags.namespace || "misc"; + if (!acc[namespace]) { + acc[namespace] = []; + } + acc[namespace].push(doc); + return acc; + }, {}); + return ` + <html> + <head> + <style> + body { font-family: sans-serif; } + article { margin-left: 1em; } + span { font-size: 70%; color: grey; } + </style> + </head> + <body> + <h1>Docs</h1> + + <ul> + <li>Systems + <ul> + ${systems + .map(system => { + return `<li><a href="#systems/${system.doc.tags.system}">${system.doc.tags.system}</a></li>`; + }) + .join("")} + </ul> + </li> + + <li>Components + <ul> + ${Object.entries(components) + .sort((a, b) => a[0] > b[0]) + .map(([namespace, components]) => { + return `<li><a href="#components/${namespace}">${namespace}</a><ul> + ${components + .map( + component => `<li> + <a href="#components/${namespace}/${component.doc.tags.component}"> + ${component.doc.tags.component} + </a> + </li>` + ) + .join("")} + </ul></li>`; + }) + .join("")} + </ul> + </li> + </ul> + + <h2>Systems</h2> + ${systems + .map(system => { + return `<article> + <a name="systems/${system.doc.tags.system}"></a><h4>${system.doc.tags.system}</h4> + <p>${system.doc.desc}</p> + <span>${system.file}</span> + </article>`; + }) + .join("")} + + <h2>Components</h2> + ${Object.entries(components) + .map(([namespace, components]) => { + return `<a name="components/${namespace}"></a><h3>${namespace}</h3> + ${components + .map( + component => `<article> + <a name="components/${namespace}/${component.doc.tags.component}"></a> + <h4>${component.doc.tags.component}</h4> + <p>${component.doc.desc}</p> + <span>${component.file}</span> + </article>` + ) + .join("")} + `; + }) + .join("")} + </body> + </html> + `; +};