UNPKG

@webdoc/template-library

Version:

Goodies for @webdoc template packages! See @webdoc/legacy-template for an example!

385 lines (359 loc) 12.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LinkerPlugin = void 0; exports.LinkerPluginShell = LinkerPluginShell; var external = _interopRequireWildcard(require("@webdoc/externalize")); var _nodeFetch = _interopRequireDefault(require("node-fetch")); var _fs = require("fs"); var _model = require("@webdoc/model"); var _Logger = require("../Logger"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function LinkerPluginShell() { const catharsis = require("catharsis"); const path = require("path"); const { query } = require("@webdoc/model"); const { MixinPlugin } = require("./MixinPlugin"); function isComplexTypeExpression(expr) { return /^{.+}$/.test(expr) || /^.+\|.+$/.test(expr) || /^.+<.+>$/.test(expr); } function hasUrlPrefix(text) { return /^(http|ftp)s?:\/\//.test(text); } function getShortName(docPath) { const parts = docPath.split(/(.|#|~)/); return parts[parts.length - 1]; } function stringifyType(parsedType, cssClass, stringifyLinkMap) { return require("catharsis").stringify(parsedType, { cssClass: cssClass, htmlSafe: true, links: stringifyLinkMap }); } function parseType(longname) { let err; try { return catharsis.parse(longname, { jsdoc: true }); } catch (e) { err = new Error(`unable to parse ${longname}: ${e.message}`); _Logger.templateLogger.error(err); return longname; } } function mapToLinks(documentRegistry) { const keys = documentRegistry.keys(); const object = {}; for (const key of keys) { object[key] = (documentRegistry.get(key)).uri; } return object; } class LinkerPluginImpl extends MixinPlugin { constructor(...args) { super(...args); _defineProperty(this, "standaloneDocTypes", ["ClassDoc", "NSDoc", "InterfaceDoc", "MixinDoc", "PackageDoc", "TutorialDoc"]); _defineProperty(this, "documentRegistry", new Map()); _defineProperty(this, "fileLayout", "tree"); _defineProperty(this, "fileRegistry", new Map()); _defineProperty(this, "siteRoot", ""); _defineProperty(this, "queryCache", new Map()); _defineProperty(this, "importedManifests", []); } async loadManifest(uri) { let isURL = true; try { new URL(uri); } catch (e) { isURL = false; } let contents; if (isURL) { contents = await (0, _nodeFetch.default)(uri).then(res => res.text()); } else { contents = await _fs.promises.readFile(path.join(process.cwd(), uri), "utf8"); } const manifest = external.read(contents); const { registry } = manifest; let { siteDomain, siteRoot } = manifest.metadata; if (siteDomain === undefined && isURL) { const { origin, pathname } = new URL(uri); siteDomain = origin; siteRoot = path.dirname(pathname); } if (!siteDomain) { throw new Error("Imported documented interfaces must have a siteDomain!"); } for (const id in registry) { if (registry[id].uri) { const internalUri = registry[id].uri; const baseRelativeUri = path.join(siteRoot || "", internalUri); this.getDocumentRecord(id).uri = new URL(baseRelativeUri, siteDomain).toString(); } } this.importedManifests.push(manifest); } getDocumentRecord(id) { if (typeof id !== "string") { throw new Error("The id must be a valid string!"); } const recordHit = this.documentRegistry.get(id); if (!recordHit) { const record = {}; this.documentRegistry.set(id, record); return record; } return recordHit; } getFileRecord(uri) { const key = uri.toLowerCase(); const recordHit = this.fileRegistry.get(key); if (!recordHit) { const record = { uriFragments: new Set() }; this.fileRegistry.set(key, record); return record; } return recordHit; } linkTo(docPath, linkText = docPath, options = {}) { if (!options) { options = {}; } if (!docPath) { return ""; } if (this.queryCache.has(docPath)) { return `<a href=${encodeURI(this.queryCache.get(docPath) || "")}>${linkText}</a>`; } if ((0, _model.isDataType)(docPath)) { let link = docPath.template; if (options.htmlSafe !== false) { link = link.replace(/</g, "&lt;").replace(/>/g, "&gt;"); } for (let i = 1; i < docPath.length; i++) { link = link.replace(`%${i}`, this.linkTo(docPath[i], docPath[i], options)); } return link; } else if (typeof docPath !== "string") { docPath = docPath.path; } if (linkText && typeof linkText !== "string") { linkText = linkText.path; } const classString = options.cssClass ? ` class="${options.cssClass}"` : ""; let fileUrl; const fragmentString = options.fragmentId ? "#" + options.fragmentId : ""; let text; let parsedType; const stripped = docPath ? docPath.replace(/^<|>$/g, "") : ""; if (hasUrlPrefix(stripped)) { fileUrl = stripped; text = linkText || stripped; } else if (docPath && isComplexTypeExpression(docPath) && /\{@.+\}/.test(docPath) === false && /^<[\s\S]+>/.test(docPath) === false) { parsedType = parseType(docPath); return stringifyType(parsedType, options.cssClass, mapToLinks(this.documentRegistry)); } else { const doc = query(docPath, this.renderer.docTree)[0]; if (doc) { const rec = this.documentRegistry.get(doc.id); fileUrl = rec && rec.uri ? this.processInternalURI(rec.uri) : this.getURI(doc); if (fileUrl) { this.queryCache.set(docPath, fileUrl); } } else { for (let i = 0; i < this.importedManifests.length && !fileUrl; i++) { const externalInterface = this.importedManifests[i]; const doc = query(docPath, externalInterface.root)[0]; if (doc) { const { uri } = this.getDocumentRecord(doc.id); if (uri) { fileUrl = uri; this.queryCache.set(docPath, fileUrl); } } } if (!fileUrl) { return linkText || docPath; } } text = linkText || (options.shortenName ? getShortName(docPath) : docPath); } text = options.monospace ? `<code>${text}</code>` : text; if (!fileUrl) { return text; } else { return `<a href="${encodeURI(fileUrl + fragmentString)}"${classString}>${text}</a>`; } } linksToAncestors(doc, cssClass) { const ancestors = []; let searchDoc = doc.parent; while (searchDoc) { ancestors.push(searchDoc); searchDoc = searchDoc.parent; } const links = []; ancestors.forEach(ancestor => { if (ancestor.type === "RootDoc" || !ancestor.parent) { return; } links.unshift(this.linkTo(ancestor.path, ancestor.name, { cssClass })); }); if (links.length) { } return links; } getMixin() { return { linkTo: this.linkTo.bind(this) }; } getShell() { return LinkerPluginShell; } getURI(doc, outputRelative) { const id = doc.id; const record = this.getDocumentRecord(id); let uri = record.uri; if (!uri) { uri = this.generateURI(doc); record.uri = uri; } return this.processInternalURI(uri, { outputRelative }); } getResourceURI(subpath) { return this.processInternalURI(path.join("/<siteRoot>", subpath)); } getFileSystemPath(uri) { return this.siteRoot ? uri.replace(this.siteRoot, "").replace("//", "/") : uri; } createURI(preferredUri, outputRelative) { const uri = this.generateBaseURI(preferredUri); return this.processInternalURI(uri, { outputRelative }); } processInternalURI(uri, options = {}) { return uri.replace(/<siteRoot>/g, options.outputRelative ? "" : this.siteRoot).replace(/\/+/g, "/"); } generateBaseURI(canonicalPath, pathPrefix = "") { let seedURI = (canonicalPath || "" ).replace(/[\\?*:|'"<>]/g, "_") .replace(/~/g, "-") .replace(/#/g, "_") .replace(/\([\s\S]*\)$/, "") .replace(/^[.-]/, ""); if (this.fileLayout === "tree") { seedURI = seedURI.replace(/[.]/g, "/"); seedURI = path.join("/<siteRoot>", pathPrefix, seedURI); } else { seedURI = seedURI.replace(/\//g, "_"); } seedURI = seedURI.length ? seedURI : "_"; seedURI += ".html"; let probeURI = seedURI; let probeKey = seedURI.toLowerCase(); let nonUnique = true; if (!seedURI.length || seedURI[0] === "_") { probeURI = `-${seedURI}`; probeKey = probeURI.toLowerCase(); } while (nonUnique) { if (this.fileRegistry.has(probeKey)) { probeURI = probeURI.replace(".html", "_.html"); probeKey = probeKey.replace(".html", "_.html"); } else { nonUnique = false; } } this.getFileRecord(probeURI); return probeURI; } generateURIFragment(fileName, id, usedIDs) { let key; let nonUnique = true; key = id.toLowerCase(); id = id.replace(/\s/g, ""); while (nonUnique) { if (usedIDs.has(key)) { id += "_"; key = id.toLowerCase(); } else { nonUnique = false; } } usedIDs.add(key); return id; } generateURI(doc) { if (typeof doc === "string") { throw new Error("Invalid argument, cannot generate an URI from a document path."); } let baseURI; let fragment; const docPath = doc.path; const { standaloneDocTypes } = this; if (standaloneDocTypes.includes(doc.type)) { let pathPrefix = ""; let route = docPath; if (doc.type !== "PackageDoc" && doc.loc) { const pkg = doc.loc.file.package; if (pkg) { pathPrefix = this.getURI(pkg, true).split(".").slice(0, -1).join("."); } } if (doc.type === "TutorialDoc" && doc.route) { route = doc.route; } baseURI = this.generateBaseURI(route, pathPrefix); this.getFileRecord(baseURI); } else { baseURI = doc.parent && doc.parent.type !== "RootDoc" ? this.getURI(doc.parent) : "global.html"; fragment = this.generateURIFragment(baseURI, doc.name, this.getFileRecord(baseURI).uriFragments); } return `${baseURI}${fragment ? "#" + fragment : ""}`; } static async from(config) { const linkerPlugin = new LinkerPlugin(); linkerPlugin.siteRoot = config.template.siteRoot; if (Array.isArray(config.template.import)) { for (const manifestURI of config.template.import) { await linkerPlugin.load(manifestURI); } } return linkerPlugin; } } return LinkerPluginImpl; } const LinkerPlugin = LinkerPluginShell(); exports.LinkerPlugin = LinkerPlugin;