Skip to content

Instantly share code, notes, and snippets.

@kaosine
Created September 30, 2022 23:10
Show Gist options
  • Save kaosine/5c4a87b8c2efbd87eca8614a0ef655ce to your computer and use it in GitHub Desktop.
Save kaosine/5c4a87b8c2efbd87eca8614a0ef655ce to your computer and use it in GitHub Desktop.
Conversion of atom json filter for api
require("json");
require("kramdown");
class ApiJsonFilter extends Nanoc.Filter {
#classNames = {};
#className = null;
#version = null;
#mdnBaseUrl = "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects";
// ----- Helpers -----
classSourceLink(data) {
this.sourceLink(data, "class")
};
propertySourceLink(data) {
this.sourceLink(data, "property")
};
sourceLink(item, type=null) {
`<a
class="document-source"
href="${item.srcUrl}"
${type ? ` title="View ${type} source"` : ""}>
${this.octicon("file-code")}
</a>
`
};
markdown(text) {
text = this.linkReferences(text);
new Kramdown.Document(text, {input: "GFM", hardWrap: false}).toHtml
};
linkReferences(text) {
let bracesLinkPattern = /\{(?<cname>\w+)?(::(?<fname>\w+))?\}/;
text.replaceAll(bracesLinkPattern, (match) => {
let link;
let cname = Regexp.lastMatch.namedCaptures.cname;
let fname = Regexp.lastMatch.namedCaptures.fname;
if (cname && this.#classNames[cname] && fname) {
return `[${cname}::${fname}](../${cname}/#instance-${fname})`
} else if (cname && fname) {
link = File.join(this.#mdnBaseUrl, cname, fname);
return `[${cname}::${fname}](${link})`
} else if (cname && this.#classNames[cname]) {
return `[${cname}](../${cname}/)`
} else if (cname) {
link = File.join(this.#mdnBaseUrl, cname);
return `[${cname}](${link})`
} else if (fname) {
return `[::${fname}](#instance-${fname})`
} else {
match.toString()
}
})
};
octicon(name) {
`<span class="octicon octicon-${name}"></span>`
};
argument(arg) {
`<span class="argument">${arg.name}</span>`
};
argumentList(func) {
let args = func.arguments;
`<span class="argument-list">(${args ? args.map(arg => this.argument(arg)).join(", ") : ""})</span>\n`
};
method(func, scope) {
let id = `${scope}-${func.name}`;
let prefix = (() => {
switch (scope) {
case "instance":
return "::";
case "class":
return ".";
default:
return ""
}
})();
`<div
class="api-entry js-api-entry ${this.visibilityClass(func.visibility)}"
id="${id}">
<h3 class="name">
<a href="#${id}" class="js-api-name method-signature" name="${id}">
${prefix}${func.name}${this.argumentList(func)}
</a>
${this.sourceLink(func)}
</h3>
<div class="method-summary-wrapper">
${this.summary(func)}
${this.description(func)}
${func.arguments ? this.argumentsTable(func) : null}
${func.returnValues ? this.returnValuesTable(func) : null}
</div>
</div>
`
};
returnValuesTable(func) {
`<table class="return-values">
<thead>
<tr>
<th>Return values</th>
</tr>
</thead>
<tbody>
${this.returnValueRows(func.returnValues)}
</tbody>
</table>
`
};
returnValueRows(retvals) {
retvals.map(retval => this.returnValueRow(retval)).join
};
returnValueRow(retval) {
`<tr>
<td class="markdown-body">
${this.markdown(retval.description)}
</td>
</tr>
`
};
argumentsTable(func) {
`<table class="arguments">
<thead>
<tr>
<th>Argument</th>
<th>Description</th>
</tr>
</thead>
<tbody>
${this.argumentRows(func.arguments)}
</tbody>
</table>
`
};
argumentRows(args, level=0) {
args.map(arg => this.argumentRow(arg, level)).join
};
argumentRow(arg, level) {
let children = arg.children;
`<tr class="markdown-body argument-depth-${level}">
<td>
${this.markdown(`\`${arg.name}\``)}
</td>
<td>
${arg.isOptional ? "<span class=\"optional\">optional</span>" : ""}
${this.markdown(arg.description)}
</td>
</tr>
${children ? this.argumentRows(
children,
level + 1
) : null}
`
};
summary(obj) {
`<div class="summary markdown-body">\n ${this.markdown(obj.summary)}\n</div>\n`
};
description(obj) {
let text = ((s) => {
s.slice(obj.summary);
return s
})(obj.description);
`<div class="body markdown-body">
<div class="description">
${this.markdown(text)}
</div>
</div>
`
};
property(prop, scope) {
let id = `${scope}-${prop.name}`;
`<div
class="api-entry js-api-entry ${this.visibilityClass(prop.visibility)}"
id="${scope}-${prop.name}">
<h3 class="name">
<a href="#${id}" class="js-api-name" name="${id}">
<span class="operator operator-instance">::</span>${prop.name.trim()}
</a>
${this.propertySourceLink(prop)}
</h3>
<div class="method-summary-wrapper">
${this.summary(prop)}
</div>
</div>
`
};
visibility(viz) {
switch (viz) {
case "Public":
"Essential";
break;
default:
viz
}
};
visibilityClass(viz) {
this.visibility(viz).toLowerCase
};
visibilityLabel(data) {
`<span
class="label label-${this.visibilityClass(data.visibility)}"
title="This class is in the ${this.visibilityClass(data.visibility)} API">
${this.visibility(data.visibility)}
</span>
`
};
// ----- Page Sections -----
classDescription(data) {
`<div class="markdown-body">\n ${this.markdown(data.description)}\n</div>\n`
};
classExamples(data) {
let examples = Array.from(data.examples);
if (examples.length == 0) return "";
let entries = examples.map((example) => {
let description;
this.description = "";
if (example.description.length != 0) {
this.description = `<div class="description markdown-body">\n ${this.markdown(example.description)}\n</div>\n`
};
return `<div class="example">\n ${description}\n ${this.markdown(example.raw)}\n</div>\n`
}).join("\n");
`<h2 class="section">Examples</h2>
<div class="document-examples markdown-body">
${entries}
</div>
`
};
pageTitle(data) {
`<h2 class="page-title">\n ${data.name}\n ${this.visibilityLabel(data)}\n ${this.classSourceLink(data)}\n</h2>\n`
};
uncategorizedMethods(data) {
let uncategorizedClassMethods = data.classMethods.filter(func => (
func.sectionName == null
));
let uncategorizedInstanceMethods = data.instanceMethods.filter(func => (
func.sectionName == null
));
if ((uncategorizedClassMethods + uncategorizedInstanceMethods).length == 0) {
return ""
};
`<h2 class="detail-section">Methods</h2>\n${uncategorizedClassMethods.map(func => (
this.method(func, "class")
)).join}\n${uncategorizedInstanceMethods.map(func => (
this.method(func, "instance")
)).join}\n`
};
instanceMethods(methods) {
let [extended, essential] = methods.partition(func => (
func.visibility == "Extended"
));
let html = "";
html.push(essential.map(func => this.method(func, "instance")).join);
if (extended.any) {
if (essential.length == 0) {
html.push("<p class=\"no-methods-message\">This section only has Extended methods.</p>")
};
html.push("<h4>Extended Methods</h4>");
html.push(extended.map(func => this.method(func, "instance")).join)
};
html
};
sections(data) {
let sectionNames = data.sections.map(section => section.name).uniq;
let sectionText = sectionNames.map((sectionName) => {
let _classMethods = data.classMethods.filter(func => (
func.sectionName == sectionName
));
let _instanceProperties = data.instanceProperties.filter(prop => (
prop.sectionName == sectionName
));
let _instanceMethods = data.instanceMethods.filter(func => (
func.sectionName == sectionName
));
return `<h2 class="detail-section">${sectionName}</h2>\n${_classMethods.map(func => (
this.method(func, "class")
)).join}\n${_instanceProperties.map(prop => this.property(prop, "instance")).join}\n${this.instanceMethods(_instanceMethods)}\n`
});
sectionText.join("\n")
};
run(content, params={}) {
this.#className = params.className;
this.#version = params.version;
let apiDir = `${Dir.pwd}/content/api/${this.#version}`;
Dir.glob(
`${apiDir}/*.json`,
entry => this.#classNames[File.basename(entry, ".json")] = true
);
let data = JSON.parse(content);
this.pageTitle(data) + this.classDescription(data) + this.classExamples(data) + this.uncategorizedMethods(data) + this.sections(data)
}
};
ApiJsonFilter.identifier("apiJson")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment