mirror of
https://github.com/libgit2/libgit2.git
synced 2026-01-25 11:06:32 +00:00
Clean up the outdated documentation folder before re-generating it in place. This accomodates a deleted API.
1418 lines
42 KiB
JavaScript
Executable File
1418 lines
42 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
const markdownit = require('markdown-it');
|
|
const { program } = require('commander');
|
|
|
|
const path = require('node:path');
|
|
const fs = require('node:fs/promises');
|
|
const process = require('node:process');
|
|
|
|
const githubPath = 'https://github.com/libgit2/libgit2';
|
|
|
|
const linkPrefix = '/docs/reference';
|
|
|
|
const projectTitle = 'libgit2';
|
|
const includePath = 'include/git2';
|
|
|
|
const fileDenylist = [ 'stdint.h' ];
|
|
const showVersions = true;
|
|
|
|
const defaultBranch = 'main';
|
|
|
|
const markdown = markdownit();
|
|
const markdownDefaults = {
|
|
code_inline: markdown.renderer.rules.code_inline
|
|
};
|
|
markdown.renderer.rules.code_inline = (tokens, idx, options, env, self) => {
|
|
const version = env.__version || defaultBranch;
|
|
|
|
const code = tokens[idx].content;
|
|
const text = `<code>${nowrap(sanitize(tokens[idx].content))}</code>`;
|
|
const link = linkForCode(version, code, text);
|
|
|
|
return link ? link : text;
|
|
};
|
|
|
|
// globals
|
|
const apiData = { };
|
|
const versions = [ ];
|
|
const versionDeltas = { };
|
|
|
|
function produceVersionPicker(version, classes, cb) {
|
|
let content = "";
|
|
|
|
if (!showVersions) {
|
|
return content;
|
|
}
|
|
|
|
content += ` <div class="${classes}">\n`;
|
|
content += ` <span>Version:</span>\n`;
|
|
content += ` <select onChange="window.location.href = this.value">\n`;
|
|
|
|
for (const v of versions) {
|
|
const link = cb(v);
|
|
if (link) {
|
|
content += ` <option value="${link}"${v === version ? ' selected' : ''}>${v}</option>\n`;
|
|
}
|
|
}
|
|
|
|
content += ` </select>\n`;
|
|
|
|
content += ` </div>\n`;
|
|
|
|
return content;
|
|
}
|
|
|
|
function produceBreadcrumb(version, api, type) {
|
|
let content = "";
|
|
let group = api.group;
|
|
let sys = false;
|
|
|
|
if (group.endsWith('.h')) {
|
|
group = group.substr(0, group.length - 2);
|
|
}
|
|
|
|
let groupTitle = group;
|
|
|
|
if (groupTitle.startsWith('sys/')) {
|
|
groupTitle = groupTitle.substr(4);
|
|
groupTitle += ' (advanced)';
|
|
}
|
|
|
|
content += ` <div class="apiBreadcrumb ${type}Breadcrumb">\n`;
|
|
content += ` <ul>\n`;
|
|
content += ` <li><a href="${linkPrefix}/${version}/index.html">API Documentation</a></li>\n`;
|
|
content += ` <li><a href="${linkPrefix}/${version}/${group}/index.html">${groupTitle}</a></li>\n`;
|
|
content += ` </ul>\n`;
|
|
content += ` </div>\n`;
|
|
|
|
return content;
|
|
}
|
|
|
|
function produceHeader(version, api, type) {
|
|
let content = "";
|
|
|
|
content += ` <div class="apiHeader ${type}Header">\n`;
|
|
content += ` <h2 class="apiName ${type}Name">${api.name}</h2>\n`;
|
|
|
|
content += produceAttributes(version, api, type);
|
|
content += produceSearchArea(version, type);
|
|
|
|
content += produceVersionPicker(version,
|
|
`apiHeaderVersionSelect ${type}HeaderVersionSelect`,
|
|
(v) => {
|
|
const versionedApi = selectApi(v, (i => i.name === api.name));
|
|
return versionedApi ? linkFor(v, versionedApi) : undefined;
|
|
});
|
|
|
|
content += ` </div>\n`;
|
|
content += `\n`;
|
|
|
|
return content;
|
|
}
|
|
|
|
function produceAttributes(version, api, type) {
|
|
let content = "";
|
|
|
|
if (api.deprecations) {
|
|
content += ` <span class="apiAttribute ${type}Attribute apiAttributeDeprecated ${type}AttributeDeprecated">Deprecated</span>\n`;
|
|
}
|
|
|
|
return content;
|
|
}
|
|
|
|
function produceDescription(version, desc, type) {
|
|
let content = "";
|
|
|
|
if (! desc.comment) {
|
|
return content;
|
|
}
|
|
|
|
content += `\n`;
|
|
content += ` <div class="apiDescription ${type}Description">\n`;
|
|
|
|
for (const para of Array.isArray(desc.comment) ? desc.comment : [ desc.comment ]) {
|
|
content += ` ${markdown.render(para, { __version: version })}\n`;
|
|
}
|
|
|
|
content += ` </div>\n`;
|
|
|
|
return content;
|
|
}
|
|
|
|
function produceList(version, api, type, listType) {
|
|
let content = "";
|
|
|
|
if (!api[listType]) {
|
|
return content;
|
|
}
|
|
|
|
const listTypeUpper = listType.charAt(0).toUpperCase() + listType.slice(1);
|
|
const listTypeTitle = listTypeUpper.replaceAll(/(.)([A-Z])/g, (match, one, two) => { return one + ' ' + two; });
|
|
|
|
content += `\n`;
|
|
content += ` <h3 class="api${listTypeUpper}Header ${type}${listTypeUpper}Header">${listTypeTitle}</h3>\n`;
|
|
|
|
content += ` <div class="api${listTypeUpper} ${type}${listTypeUpper}">\n`;
|
|
content += ` <ul>\n`;
|
|
|
|
for (const item of api[listType]) {
|
|
content += ` <li>\n`;
|
|
content += ` ${linkText(version, item)}\n`;
|
|
content += ` </li>\n`;
|
|
}
|
|
|
|
content += ` </ul>\n`;
|
|
content += ` </div>\n`;
|
|
|
|
return content;
|
|
}
|
|
|
|
function produceNotes(version, api, type) {
|
|
return produceList(version, api, type, 'notes');
|
|
}
|
|
|
|
function produceSeeAlso(version, api, type) {
|
|
return produceList(version, api, type, 'deprecated');
|
|
}
|
|
|
|
function produceSeeAlso(version, api, type) {
|
|
return produceList(version, api, type, 'see');
|
|
}
|
|
|
|
function produceWarnings(version, api, type) {
|
|
return produceList(version, api, type, 'warnings');
|
|
}
|
|
|
|
function produceDeprecations(version, api, type) {
|
|
return produceList(version, api, type, 'deprecations');
|
|
}
|
|
|
|
function produceGitHubLink(version, api, type) {
|
|
if (!api || !api.location || !api.location.file) {
|
|
return undefined;
|
|
}
|
|
|
|
let file = api.location.file;
|
|
|
|
let link = githubPath + '/blob/' + version + '/' + includePath + '/' + file;
|
|
|
|
if (api.location.line) {
|
|
link += '#L' + api.location.line;
|
|
}
|
|
|
|
return link;
|
|
}
|
|
|
|
function produceSignatureForFunction(version, api, type) {
|
|
let content = "";
|
|
let paramCount = 0;
|
|
|
|
let prefix = type === 'callback' ? 'typedef' : '';
|
|
const returnType = api.returns?.type || 'int';
|
|
|
|
const githubLink = produceGitHubLink(version, api, type);
|
|
|
|
content += `\n`;
|
|
|
|
content += ` <h3 class="apiSignatureHeader ${type}SignatureHeader">Signature</h3>\n`;
|
|
|
|
if (githubLink) {
|
|
content += ` <div class="apiSignatureLink ${type}SignatureLink">\n`;
|
|
content += ` <a href="${githubLink}" target="_blank" rel="noopener">GitHub</a>\n`;
|
|
content += ` </div>\n`;
|
|
}
|
|
|
|
content += ` <div class="apiSignature ${type}Signature">\n`;
|
|
|
|
content += ` ${prefix ? prefix + ' ' : ''}${returnType}`;
|
|
content += returnType.endsWith('*') ? '' : ' ';
|
|
content += `${api.name}(`;
|
|
|
|
for (const param of api.params || [ ]) {
|
|
content += (paramCount++ > 0) ? ', ' : '';
|
|
|
|
if (!param.type && options.strict) {
|
|
throw new Error(`param ${param.name} has no type for function ${api.name}`);
|
|
}
|
|
else if (!param.type) {
|
|
continue;
|
|
}
|
|
|
|
content += `<span class="apiSignatureParameter ${type}SignatureParameter">`;
|
|
content += `${param.type}`;
|
|
content += param.type.endsWith('*') ? '' : ' ';
|
|
|
|
if (param.name) {
|
|
content += `${param.name}`;
|
|
}
|
|
|
|
content += `</span>`;
|
|
}
|
|
|
|
content += `);\n`;
|
|
content += ` </div>\n`;
|
|
|
|
return content;
|
|
}
|
|
|
|
function produceFunctionParameters(version, api, type) {
|
|
let content = "";
|
|
|
|
if (!api.params || api.params.length == 0) {
|
|
return content;
|
|
}
|
|
|
|
content += `\n`;
|
|
|
|
content += ` <h3 class="apiParametersHeader ${type}ParametersHeader">Parameters</h3>\n`;
|
|
content += ` <div class="apiParameters ${type}Parameters">\n`;
|
|
|
|
for (const param of api.params) {
|
|
let direction = param.direction || 'in';
|
|
direction = direction.charAt(0).toUpperCase() + direction.slice(1);
|
|
|
|
if (!param.type && options.strict) {
|
|
throw new Error(`param ${param.name} has no type for function ${api.name}`);
|
|
}
|
|
else if (!param.type) {
|
|
continue;
|
|
}
|
|
|
|
content += ` <div class="apiParameter ${type}Parameter apiParameter${direction} ${type}Parameter${direction}">\n`;
|
|
content += ` <div class="apiParameterType ${type}ParameterType">\n`;
|
|
content += ` ${linkType(version, param.type)}\n`;
|
|
content += ` </div>\n`;
|
|
|
|
if (param.extendedType) {
|
|
content += ` <div class="apiParameterTypeExtended ${type}ParameterTypeExtended">\n`;
|
|
content += ` ${linkType(version, param.extendedType.type)}\n`;
|
|
content += ` </div>\n`;
|
|
}
|
|
|
|
content += ` <div class="apiParameterDirection ${type}ParameterDirection apiParameterDirection${direction} ${type}ParameterDirection${direction}">\n`;
|
|
|
|
content += ` ${direction}\n`;
|
|
content += ` </div>\n`;
|
|
|
|
if (param.name) {
|
|
content += ` <div class="apiParameterName ${type}ParameterName">\n`;
|
|
content += ` ${param.name}\n`;
|
|
content += ` </div>\n`;
|
|
}
|
|
|
|
content += ` <div class="apiParameterDescription ${type}ParameterDescription">\n`;
|
|
content += ` ${render(version, param.comment)}\n`;
|
|
content += ` </div>\n`;
|
|
content += ` </div>\n`;
|
|
}
|
|
|
|
content += ` </div>\n`;
|
|
|
|
return content;
|
|
}
|
|
|
|
function produceFunctionReturn(version, api, type) {
|
|
let content = "";
|
|
|
|
if (api.returns && api.returns.type && api.returns.type !== 'void') {
|
|
content += `\n`;
|
|
content += ` <h3 class="apiReturnHeader ${type}ReturnHeader">Returns</h3>\n`;
|
|
content += ` <div class="apiReturn ${type}Return">\n`;
|
|
content += ` <div class="apiReturnType ${type}ReturnType">\n`;
|
|
content += ` ${linkType(version, api.returns.type)}\n`;
|
|
content += ` </div>\n`;
|
|
content += ` <div class="apiReturnDescription ${type}ReturnDescription">\n`;
|
|
content += ` ${render(version, api.returns.comment)}\n`;
|
|
content += ` </div>\n`;
|
|
content += ` </div>\n`;
|
|
}
|
|
|
|
return content;
|
|
}
|
|
|
|
function produceSignatureForObject(version, api, type) {
|
|
let content = "";
|
|
|
|
const githubLink = produceGitHubLink(version, api, type);
|
|
|
|
content += `\n`;
|
|
|
|
content += ` <h3 class="apiSignatureHeader ${type}SignatureHeader">Signature</h3>\n`;
|
|
|
|
if (githubLink) {
|
|
content += ` <div class="apiSignatureLink ${type}SignatureLink">\n`;
|
|
content += ` <a href="${githubLink}" target="_blank" rel="noopener">GitHub</a>\n`;
|
|
content += ` </div>\n`;
|
|
}
|
|
|
|
content += ` <div class="apiSignature ${type}Signature">\n`;
|
|
content += ` typedef ${api.referenceName} ${api.name}\n`;
|
|
content += ` </div>\n`;
|
|
|
|
return content;
|
|
}
|
|
|
|
function produceSignatureForStruct(version, api, type) {
|
|
let content = "";
|
|
|
|
const githubLink = produceGitHubLink(version, api, type);
|
|
|
|
content += `\n`;
|
|
|
|
content += ` <h3 class="apiSignatureHeader ${type}SignatureHeader">Signature</h3>\n`;
|
|
|
|
if (githubLink) {
|
|
content += ` <div class="apiSignatureLink ${type}SignatureLink">\n`;
|
|
content += ` <a href="${githubLink}" target="_blank" rel="noopener">GitHub</a>\n`;
|
|
content += ` </div>\n`;
|
|
}
|
|
|
|
const typedef = api.name.startsWith('struct') ? '' : 'typedef ';
|
|
|
|
content += ` <div class="apiSignature ${type}Signature">\n`;
|
|
content += ` ${typedef}struct ${api.name} {\n`;
|
|
|
|
for (const member of api.members || [ ]) {
|
|
content += `<span class="apiSignatureMember ${type}SignatureMember">`;
|
|
content += `${member.type}`;
|
|
content += member.type.endsWith('*') ? '' : ' ';
|
|
|
|
if (member.name) {
|
|
content += `${member.name}`;
|
|
}
|
|
|
|
content += `</span>\n`;
|
|
}
|
|
|
|
content += ` };\n`;
|
|
content += ` </div>\n`;
|
|
|
|
return content;
|
|
}
|
|
|
|
function isOctalEnum(version, api, type) {
|
|
return api.name === 'git_filemode_t';
|
|
}
|
|
|
|
function isFlagsEnum(version, api, type) {
|
|
// TODO: also handle the flags metadata instead of always just guessing
|
|
if (type !== 'enum') {
|
|
return false;
|
|
}
|
|
|
|
let largest = 0;
|
|
|
|
for (const member of api.members) {
|
|
if (member.value === undefined) {
|
|
return false;
|
|
}
|
|
|
|
if (member.value && (member.value & (member.value - 1))) {
|
|
return false;
|
|
}
|
|
|
|
largest = member.value;
|
|
}
|
|
|
|
return (largest > 1);
|
|
}
|
|
|
|
function flagsOctal(v) {
|
|
const n = parseInt(v);
|
|
return n ? `0${n.toString(8)}` : 0;
|
|
}
|
|
|
|
function flagsValue(v) {
|
|
if (v === '0') {
|
|
return '0';
|
|
}
|
|
|
|
return `(1 << ${Math.log2(v)})`;
|
|
}
|
|
|
|
function produceMembers(version, api, type) {
|
|
let content = "";
|
|
let value = 0;
|
|
|
|
if (!api.members || api.members.length == 0) {
|
|
return "";
|
|
}
|
|
|
|
let title = type === 'enum' ? 'Values' : 'Members';
|
|
const isOctal = isOctalEnum(version, api, type);
|
|
const isFlags = isFlagsEnum(version, api, type);
|
|
|
|
content += `\n`;
|
|
|
|
content += ` <h3 class="apiMembersHeader ${type}MembersHeader">${title}</h3>\n`;
|
|
|
|
const githubLink = api.kind === 'struct' ? undefined : produceGitHubLink(version, api, type);
|
|
|
|
if (githubLink) {
|
|
content += ` <div class="apiSignatureLink ${type}SignatureLink">\n`;
|
|
content += ` <a href="${githubLink}" target="_blank" rel="noopener">GitHub</a>\n`;
|
|
content += ` </div>\n`;
|
|
}
|
|
|
|
content += ` <div class="apiMembers ${type}Members">\n`;
|
|
|
|
for (const member of api.members) {
|
|
value = member.value ? member.value : value;
|
|
|
|
content += ` <div class="apiMember ${type}Member">\n`;
|
|
|
|
if (type === 'struct') {
|
|
content += ` <div class="apiMemberType ${type}MemberType">\n`;
|
|
content += ` ${linkType(version, member.type)}\n`;
|
|
content += ` </div>\n`;
|
|
}
|
|
|
|
content += ` <div class="apiMemberName ${type}MemberName">\n`;
|
|
content += ` ${member.name}\n`;
|
|
content += ` </div>\n`;
|
|
|
|
if (type === 'enum') {
|
|
const enumValue = isOctal ? flagsOctal(value) : (isFlags ? flagsValue(value) : value);
|
|
|
|
content += ` <div class="apiMemberValue ${type}MemberValue">\n`;
|
|
content += ` ${enumValue}\n`;
|
|
content += ` </div>\n`;
|
|
}
|
|
|
|
content += ` <div class="apiMemberDescription ${type}MemberDescription">\n`;
|
|
content += ` ${render(version, member.comment)}\n`;
|
|
content += ` </div>\n`;
|
|
content += ` </div>\n`;
|
|
|
|
value++;
|
|
}
|
|
|
|
content += ` </div>\n`;
|
|
|
|
return content;
|
|
}
|
|
|
|
function produceReturnedBy(version, api, type) {
|
|
return produceList(version, api, type, 'returnedBy');
|
|
}
|
|
|
|
function produceParameterTo(version, api, type) {
|
|
return produceList(version, api, type, 'parameterTo');
|
|
}
|
|
|
|
function produceVersionDeltas(version, api, type) {
|
|
let content = '';
|
|
|
|
if (!showVersions) {
|
|
return content;
|
|
}
|
|
|
|
const deltas = versionDeltas[api.name];
|
|
if (!deltas) {
|
|
throw new Error(`no version information for ${api.kind} ${api.name}`);
|
|
}
|
|
|
|
content += ` <h3 class="apiVersionsHeader ${type}VersionsHeader">Versions</h3>\n`;
|
|
content += ` <div class="apiVersions ${type}Versions">\n`;
|
|
content += ` <ul>\n`;
|
|
|
|
for (const idx in deltas) {
|
|
const item = deltas[idx];
|
|
|
|
if (idx == deltas.length - 1) {
|
|
content += ` <li class="apiVersionDeltaIntroduced ${type}VersionDeltaIntroduced">\n`;
|
|
} else if (item.changed) {
|
|
content += ` <li class="apiVersionDeltaChanged ${type}VersionDeltaChanged">\n`;
|
|
} else {
|
|
content += ` <li class="apiVersionDeltaUnchanged ${type}VersionDeltaUnchanged">\n`;
|
|
}
|
|
|
|
content += ` <a href="${linkFor(item.version, item.api)}" class="apiVersionLink ${item.api.kind}VersionLink">${item.version}</a>\n`;
|
|
content += ` </li>\n`;
|
|
}
|
|
|
|
content += ` </ul>\n`;
|
|
content += ` </div>\n`;
|
|
|
|
return content;
|
|
}
|
|
|
|
async function layout(data) {
|
|
let layout;
|
|
|
|
if (options.layout) {
|
|
layout = await fs.readFile(options.layout);
|
|
}
|
|
else if (options.jekyllLayout) {
|
|
layout = `---\ntitle: {{title}}\nlayout: ${options.jekyllLayout}\n---\n\n{{content}}`;
|
|
}
|
|
else {
|
|
return data.content;
|
|
}
|
|
|
|
return layout.toString().replaceAll(/{{([a-z]+)}}/g, (match, p1) => data[p1] || "");
|
|
}
|
|
|
|
function produceSearchArea(version, type) {
|
|
let content = "";
|
|
|
|
content += `\n`;
|
|
content += ` <script src="/js/minisearch.js"></script>\n`;
|
|
content += ` <script src="/js/search.js"></script>\n`;
|
|
|
|
content += ` <div class="headerSearchArea ${type}HeaderSearchArea" id="headersearcharea">\n`;
|
|
content += ` <input class="headerSearchBox ${type}HeaderSearchBox" id="headersearchbox" placeholder="Search..." onInput="handleSearchSuggest({ version: '${version}' })" onFocusIn="handleSearchSuggest({ version: '${version}' })" onKeyUp="if (event.code === 'Enter') { submitSearch({ version: '${version}' }); }"/>\n`;
|
|
content += ` <div class="headerSearchResults ${type}HeaderSearchResults" id="headersearchresults">\n`;
|
|
content += ` </div>\n`;
|
|
content += ` </div>\n`;
|
|
content += `\n`;
|
|
|
|
return content;
|
|
}
|
|
|
|
async function produceDocumentationForApi(version, api, type) {
|
|
let content = "";
|
|
|
|
content += ` <div class="api ${type}">\n`;
|
|
|
|
content += produceBreadcrumb(version, api, type);
|
|
content += produceHeader(version, api, type);
|
|
content += produceDescription(version, api, type);
|
|
content += produceNotes(version, api, type);
|
|
content += produceDeprecations(version, api, type);
|
|
content += produceSeeAlso(version, api, type);
|
|
content += produceWarnings(version, api, type);
|
|
content += produceSignature(version, api, type);
|
|
content += produceMembers(version, api, type);
|
|
content += produceFunctionParameters(version, api, type);
|
|
content += produceFunctionReturn(version, api, type);
|
|
content += produceReturnedBy(version, api, type);
|
|
content += produceParameterTo(version, api, type);
|
|
content += produceVersionDeltas(version, api, type);
|
|
|
|
content += ` </div>\n`;
|
|
|
|
|
|
const name = (type === 'macro' && api.name.includes('(')) ?
|
|
api.name.replace(/\(.*/, '') : api.name;
|
|
|
|
const groupDir = `${outputPath}/${version}/${api.group}`;
|
|
const filename = `${groupDir}/${name}.html`;
|
|
|
|
await fs.mkdir(groupDir, { recursive: true });
|
|
await fs.writeFile(filename, await layout({
|
|
title: `${api.name} (${projectTitle} ${version})`,
|
|
content: content
|
|
}));
|
|
}
|
|
|
|
function selectApi(version, cb) {
|
|
const allApis = allApisForVersion(version, apiData[version]['groups']);
|
|
|
|
for (const name in allApis) {
|
|
const api = allApis[name];
|
|
|
|
if (cb(api)) {
|
|
return api;
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function apiFor(version, type) {
|
|
return selectApi(version, ((api) => api.name === type));
|
|
}
|
|
|
|
function linkFor(version, api) {
|
|
const name = (api.kind === 'macro' && api.name.includes('(')) ?
|
|
api.name.replace(/\(.*/, '') : api.name;
|
|
|
|
return `${linkPrefix}/${version}/${api.group}/${name}.html`;
|
|
}
|
|
|
|
function linkForCode(version, code, text) {
|
|
let api = selectApi(version, ((api) => api.name === code));
|
|
let valueDecl = undefined;
|
|
|
|
const apisForVersion = allApisForVersion(version, apiData[version]['groups']);
|
|
|
|
if (!api) {
|
|
for (const enumDecl of Object.values(apisForVersion).filter(api => api.kind === 'enum')) {
|
|
const member = enumDecl.members.filter((m) => m.name === code);
|
|
|
|
if (member && member[0]) {
|
|
api = enumDecl;
|
|
valueDecl = member[0];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!api) {
|
|
return undefined;
|
|
}
|
|
|
|
const kind = internalKind(version, api);
|
|
let link = linkFor(version, api);
|
|
|
|
if (valueDecl) {
|
|
link += `#${valueDecl.name}`;
|
|
}
|
|
|
|
if (!text) {
|
|
text = `<code>${sanitize(code)}</code>`;
|
|
}
|
|
|
|
return `<a href="${link}" class="apiLink ${kind}Link">${text}</a>`;
|
|
}
|
|
|
|
function linkType(version, given) {
|
|
let type = given;
|
|
|
|
if ((content = given.match(/^(?:const\s+)?([A-Za-z0-9_]+)(?:\s+\*+)?/))) {
|
|
type = content[1];
|
|
}
|
|
|
|
const api = apiFor(version, type);
|
|
|
|
if (api) {
|
|
return `<a href="${linkFor(version, api)}" class="apiLink ${internalKind(version, api)}Link">${given}</a>`;
|
|
}
|
|
|
|
return given;
|
|
}
|
|
|
|
function linkText(version, str) {
|
|
const api = apiFor(version, str);
|
|
|
|
if (api) {
|
|
return `<a href="${linkFor(version, api)}" className="apiLink ${internalKind(version, api)}Link">${str}</a>`;
|
|
}
|
|
|
|
return sanitize(str);
|
|
}
|
|
|
|
function render(version, str) {
|
|
let content = [ ];
|
|
|
|
if (!str) {
|
|
return '';
|
|
}
|
|
|
|
for (const s of Array.isArray(str) ? str : [ str ] ) {
|
|
content.push(markdown.render(s, { __version: version }).replaceAll(/\s+/g, ' '));
|
|
}
|
|
|
|
return content.join(' ');
|
|
}
|
|
|
|
function nowrap(text) {
|
|
text = text.replaceAll(' ', '</span> <span class="codeWord">');
|
|
text = `<span class="codeWord">${text}</span>`;
|
|
return text;
|
|
}
|
|
|
|
function sanitize(str) {
|
|
let content = [ ];
|
|
|
|
if (!str) {
|
|
return '';
|
|
}
|
|
|
|
for (const s of Array.isArray(str) ? str : [ str ] ) {
|
|
content.push(s.replaceAll('&', '&')
|
|
.replaceAll('<', '<')
|
|
.replaceAll('>', '>')
|
|
.replaceAll('{', '{')
|
|
.replaceAll('}', '}'));
|
|
}
|
|
|
|
return content.join(' ');
|
|
}
|
|
|
|
function produceSignatureForAlias(version, api, type) {
|
|
let content = "";
|
|
|
|
const githubLink = produceGitHubLink(version, api, type);
|
|
|
|
content += ` <h3 class="apiSignatureHeader ${type}SignatureHeader">Signature</h3>\n`;
|
|
|
|
if (githubLink) {
|
|
content += ` <div class="apiSignatureLink ${type}SignatureLink">\n`;
|
|
content += ` <a href="${githubLink}" target="_blank" rel="noopener">GitHub</a>\n`;
|
|
content += ` </div>\n`;
|
|
}
|
|
|
|
content += ` <div class="apiSignature ${type}Signature">\n`;
|
|
content += ` typedef ${api.name} ${api.type};`;
|
|
content += ` </div>\n`;
|
|
|
|
return content;
|
|
}
|
|
|
|
function produceSignatureForMacro(version, api, type) {
|
|
let content = "";
|
|
|
|
const githubLink = produceGitHubLink(version, api, type);
|
|
|
|
content += ` <h3 class="apiSignatureHeader ${type}SignatureHeader">Signature</h3>\n`;
|
|
|
|
if (githubLink) {
|
|
content += ` <div class="apiSignatureLink ${type}SignatureLink">\n`;
|
|
content += ` <a href="${githubLink}" target="_blank" rel="noopener">GitHub</a>\n`;
|
|
content += ` </div>\n`;
|
|
}
|
|
|
|
content += ` <div class="apiSignature ${type}Signature">\n`;
|
|
content += ` #define ${api.name} ${sanitize(api.value)}`;
|
|
content += ` </div>\n`;
|
|
|
|
return content;
|
|
}
|
|
|
|
function produceSignature(version, api, type) {
|
|
if (type === 'macro') {
|
|
return produceSignatureForMacro(version, api, type);
|
|
}
|
|
else if (type === 'alias') {
|
|
return produceSignatureForAlias(version, api, type);
|
|
}
|
|
else if (type === 'function' || type === 'callback') {
|
|
return produceSignatureForFunction(version, api, type);
|
|
}
|
|
else if (type === 'object') {
|
|
return produceSignatureForObject(version, api, type);
|
|
}
|
|
else if (type === 'struct') {
|
|
return produceSignatureForStruct(version, api, type);
|
|
}
|
|
else if (type === 'struct' || type === 'enum') {
|
|
return "";
|
|
}
|
|
else {
|
|
throw new Error(`unknown type: ${api.kind}`);
|
|
}
|
|
}
|
|
|
|
function isFunctionPointer(type) {
|
|
return type.match(/^(const\s+)?[A-Za-z0-9_]+\s+\*?\(\*/);
|
|
}
|
|
|
|
function isEnum(type) {
|
|
return type.match(/^enum\s+/);
|
|
}
|
|
|
|
function isStruct(type) {
|
|
return type.match(/^struct\s+/);
|
|
}
|
|
|
|
function internalKind(version, api) {
|
|
if (api.kind === 'struct' && api.opaque) {
|
|
return 'object';
|
|
}
|
|
|
|
return api.kind;
|
|
}
|
|
|
|
function externalKind(kind) {
|
|
if (kind === 'object') {
|
|
return 'struct';
|
|
}
|
|
|
|
return kind;
|
|
}
|
|
|
|
async function produceIndexForGroup(version, group, versionApis) {
|
|
let content = "";
|
|
|
|
if (versionApis['groups'][group].apis.length == 0) {
|
|
return;
|
|
}
|
|
|
|
const apis = Object.values(versionApis['groups'][group].apis);
|
|
|
|
let fileName = group;
|
|
if (fileName.endsWith('.h')) {
|
|
fileName = fileName.substr(0, fileName.length - 2);
|
|
}
|
|
|
|
const system = fileName.startsWith('sys/');
|
|
let groupName = system ? fileName.substr(4) : fileName;
|
|
|
|
content += ` <div class="group">\n`;
|
|
|
|
content += ` <div class="groupBreadcrumb">\n`;
|
|
content += ` <ul>\n`;
|
|
content += ` <li><a href="${linkPrefix}/${version}/index.html">API Documentation</a></li>\n`;
|
|
content += ` </ul>\n`;
|
|
content += ` </div>\n`;
|
|
|
|
content += ` <div class="groupHeader">\n`;
|
|
content += ` <h2 class="groupName">${groupName}</h2>\n`;
|
|
|
|
content += produceSearchArea(version, 'group');
|
|
content += produceVersionPicker(version, "groupHeaderVersionSelect", (v) => {
|
|
if (apiData[v]['groups'][group]) {
|
|
return `${linkPrefix}/${v}/${groupName}/index.html`;
|
|
}
|
|
return undefined;
|
|
});
|
|
|
|
content += ` </div>\n`;
|
|
|
|
let details = undefined;
|
|
|
|
if (versionApis['groups'][group].info?.details) {
|
|
details = markdown.render(versionApis['groups'][group].info.details, { __version: version });
|
|
} else if (versionApis['groups'][group].info?.summary) {
|
|
details = versionApis['groups'][group].info.summary;
|
|
}
|
|
|
|
if (details) {
|
|
content += ` <div class="groupIndexDetails">\n`;
|
|
content += ` ${details}\n`;
|
|
content += ` </div>\n`;
|
|
}
|
|
|
|
for (const kind of [ 'object', 'struct', 'macro', 'enum', 'callback', 'alias', 'function' ]) {
|
|
content += produceIndexForApiKind(version, apis.filter(api => {
|
|
if (kind === 'object') {
|
|
return api.kind === 'struct' && api.opaque;
|
|
}
|
|
else if (kind === 'struct') {
|
|
return api.kind === 'struct' && !api.opaque;
|
|
}
|
|
else {
|
|
return api.kind === kind;
|
|
}
|
|
}), kind);
|
|
}
|
|
|
|
content += ` </div>\n`;
|
|
|
|
const groupsDir = `${outputPath}/${version}/${fileName}`;
|
|
const filename = `${groupsDir}/index.html`;
|
|
|
|
await fs.mkdir(groupsDir, { recursive: true });
|
|
await fs.writeFile(filename, await layout({
|
|
title: `${groupName} APIs (${projectTitle} ${version})`,
|
|
content: content
|
|
}));
|
|
}
|
|
|
|
async function produceDocumentationForApis(version, apiData) {
|
|
const apis = allApisForVersion(version, apiData['groups']);
|
|
|
|
for (const func of Object.values(apis).filter(api => api.kind === 'function')) {
|
|
await produceDocumentationForApi(version, func, 'function');
|
|
}
|
|
|
|
for (const struct of Object.values(apis).filter(api => api.kind === 'struct')) {
|
|
await produceDocumentationForApi(version, struct, internalKind(version, struct));
|
|
}
|
|
|
|
for (const e of Object.values(apis).filter(api => api.kind === 'enum')) {
|
|
await produceDocumentationForApi(version, e, 'enum');
|
|
}
|
|
|
|
for (const callback of Object.values(apis).filter(api => api.kind === 'callback')) {
|
|
await produceDocumentationForApi(version, callback, 'callback');
|
|
}
|
|
|
|
for (const alias of Object.values(apis).filter(api => api.kind === 'alias')) {
|
|
await produceDocumentationForApi(version, alias, 'alias');
|
|
}
|
|
|
|
for (const macro of Object.values(apis).filter(api => api.kind === 'macro')) {
|
|
await produceDocumentationForApi(version, macro, 'macro');
|
|
}
|
|
}
|
|
|
|
function produceIndexForApiKind(version, apis, kind) {
|
|
let content = "";
|
|
|
|
if (!apis || !apis.length) {
|
|
return content;
|
|
}
|
|
|
|
let kindUpper = kind.charAt(0).toUpperCase() + kind.slice(1);
|
|
kindUpper += (kind === 'alias') ? 'es' : 's';
|
|
|
|
content += `\n`;
|
|
content += ` <h3 class="apiListHeader ${kind}ListHeader">${kindUpper}</h3>\n`;
|
|
|
|
content += ` <div class="apiList ${kind}List">\n`;
|
|
|
|
for (const item of apis) {
|
|
if (item.changed) {
|
|
content += ` <div class="apiListItemChanged ${kind}ListItemChanged">\n`;
|
|
} else {
|
|
content += ` <div class="apiListItem ${kind}ListItem">\n`;
|
|
}
|
|
|
|
content += ` <div class="apiListName ${kind}ListName">\n`;
|
|
content += ` <a href="${linkFor(version, item)}">\n`;
|
|
content += ` ${item.name}\n`;
|
|
content += ` </a>\n`;
|
|
content += ` </div>\n`;
|
|
|
|
let shortComment = Array.isArray(item.comment) ? item.comment[0] : item.comment;
|
|
shortComment = shortComment || '';
|
|
|
|
shortComment = shortComment.replace(/\..*/, '');
|
|
|
|
content += ` <div class="apiListItemDescription ${kind}ListItemDescription">\n`;
|
|
content += ` ${render(version, shortComment)}\n`;
|
|
content += ` </div>\n`;
|
|
content += ` </div>\n`;
|
|
}
|
|
|
|
content += ` </div>\n`;
|
|
|
|
return content;
|
|
}
|
|
|
|
function versionIndexContent(version, apiData) {
|
|
let content = "";
|
|
let hasSystem = false;
|
|
|
|
content += ` <div class="version">\n`;
|
|
content += ` <div class="versionHeader">\n`;
|
|
content += ` <h2 class="versionName">${projectTitle} ${version}</h2>\n`;
|
|
|
|
content += produceSearchArea(version, 'version');
|
|
content += produceVersionPicker(version, "versionHeaderVersionSelect",
|
|
(v) => `${linkPrefix}/${v}/index.html`);
|
|
|
|
content += ` </div>\n`;
|
|
|
|
content += `\n`;
|
|
content += ` <h3>Groups</h3>\n`;
|
|
content += ` <ul class="versionIndexStandard">\n`;
|
|
|
|
for (const group of Object.keys(apiData['groups']).sort((a, b) => {
|
|
if (a.startsWith('sys/')) { return 1; }
|
|
if (b.startsWith('sys/')) { return -1; }
|
|
return a.localeCompare(b);
|
|
}).map(fn => {
|
|
let n = fn;
|
|
let sys = false;
|
|
|
|
if (n.endsWith('.h')) {
|
|
n = n.substr(0, n.length - 2);
|
|
}
|
|
|
|
if (n.startsWith('sys/')) {
|
|
n = n.substr(4);
|
|
sys = true;
|
|
}
|
|
|
|
return {
|
|
name: n, filename: fn, system: sys, info: apiData['groups'][fn].info, apis: apiData['groups'][fn]
|
|
};
|
|
}).filter(filedata => {
|
|
return Object.keys(filedata.apis).length > 0 && !fileDenylist.includes(filedata.filename);
|
|
})) {
|
|
if (group.system && !hasSystem) {
|
|
hasSystem = true;
|
|
|
|
content += ` </ul>\n`;
|
|
content += `\n`;
|
|
content += ` <h3>System Groups (Advanced)</h3>\n`;
|
|
content += ` <ul class="versionIndexSystem">\n`;
|
|
}
|
|
|
|
let link = `${linkPrefix}/${version}/`;
|
|
link += group.system ? `sys/` : '';
|
|
link += group.name;
|
|
link += `/index.html`;
|
|
|
|
content += ` <li>\n`;
|
|
content += ` <div class="versionIndexName">\n`;
|
|
content += ` <a href="${link}">\n`;
|
|
content += ` ${group.name}\n`;
|
|
content += ` </a>\n`;
|
|
content += ` </div>\n`;
|
|
|
|
if (group.info?.summary) {
|
|
content += ` <div class="versionIndexSummary">\n`;
|
|
content += ` ${group.info.summary}`;
|
|
content += ` </div>\n`;
|
|
}
|
|
|
|
content += ` </li>\n`;
|
|
}
|
|
|
|
content += ` </ul>\n`;
|
|
|
|
content += ` </div>\n`;
|
|
|
|
return content;
|
|
}
|
|
|
|
async function produceDocumentationIndex(version, apiData) {
|
|
const content = versionIndexContent(version, apiData);
|
|
|
|
const versionDir = `${outputPath}/${version}`;
|
|
const filename = `${versionDir}/index.html`;
|
|
|
|
await fs.mkdir(versionDir, { recursive: true });
|
|
await fs.writeFile(filename, await layout({
|
|
title: `APIs (${projectTitle} ${version})`,
|
|
content: content
|
|
}));
|
|
}
|
|
|
|
async function documentationIsUpToDateForVersion(version, apiData) {
|
|
try {
|
|
const existingMetadata = JSON.parse(await fs.readFile(`${outputPath}/${version}/.metadata`));
|
|
return existingMetadata?.commit === apiData.info.commit;
|
|
}
|
|
catch (e) {
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
async function produceDocumentationMetadata(version, apiData) {
|
|
const versionDir = `${outputPath}/${version}`;
|
|
const filename = `${versionDir}/.metadata`;
|
|
|
|
await fs.mkdir(versionDir, { recursive: true });
|
|
await fs.writeFile(filename, JSON.stringify(apiData.info, null, 2) + "\n");
|
|
}
|
|
|
|
async function cleanupOldDocumentation(version) {
|
|
const versionDir = `${outputPath}/${version}`;
|
|
|
|
for (const fn of await fs.readdir(versionDir)) {
|
|
if (fn === '.metadata') {
|
|
continue;
|
|
}
|
|
|
|
const path = `${versionDir}/${fn}`;
|
|
await fs.rm(path, { recursive: true });
|
|
}
|
|
}
|
|
|
|
async function produceDocumentationForVersion(version, apiData) {
|
|
if (!options.force && await documentationIsUpToDateForVersion(version, apiData)) {
|
|
if (options.verbose) {
|
|
console.log(`Documentation exists for ${version} at version ${apiData.info.commit.substr(0, 7)}; skipping...`);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (options.verbose) {
|
|
console.log(`Producing documentation for ${version}...`);
|
|
}
|
|
|
|
await cleanupOldDocumentation(version);
|
|
|
|
await produceDocumentationForApis(version, apiData);
|
|
|
|
for (const group in apiData['groups']) {
|
|
await produceIndexForGroup(version, group, apiData);
|
|
}
|
|
|
|
await produceDocumentationIndex(version, apiData);
|
|
|
|
await produceDocumentationMetadata(version, apiData);
|
|
}
|
|
|
|
function versionDeltaData(version, api) {
|
|
const base = { version: version, api: api };
|
|
|
|
if (api.kind === 'function') {
|
|
return {
|
|
...base,
|
|
returns: api.returns?.type || 'int',
|
|
params: api.params?.map((p) => p.type) || [ 'void' ]
|
|
};
|
|
}
|
|
else if (api.kind === 'enum') {
|
|
return {
|
|
...base,
|
|
members: api.members?.map((m) => { return { 'name': m.name, 'value': m.value } })
|
|
};
|
|
}
|
|
else if (api.kind === 'callback') {
|
|
return { ...base, };
|
|
}
|
|
else if (api.kind === 'alias') {
|
|
return { ...base, };
|
|
}
|
|
else if (api.kind === 'struct') {
|
|
return {
|
|
...base,
|
|
members: api.members?.map((m) => { return { 'name': m.name, 'type': m.type } })
|
|
};
|
|
}
|
|
else if (api.kind === 'macro') {
|
|
return {
|
|
...base,
|
|
name: api.name,
|
|
value: api.value
|
|
};
|
|
}
|
|
else {
|
|
throw new Error(`unknown api kind: '${api.kind}'`);
|
|
}
|
|
}
|
|
|
|
function deltasEqual(a, b) {
|
|
const unversionedA = { ...a };
|
|
const unversionedB = { ...b };
|
|
|
|
delete unversionedA.version;
|
|
delete unversionedA.api;
|
|
delete unversionedA.changed;
|
|
delete unversionedB.version;
|
|
delete unversionedB.api;
|
|
delete unversionedB.changed;
|
|
|
|
return JSON.stringify(unversionedA) === JSON.stringify(unversionedB);
|
|
}
|
|
|
|
const apiForVersionCache = { };
|
|
function allApisForVersion(version, apiData) {
|
|
if (apiForVersionCache[version]) {
|
|
return apiForVersionCache[version];
|
|
}
|
|
|
|
let result = { };
|
|
for (const file in apiData['groups']) {
|
|
result = { ...result, ...apiData['groups'][file].apis };
|
|
}
|
|
|
|
apiForVersionCache[version] = result;
|
|
return result;
|
|
}
|
|
|
|
function seedVersionApis(apiData) {
|
|
for (const version in apiData) {
|
|
allApisForVersion(version, apiData[version]);
|
|
}
|
|
}
|
|
|
|
function calculateVersionDeltas(apiData) {
|
|
for (const version in apiData) {
|
|
const apisForVersion = allApisForVersion(version, apiData[version]);
|
|
|
|
for (const api in apisForVersion) {
|
|
if (!versionDeltas[api]) {
|
|
versionDeltas[api] = [ ];
|
|
}
|
|
|
|
versionDeltas[api].push(versionDeltaData(version, apisForVersion[api]));
|
|
}
|
|
}
|
|
|
|
for (const api in versionDeltas) {
|
|
const count = versionDeltas[api].length;
|
|
|
|
versionDeltas[api][count - 1].changed = true;
|
|
|
|
for (let i = count - 2; i >= 0; i--) {
|
|
versionDeltas[api][i].changed = !deltasEqual(versionDeltas[api][i], versionDeltas[api][i + 1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function produceSearch(versions) {
|
|
if (options.verbose) {
|
|
console.log(`Producing search page...`);
|
|
}
|
|
|
|
let content = "";
|
|
|
|
content += `<script src="/js/minisearch.js"></script>\n`;
|
|
content += `<script src="/js/search.js"></script>\n`;
|
|
content += `<script src="/js/markdown-it.js"></script>\n`;
|
|
content += `<script>\n`;
|
|
content += ` const markdown = window.markdownit();\n`;
|
|
content += `</script>\n`;
|
|
|
|
content += `\n`;
|
|
|
|
content += ` <div class="search">\n`;
|
|
content += ` <div class="searchHeader">\n`;
|
|
content += ` <h2 class="searchName">libgit2 search</h2>\n`;
|
|
content += ` <div class="searchHeaderVersionSelect">\n`;
|
|
content += ` <span>Version:</span>\n`;
|
|
content += ` <select id="searchversion" onChange="setSearchVersion(this.value)">\n`;
|
|
|
|
for (const version of versions) {
|
|
content += ` <option value="${version}">${version}</option>\n`;
|
|
}
|
|
|
|
content += ` </select>\n`;
|
|
content += ` </div>\n`;
|
|
content += ` </div>\n`;
|
|
|
|
content += `\n`;
|
|
|
|
content += ` <div class="searchSearchBox">\n`;
|
|
content += ` <input type="text" id="bodysearchbox" placeholder="Search..." onKeyDown="if (event.key === 'Enter') { resetSearch(); }" />\n`;
|
|
content += ` <button onClick="resetSearch()">Search</button>\n`;
|
|
content += ` </div>\n`;
|
|
|
|
content += `\n`;
|
|
|
|
content += ` <div class="searchResultsArea" id="bodyresultsarea" style="visibility: hidden;">\n`;
|
|
content += ` <h3>Results</h3>\n`;
|
|
content += ` <div class="searchResults" id="bodysearchresults">\n`;
|
|
content += ` </div>\n`;
|
|
content += ` </div>\n`;
|
|
content += ` </div>\n`;
|
|
|
|
const filename = `${outputPath}/search.html`;
|
|
|
|
await fs.mkdir(outputPath, { recursive: true });
|
|
await fs.writeFile(filename, await layout({
|
|
title: `API search (${projectTitle})`,
|
|
content: content
|
|
}));
|
|
}
|
|
|
|
async function produceMainIndex(versions) {
|
|
const versionDefault = versions[0];
|
|
|
|
if (options.verbose) {
|
|
console.log(`Producing documentation index...`);
|
|
}
|
|
|
|
let content = "";
|
|
|
|
content += `<script>\n`;
|
|
content += `let newLocation = window.location.href;\n`;
|
|
content += `\n`;
|
|
content += `if (newLocation.endsWith('index.html')) {\n`;
|
|
content += ` newLocation = newLocation.substr(0, newLocation.length - 10);\n`;
|
|
content += `}\n`;
|
|
content += `\n`;
|
|
content += `if (!newLocation.endsWith('/')) {\n`;
|
|
content += ` newLocation = newLocation + '/';\n`;
|
|
content += `}\n`;
|
|
content += `\n`;
|
|
content += `newLocation = newLocation + '${versionDefault}/';\n`;
|
|
content += `history.replaceState({}, "APIs (${projectTitle} ${versionDefault})", newLocation);\n`;
|
|
content += `</script>\n`;
|
|
content += `\n`;
|
|
|
|
content += versionIndexContent(versionDefault, apiData[versionDefault]);
|
|
|
|
const filename = `${outputPath}/index.html`;
|
|
|
|
await fs.mkdir(outputPath, { recursive: true });
|
|
await fs.writeFile(filename, await layout({
|
|
title: `APIs (${projectTitle} ${versionDefault})`,
|
|
content: content
|
|
}));
|
|
}
|
|
|
|
function versionSort(a, b) {
|
|
if (a === b) {
|
|
return 0;
|
|
}
|
|
|
|
const aVersion = a.match(/^v(\d+)(?:\.(\d+)(?:\.(\d+)(?:\.(\d+))?)?)?(?:-(.*))?$/);
|
|
const bVersion = b.match(/^v(\d+)(?:\.(\d+)(?:\.(\d+)(?:\.(\d+))?)?)?(?:-(.*))?$/);
|
|
|
|
if (!aVersion && !bVersion) {
|
|
return a.localeCompare(b);
|
|
}
|
|
else if (aVersion && !bVersion) {
|
|
return -1;
|
|
}
|
|
else if (!aVersion && bVersion) {
|
|
return 1;
|
|
}
|
|
|
|
for (let i = 1; i < 5; i++) {
|
|
if (!aVersion[i] && !bVersion[i]) {
|
|
break;
|
|
}
|
|
else if (aVersion[i] && !bVersion[i]) {
|
|
return 1;
|
|
}
|
|
else if (!aVersion[i] && bVersion[i]) {
|
|
return -1;
|
|
}
|
|
else if (aVersion[i] !== bVersion[i]) {
|
|
return aVersion[i] - bVersion[i];
|
|
}
|
|
}
|
|
|
|
if (aVersion[5] && !bVersion[5]) {
|
|
return -1;
|
|
}
|
|
else if (!aVersion[5] && bVersion[5]) {
|
|
return 1;
|
|
}
|
|
else if (aVersion[5] && bVersion[5]) {
|
|
return aVersion[5].localeCompare(bVersion[5]);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
program.option('--output <filename>')
|
|
.option('--layout <filename>')
|
|
.option('--jekyll-layout <name>')
|
|
.option('--version <version...>')
|
|
.option('--verbose')
|
|
.option('--force')
|
|
.option('--strict');
|
|
program.parse();
|
|
|
|
const options = program.opts();
|
|
|
|
if (program.args.length != 2) {
|
|
console.error(`usage: ${path.basename(process.argv[1])} raw_api_dir output_dir`);
|
|
process.exit(1);
|
|
}
|
|
|
|
const docsPath = program.args[0];
|
|
const outputPath = program.args[1];
|
|
|
|
(async () => {
|
|
try {
|
|
const v = options.version ? options.version :
|
|
(await fs.readdir(docsPath))
|
|
.filter(a => a.endsWith('.json'))
|
|
.map(a => a.replace(/\.json$/, ''));
|
|
|
|
versions.push(...v.sort(versionSort).reverse());
|
|
|
|
for (const version of versions) {
|
|
if (options.verbose) {
|
|
console.log(`Reading documentation data for ${version}...`);
|
|
}
|
|
|
|
apiData[version] = JSON.parse(await fs.readFile(`${docsPath}/${version}.json`));
|
|
}
|
|
|
|
if (showVersions) {
|
|
if (options.verbose) {
|
|
console.log(`Calculating version deltas...`);
|
|
}
|
|
|
|
calculateVersionDeltas(apiData);
|
|
}
|
|
|
|
for (const version of versions) {
|
|
await produceDocumentationForVersion(version, apiData[version]);
|
|
}
|
|
|
|
await produceSearch(versions);
|
|
await produceMainIndex(versions);
|
|
} catch (e) {
|
|
console.error(e);
|
|
process.exit(1);
|
|
}
|
|
})();
|