@webdoc/template-library
Version:
Goodies for @webdoc template packages! See @webdoc/legacy-template for an example!
385 lines (359 loc) • 12.9 kB
JavaScript
;
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, "<").replace(/>/g, ">");
}
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;