spiegel-keyman/core/tools/api-header-extractor/src/index.ts
Marc Durdin ac6eb0b767 chore: use explicit node: prefix for node imports
This makes it easier for us to enumerate modules that have node
dependencies. There may be others I have not yet picked up.
2025-04-03 08:01:08 +07:00

140 lines
No EOL
3 KiB
TypeScript

import * as fs from 'node:fs';
import * as path from 'node:path';
// TODO: support multiple infiles to generate from multiple headers
if(process.argv.length < 4) {
console.error('Invalid parameters');
console.log('Usage: node index.js <srcfile> <dstPath>');
process.exit(1);
}
const src = process.argv[2];
const dst = process.argv[3];
interface ApiEndpoint {
filename: string;
type: string;
endpoint: string;
};
interface DocFile {
filename: string;
lines: string[];
};
const lines = fs.readFileSync(src, 'utf-8').replace(/\r/g, '').split('\n');
let result: string[] = [], apiEndpoints: ApiEndpoint[] = [];
let files: DocFile[] = [];
let filename: string = null;
type ProcessorMode = 'preamble' | 'filename' | 'title' | 'content' | 'eof';
let mode: ProcessorMode = 'preamble';
const processor = {
preamble: (line: string) => {
if(line.trim() == '---') {
mode = 'filename';
}
},
filename: (line: string) => {
if(!line.trim().startsWith('filename:')) {
throw new Error(`Expecting line to start with "filename:" but instead got "${line}"`);
}
cacheResult();
filename = line.split(':', 2)[1].trim();
mode = 'title';
result = [
'---' // first line of markdown title section
];
},
title: (line: string) => {
if(line.trim() == '---') {
mode = 'content';
}
result.push(line);
},
content: (line: string) => {
if(line.trim() == '---') {
mode = 'filename';
return;
}
if(line.trim() == '$EOF') {
mode = 'eof';
cacheResult();
return;
}
if(line.trim() == '/*' || line.trim() == '*/') {
// TODO: is this safe?
return;
}
if(line.trim() == '```c */') {
result.push('```c');
return;
}
const m = line.match(/^(#+) (km_core_[a-z0-9_]+)(\(\)| .+)$/i);
if(m) {
// API entry, need to capture as a link ref
apiEndpoints.push({filename, endpoint: m[2], type: m[3] == '()' ? 'function' : m[3].trim()});
// Rewrite the heading to include an anchor
line = `${m[1]} ${m[2]}${m[3]} {#${m[2]}}`;
}
result.push(line);
},
eof: (line: string) => {
}
}
function cacheResult() {
if(filename != null && result.length > 1) {
files.push({
filename,
lines: result
});
}
}
for(const line of lines) {
// console.log(mode+'\t'+line);
processor[mode](line);
}
cacheResult();
console.dir(apiEndpoints);
// Build heading references
const refs = buildRefs();
// Write out
for(const file of files) {
writeFile(file, refs);
}
function writeFile(file: DocFile, refs: string) {
fs.writeFileSync(path.join(dst, file.filename), file.lines.join('\n') + '\n\n' + refs, 'utf-8');
}
function buildRefs() {
let result = [];
for(let ep of apiEndpoints) {
const filename = ep.filename.substring(0,ep.filename.length-3); // Strip .md, rough but ready
result.push(`[${ep.endpoint}]: ${filename}#${ep.endpoint} "${ep.endpoint} ${ep.type}"`);
}
return result.join('\n');
}