UNPKG

@canalplus/readme.doc

Version:

Readme's an Extremely Accessible Documentation MakEr

321 lines (320 loc) 14.1 kB
import * as fs from "fs"; import * as path from "path"; import { promisify } from "util"; export default async function parseDocConfigs(baseInDir, baseOutDir) { var _a, _b; const rootConfigFileName = path.join(baseInDir, ".docConfig.json"); const rootConfig = await parseAndCheckRootConfigFile(rootConfigFileName); const ret = { logo: undefined, favicon: undefined, links: [], linksRightIndex: -1, siteMapRoot: undefined, }; if (typeof rootConfig.logo === "object") { ret.logo = rootConfig.logo; } if (typeof rootConfig.favicon === "object") { ret.favicon = rootConfig.favicon; } const linksLeft = (_a = rootConfig.linksLeft) !== null && _a !== void 0 ? _a : []; const linksRight = (_b = rootConfig.linksRight) !== null && _b !== void 0 ? _b : []; const categoryLinks = linksLeft.concat(linksRight); if (linksRight.length > 0) { ret.linksRightIndex = linksLeft.length; } for (let i = 0; i < categoryLinks.length; i++) { const category = categoryLinks[i]; if (typeof category !== "object" || category === null) { if (i < linksLeft.length) { throw new Error(`Invalid element in \`linksLeft\` at index ${i}`); } else { throw new Error(`Invalid element in \`linksRight\` at index ${i - linksLeft.length}`); } } if (!("type" in category) || typeof category.type !== "string") { if (i < linksLeft.length) { throw new Error(`Element in \`linksLeft\` at index ${i} is missing its \`type\` property`); } else { throw new Error(`Element in \`linksRight\` at index ${i - linksLeft.length} is missing its \`type\` property`); } } let parsedCategory; switch (category.type) { case "local-doc": if (!("path" in category) || typeof category.path !== "string") { throw new Error(`A "local-doc" element is missing its "path" property.`); } if (!("displayName" in category) || typeof category.displayName !== "string") { throw new Error(`A "local-doc" element is missing its "path" property.`); } parsedCategory = await parseLocalDocCategory({ displayName: category.displayName, path: category.path }, baseInDir, baseOutDir); break; case "version": let link; if ("link" in category) { if (typeof category.link !== "string") { throw new Error(`A version" element has a "link" property in the wrong format.` + " Should be a string."); } link = category.link; } parsedCategory = { type: "version", link }; break; case "link": if (!("link" in category) || typeof category.link !== "string") { throw new Error(`A "link" element is missing its "link" property.`); } if (!("displayName" in category) || typeof category.displayName !== "string") { throw new Error(`A "link" element is missing its "path" property.`); } parsedCategory = { type: "external-link", link: category.link, displayName: category.displayName, }; break; case "github-link": if (!("link" in category) || typeof category.link !== "string") { throw new Error(`A "github-link" element is missing its "link" property.`); } parsedCategory = { type: "github-link", link: category.link, }; break; case "search": parsedCategory = { type: "search" }; break; } if (parsedCategory !== undefined) { ret.links.push(parsedCategory); } } if (typeof rootConfig.siteMapRoot === "string") { ret.siteMapRoot = rootConfig.siteMapRoot; } return ret; } async function parseLocalDocCategory(category, baseInDir, baseOutDir) { var _a, _b, _c, _d; const categoryPath = path.join(baseInDir, category.path); const categoryOutPath = path.join(baseOutDir, category.path); const parsedCategory = { type: "local-doc", displayName: category.displayName, firstPage: "", pages: [], }; const catCfgFileName = path.join(categoryPath, ".docConfig.json"); const categoryConfig = await parseAndCheckSubConfigFile(catCfgFileName); for (const page of categoryConfig.pages) { const pagePath = path.join(categoryPath, page.path); let pageStat; try { pageStat = await promisify(fs.stat)(pagePath); } catch (err) { const srcMessage = (_b = ((_a = err) !== null && _a !== void 0 ? _a : {}).message) !== null && _b !== void 0 ? _b : "Unknown error"; throw new Error(`Cannot run stat on "${pagePath}": ${srcMessage}`); } if (pageStat.isDirectory()) { const parsedPage = { isPageGroup: true, defaultOpen: page.defaultOpen === true, displayName: page.displayName, pages: [], }; parsedCategory.pages.push(parsedPage); const pageCfgFileName = path.join(pagePath, ".docConfig.json"); const pageGroupConfig = await parseAndCheckSubConfigFile(pageCfgFileName); for (const subPage of pageGroupConfig.pages) { const subPagePath = path.join(pagePath, subPage.path); const subPageOutPath = path.join(categoryOutPath, page.path, subPage.path); let subPageState; try { subPageState = await promisify(fs.stat)(subPagePath); } catch (err) { const srcMessage = (_d = ((_c = err) !== null && _c !== void 0 ? _c : {}).message) !== null && _d !== void 0 ? _d : "Unknown error"; throw new Error(`Cannot run stat on "${subPagePath}": ${srcMessage}`); } if (subPageState.isDirectory()) { throw new Error('Category page depth cannot exceed 2 yet "' + subPagePath + '" is a directory.'); } const outputFile = path.join(path.dirname(subPageOutPath), path.basename(subPagePath, ".md") + ".html"); if (parsedPage.pages === undefined) { parsedPage.pages = []; } parsedPage.pages.push({ isPageGroup: false, displayName: subPage.displayName, inputFile: path.normalize(path.resolve(subPagePath)), outputFile: path.normalize(path.resolve(outputFile)), }); } } else if (pageStat.isFile()) { const outputFile = path.join(categoryOutPath, path.basename(pagePath, ".md") + ".html"); parsedCategory.pages.push({ isPageGroup: false, displayName: page.displayName, inputFile: path.normalize(path.resolve(pagePath)), outputFile: path.normalize(path.resolve(outputFile)), }); } } // Retrieve landing page for category == first page available if (parsedCategory.pages.length > 0) { if (!parsedCategory.pages[0].isPageGroup) { parsedCategory.firstPage = parsedCategory.pages[0].outputFile; } else if (parsedCategory.pages[0].pages !== undefined && parsedCategory.pages[0].pages.length > 0) { parsedCategory.firstPage = parsedCategory.pages[0].pages[0].outputFile; } } return parsedCategory; } /** * Parse root config file into a JS object and check that all properties are in * the right format. * Exit with a meaningful error if that's not the case. * @param {string} rootConfigFileName * @returns {Promise.<Object>} */ async function parseAndCheckRootConfigFile(rootConfigFileName) { var _a, _b, _c, _d; let configStr; try { configStr = await promisify(fs.readFile)(rootConfigFileName, "utf8"); } catch (err) { const srcMessage = (_b = ((_a = err) !== null && _a !== void 0 ? _a : {}).message) !== null && _b !== void 0 ? _b : "Unknown error"; throw new Error('Impossible to read Root .docConfig.json file ("' + rootConfigFileName + '"): ' + srcMessage); } let config; try { config = JSON.parse(configStr); } catch (err) { const srcMessage = (_d = ((_c = err) !== null && _c !== void 0 ? _c : {}).message) !== null && _d !== void 0 ? _d : "Unknown error"; exitWithInvalidRootConfig(srcMessage); } if (typeof config !== "object" || config === null) { exitWithInvalidRootConfig("Should be under an object form."); } if ("logo" in config) { if (typeof config.logo !== "object" || config.logo === null) { exitWithInvalidRootConfig(`The "logo" property, if defined, should contain an object.`); } if (!("srcPath" in config.logo) || typeof config.logo.srcPath !== "string") { exitWithInvalidRootConfig(`The "logo" property, if defined, should contain a "srcPath" ` + "property set as a string."); } if ("link" in config.logo && typeof config.logo.link !== "string") { exitWithInvalidRootConfig(`The "logo.link" property, if defined, should be set as a string.`); } } if ("favicon" in config) { if (typeof config.favicon !== "object" || config.favicon === null) { exitWithInvalidRootConfig(`The "favicon" property, if defined, should contain an object.`); } if (!("srcPath" in config.favicon) || typeof config.favicon.srcPath !== "string") { exitWithInvalidRootConfig(`The "favicon" property, if defined, should contain a "srcPath" ` + "property set as a string."); } } if ("linksLeft" in config && !Array.isArray(config.linksLeft)) { exitWithInvalidRootConfig(`The "linksLeft" property, if defined, should be set as an Array.`); } if ("linksRight" in config && !Array.isArray(config.linksRight)) { exitWithInvalidRootConfig(`The "linksRight" property, if defined, should be set as an Array.`); } if ("siteMapRoot" in config && typeof config.siteMapRoot !== "string") { exitWithInvalidRootConfig(`The "siteMapRoot" property, if defined, should be a string.`); } return config; function exitWithInvalidRootConfig(reason) { throw new Error(`Root .docConfig.json file ("${rootConfigFileName}") is invalid: ${reason}`); } } /** * @param {string} filename * @returns {Promise.<Object>} */ async function parseAndCheckSubConfigFile(filename) { var _a, _b, _c, _d; let configStr; try { configStr = await promisify(fs.readFile)(filename, "utf8"); } catch (err) { const srcMessage = (_b = ((_a = err) !== null && _a !== void 0 ? _a : {}).message) !== null && _b !== void 0 ? _b : "Unknown error"; throw new Error(`Impossible to read "${filename}" config file: ${srcMessage}`); } let config; try { config = JSON.parse(configStr); } catch (err) { const srcMessage = (_d = ((_c = err) !== null && _c !== void 0 ? _c : {}).message) !== null && _d !== void 0 ? _d : "Unknown error"; throw new Error(`"${filename}" config file is invalid: ${srcMessage}`); } if (typeof config !== "object" || config === null) { throw new Error(`"${filename}" config file is invalid: ` + `Should be under an object form.`); } if (!("pages" in config) || !Array.isArray(config.pages) || config.pages.length === 0) { throw new Error(`"${filename}" config file is invalid: ` + `Should have a "pages" property with at least one entry.`); } const outputPages = []; for (let i = 0; i < config.pages.length; i++) { const page = config.pages[i]; if (typeof page !== "object" || page === null) { throw new Error(`"${filename}" config file is invalid: ` + `One of the element in "pages" has an invalid format ` + "(should be an object)."); } if (!("path" in page) || typeof page.path !== "string") { throw new Error(`"${filename}" config file is invalid: ` + `One of the element in "pages" has an invalid "path" property ` + "(should be a string)."); } if (!("displayName" in page) || typeof page.displayName !== "string") { throw new Error(`"${filename}" config file is invalid: ` + `One of the element in "pages" has an invalid "displayName" property ` + "(should be a string)."); } const pushedPage = { path: page.path, displayName: page.displayName, }; if ("defaultOpen" in page) { if (typeof page.defaultOpen !== "boolean") { throw new Error(`"${filename}" config file is invalid: ` + `One of the element in "pages" has an invalid "defaultOpen" property ` + "(should be a boolean or not defined)."); } pushedPage.defaultOpen = page.defaultOpen; } outputPages.push(pushedPage); } // TODO make TypeScript understand our checks here return { pages: outputPages }; }