docs: improvements for generated documentation

This commit is contained in:
lowlighter
2022-01-15 13:25:03 -05:00
parent 72c736c5b7
commit 696041eeeb
2 changed files with 108 additions and 112 deletions

View File

@@ -174,6 +174,5 @@ function testcase(name, env, args) {
Object.assign(result.with, {use_mocked_data:"yes", verify:"yes"}) Object.assign(result.with, {use_mocked_data:"yes", verify:"yes"})
} }
console.log(arguments, result)
return result return result
} }

View File

@@ -40,7 +40,7 @@ export default async function metadata({log = true, diff = false} = {}) {
if (!(await fs.promises.lstat(path.join(__plugins, name))).isDirectory()) if (!(await fs.promises.lstat(path.join(__plugins, name))).isDirectory())
continue continue
logger(`metrics/metadata > loading plugin metadata [${name}]`) logger(`metrics/metadata > loading plugin metadata [${name}]`)
Plugins[name] = await metadata.plugin({__plugins, name, logger}) Plugins[name] = await metadata.plugin({__plugins, __templates, name, logger})
} }
//Reorder keys //Reorder keys
const {base, core, ...plugins} = Plugins //eslint-disable-line no-unused-vars const {base, core, ...plugins} = Plugins //eslint-disable-line no-unused-vars
@@ -70,7 +70,7 @@ export default async function metadata({log = true, diff = false} = {}) {
} }
/**Metadata extractor for templates */ /**Metadata extractor for templates */
metadata.plugin = async function({__plugins, name, logger}) { metadata.plugin = async function({__plugins, __templates, name, logger}) {
try { try {
//Load meta descriptor //Load meta descriptor
const raw = `${await fs.promises.readFile(path.join(__plugins, name, "metadata.yml"), "utf-8")}` const raw = `${await fs.promises.readFile(path.join(__plugins, name, "metadata.yml"), "utf-8")}`
@@ -266,7 +266,21 @@ metadata.plugin = async function({__plugins, name, logger}) {
{ {
//Extract demos //Extract demos
const raw = `${await fs.promises.readFile(path.join(__plugins, name, "README.md"), "utf-8")}` const raw = `${await fs.promises.readFile(path.join(__plugins, name, "README.md"), "utf-8")}`
const demo = raw.match(/(?<demo><table>[\s\S]*?<[/]table>)/)?.groups?.demo?.replace(/<[/]?(?:table|tr)>/g, "")?.trim() ?? "<td></td>" const demo = meta.examples ? demos({examples:meta.examples}) : raw.match(/(?<demo><table>[\s\S]*?<[/]table>)/)?.groups?.demo?.replace(/<[/]?(?:table|tr)>/g, "")?.trim() ?? "<td></td>"
//Compatibility
const templates = {}
const compatibility = {}
for (const template of await fs.promises.readdir(__templates)) {
if (!(await fs.promises.lstat(path.join(__templates, template))).isDirectory())
continue
templates[template] = yaml.load(`${await fs.promises.readFile(path.join(__templates, template, "metadata.yml"), "utf-8")}`)
const partials = path.join(__templates, template, "partials")
if ((fs.existsSync(partials)) && ((await fs.promises.lstat(partials)).isDirectory())) {
const supported = [...await fs.promises.readdir(partials)]
compatibility[template] = !!supported.filter(id => id.match(new RegExp(`^${name}(?:[.][\s\S]+)?[.]ejs$`))).length
}
}
//Header table //Header table
const header = [ const header = [
@@ -275,100 +289,81 @@ metadata.plugin = async function({__plugins, name, logger}) {
` <tr><td colspan="2" align="center">${(meta.description ?? "").replaceAll("\n", "<br>")}</td></tr>`, ` <tr><td colspan="2" align="center">${(meta.description ?? "").replaceAll("\n", "<br>")}</td></tr>`,
" <tr>", " <tr>",
' <th rowspan="3">Supported features<br><sub><a href="metadata.yml">→ Full specification</a></sub></th>', ' <th rowspan="3">Supported features<br><sub><a href="metadata.yml">→ Full specification</a></sub></th>',
//` <td>${Object.entries(compatibility).filter(([_, value]) => value).map(([id]) => `<a href="/source/plugins/${id}" title="${plugins[id].name}">${plugins[id].icon}</a>`).join(" ")}${meta.formats?.includes("markdown") ? " <code>✓ embed()</code>" : ""}</td>`, ` <td>${Object.entries(compatibility).filter(([_, value]) => value).map(([id]) => `<a href="/source/templates/${id}"><code>${templates[id].name ?? ""}</code></a>`).join(" ")}</td>`,
" </tr>", " </tr>",
" <tr>", " <tr>",
` <td>${[ ` <td>${[
meta.supports?.includes("user") ? "👤 Users" : "", meta.supports?.includes("user") ? "<code>👤 Users</code>" : "",
meta.supports?.includes("organization") ? "👥 Organizations" : "", meta.supports?.includes("organization") ? "<code>👥 Organizations</code>" : "",
meta.supports?.includes("repository") ? "📓 Repositories" : "" meta.supports?.includes("repository") ? "<code>📓 Repositories</code>" : ""
].filter(v => v).join(", ")}</td>`, ].filter(v => v).join(" ")}</td>`,
" </tr>", " </tr>",
" <tr>", " <tr>",
` <td>${[...(meta.scopes ?? []).map(scope => `🔑 ${scope}`), ...Object.entries(inputs).filter(([_, {type}]) => type === "token").map(([token]) => `<code>🗝 ${token}</code>`)].join(", ")}</td>`, ` <td>${[
...(meta.scopes ?? []).map(scope => `<code>🔑 ${{public_access:"(scopeless)"}[scope] ?? scope}</code>`),
...Object.entries(inputs).filter(([_, {type}]) => type === "token").map(([token]) => `<code>🗝 ${token}</code>`),
...(meta.scopes?.length ? ["read:org", "read:user", "repo"].map(scope => !meta.scopes.includes(scope) ? `<code>${scope} (optional)</code>` : null).filter(v => v) : [])
].join(" ")}</td>`,
" </tr>", " </tr>",
" <tr>", " <tr>",
demos({colspan:2, examples:meta.examples}),
" </tr>", " </tr>",
"</table>" "</table>"
].join("\n") ].join("\n")
//Options table //Options table
let flags = new Set()
const table = [ const table = [
"| Option | Type *(format)* **[default]** *{allowed values}* | Description |", "<table>",
"| ------ | -------------------------------- | ----------- |", " <tr>",
' <td align="center" nowrap="nowrap">Type</i></td><td align="center" nowrap="nowrap">Description</td>',
" </tr>",
Object.entries(inputs).map(([option, {description, type, ...o}]) => { Object.entries(inputs).map(([option, {description, type, ...o}]) => {
let row = [] const cell = []
{ if (o.required)
let cell = [] cell.push("✔️ Required<br>")
if (o.required) { if (type === "token")
cell.push("✔️") cell.push("🔐 Token<br>")
flags.add("required") if (o.inherits)
} cell.push(`⏩ Inherits <code>${o.inherits}</code><br>`)
if (type === "token") { if (o.global)
cell.push("🔐") cell.push("⏭️ Global option<br>")
flags.add("secret") if (o.testing)
} cell.push("🔧 For development")
if (o.inherits) { if (!Object.keys(previous?.inputs ?? {}).includes(option))
cell.push("") cell.push("✨ On <code>master</code>/<code>main</code><br>")
flags.add("inherits") if (o.extras)
} cell.push("🌐 Web instances must configure <code>settings.json</code><br>")
if (o.global) { cell.push(`<b>type:</b> <code>${type}</code>`)
cell.push("⏭️")
flags.add("global")
}
if (o.testing) {
cell.push("🔧")
flags.add("testing")
}
if (!Object.keys(previous?.inputs ?? {}).includes(option)) {
cell.push("✨")
flags.add("beta")
}
if (o.extras) {
cell.push("🧰")
flags.add("extras")
}
cell = cell.map(flag => `<sup>${flag}</sup>`)
cell.unshift(`${"`"}${option}${"`"}`)
row.push(cell.join(" "))
}
{
const cell = [`${"`"}${type}${"`"}`]
if ("format" in o) if ("format" in o)
cell.push(`*(${Array.isArray(o.format) ? o.format[0] : o.format})*`) cell.push(`<i>(${Array.isArray(o.format) ? o.format[0] : o.format})</i>`)
if ("default" in o) { cell.push("<br>")
if ("min" in o)
cell.push(`<i>(${o.min}`)
if (("min" in o)||("max" in o))
cell.push(`${"min" in o ? "" : "<i>("}𝑥${"max" in o ? "" : ")</i>"}`)
if ("max" in o)
cell.push(`${o.max})</i>`)
if (("default" in o)&&(o.default !== "")) {
let text = o.default let text = o.default
if (o.default === ".user.login") if (o.default === ".user.login")
text = "*→ User login*" text = "<code>→ User login</code>"
if (o.default === ".user.twitter") if (o.default === ".user.twitter")
text = "*→ User attached twitter*" text = "<code>→ User attached twitter</code>"
if (o.default === ".user.website") if (o.default === ".user.website")
text = "*→ User attached website*" text = "<code>→ User attached website</code>"
cell.push(`**[${text}]**`) cell.push(`<b>default:</b> ${text}<br>`)
} }
if ("values" in o) if ("values" in o)
cell.push(`*{${o.values.map(value => `"${value}"`).join(", ")}}*`) cell.push(`<b>allowed values:</b><ul>${o.values.map(value => `<li>${value}</li>`).join("")}</ul>`)
if ("min" in o) return ` <tr>
cell.push(`*{${o.min}`) <td nowrap="nowrap"><code>${option}</code></td>
if (("min" in o)||("max" in o)) <td rowspan="2">${description}<img width="900" height="1" alt=""></td>
cell.push(`${"min" in o ? "" : "*{"}𝑥${"max" in o ? "" : "}*"}`) </tr>
if ("max" in o) <tr>
cell.push(`${o.max}}*`) <td nowrap="nowrap">${cell.join("\n")}</td>
row.push(cell.join(" ")) </tr>`
}
row.push(description)
return `| ${row.join(" | ")} |`
}).join("\n"), }).join("\n"),
"\n", "</table>",
flags.size ? "Legend for option icons:" : "",
flags.has("required") ? "* ✔️ Value must be provided" : "",
flags.has("secret") ? "* 🔐 Value should be stored in repository secrets" : "",
flags.has("inherits") ? "* ⏩ Value inherits from its related global-level option" : "",
flags.has("global") ? "* ⏭️ Value be inherited by its related plugin-level option" : "",
flags.has("testing") ? "* 🔧 For development purposes, use with caution" : "",
flags.has("beta") ? "* ✨ Currently in beta-testing on `master`/`main`" : "",
flags.has("extras") ? "* 🧰 Must be enabled in `settings.json` (for web instances)" : "",
].flat(Infinity).filter(s => s).join("\n") ].flat(Infinity).filter(s => s).join("\n")
//Readme descriptor //Readme descriptor
@@ -405,23 +400,6 @@ metadata.template = async function({__templates, name, plugins, logger}) {
} }
} }
//Demo for main and individual readmes
function demo({colspan = null} = {}) { //eslint-disable-line no-inner-declarations
return [
` <td ${colspan ? `colspan="${colspan}"` : ""} align="center">`,
`${Object.entries(meta.examples ?? {}).map(([text, link]) => {
let img = `<img src="${link}" alt=""></img>`
if (text !== "default") {
const open = text.charAt(0) === "+" ? " open" : ""
img = `<details><summary${open}>${open ? text.substring(1) : text}</summary>${img}</details>`
}
return ` ${img}`
}).join("\n")}`,
' <img width="900" height="1" alt="">',
" </td>"
].join("\n")
}
//Header table //Header table
const header = [ const header = [
"<table>", "<table>",
@@ -433,23 +411,23 @@ metadata.template = async function({__templates, name, plugins, logger}) {
" </tr>", " </tr>",
" <tr>", " <tr>",
` <td>${[ ` <td>${[
meta.supports?.includes("user") ? "👤 Users" : "", meta.supports?.includes("user") ? "<code>👤 Users</code>" : "",
meta.supports?.includes("organization") ? "👥 Organizations" : "", meta.supports?.includes("organization") ? "<code>👥 Organizations</code>" : "",
meta.supports?.includes("repository") ? "📓 Repositories" : "" meta.supports?.includes("repository") ? "<code>📓 Repositories</code>" : ""
].filter(v => v).join(", ")}</td>`, ].filter(v => v).join(" ")}</td>`,
" </tr>", " </tr>",
" <tr>", " <tr>",
` <td>${[ ` <td>${[
meta.formats?.includes("svg") ? "*️⃣ SVG" : "", meta.formats?.includes("svg") ? "<code>*️⃣ SVG</code>" : "",
meta.formats?.includes("png") ? "*️⃣ PNG" : "", meta.formats?.includes("png") ? "<code>*️⃣ PNG</code>" : "",
meta.formats?.includes("jpeg") ? "*️⃣ JPEG" : "", meta.formats?.includes("jpeg") ? "<code>*️⃣ JPEG</code>" : "",
meta.formats?.includes("json") ? "#️⃣ JSON" : "", meta.formats?.includes("json") ? "<code>#️⃣ JSON</code>" : "",
meta.formats?.includes("markdown") ? "🔠 Markdown" : "", meta.formats?.includes("markdown") ? "<code>🔠 Markdown</code>" : "",
meta.formats?.includes("markdown-pdf") ? "🔠 Markdown (PDF)" : "", meta.formats?.includes("markdown-pdf") ? "<code>🔠 Markdown (PDF)</code>" : "",
].filter(v => v).join(", ")}</td>`, ].filter(v => v).join(" ")}</td>`,
" </tr>", " </tr>",
" <tr>", " <tr>",
demo({colspan:2}), demos({colspan:2, examples:meta.examples}),
" </tr>", " </tr>",
"</table>" "</table>"
].join("\n") ].join("\n")
@@ -462,7 +440,7 @@ metadata.template = async function({__templates, name, plugins, logger}) {
formats:meta.formats ?? null, formats:meta.formats ?? null,
supports:meta.supports ?? null, supports:meta.supports ?? null,
readme:{ readme:{
demo:demo(), demo:demos({examples:meta.examples}),
compatibility:{ compatibility:{
...Object.fromEntries(Object.entries(compatibility).filter(([_, value]) => value)), ...Object.fromEntries(Object.entries(compatibility).filter(([_, value]) => value)),
...Object.fromEntries(Object.entries(compatibility).filter(([_, value]) => !value).map(([key, value]) => [key, meta.formats?.includes("markdown") ? "embed" : value])), ...Object.fromEntries(Object.entries(compatibility).filter(([_, value]) => !value).map(([key, value]) => [key, meta.formats?.includes("markdown") ? "embed" : value])),
@@ -496,3 +474,22 @@ metadata.to = {
return name ? key.replace(new RegExp(`^(${name}.)`, "g"), "") : key return name ? key.replace(new RegExp(`^(${name}.)`, "g"), "") : key
}, },
} }
//Demo for main and individual readmes
function demos({colspan = null, examples = {}} = {}) {
return [
` <td ${colspan ? `colspan="${colspan}"` : ""} align="center">`,
`${Object.entries(examples).map(([text, link]) => {
let img = `<img src="${link}" alt=""></img>`
if (text !== "default") {
const open = text.charAt(0) === "+" ? " open" : ""
text = open ? text.substring(1) : text
text = `${text.charAt(0).toLocaleUpperCase()}${text.substring(1)}`
img = `<details${open}><summary>${text}</summary>${img}</details>`
}
return ` ${img}`
}).join("\n")}`,
' <img width="900" height="1" alt="">',
" </td>"
].join("\n")
}