sass/tool/tangle.ts
2023-04-27 13:50:53 -07:00

63 lines
1.9 KiB
TypeScript

// The literate programming "tangle" tool: takes literate programming files (in
// this case, Markdown files with embedded TypeScript) and converts them to the
// corresponding physical TypeScript files.
import * as colors from 'colors/safe';
import * as crypto from 'crypto';
import * as fs from 'fs';
import * as glob from 'glob';
import {marked} from 'marked';
import * as p from 'path';
import * as prettier from 'prettier';
const args = process.argv.slice(2);
const files = glob.sync(args.length === 0 ? '**/*.d.ts.md' : args, {
ignore: ['node_modules/**/*.d.ts.md'],
});
(async () => {
for (const file of files) {
if (!file.endsWith('.md')) {
console.error(
`${colors.red(colors.bold('Error:'))} Can't tangle non-.md file ` +
`"${file}".`
);
process.exitCode = 1;
continue;
}
const outputFile = file.substring(0, file.length - 3);
const outputType = p.extname(outputFile).substring(1);
if (outputType === '') {
console.error(
`${colors.yellow(colors.bold('Warning:'))} File "${file}" has no ` +
"additional extension, so there's nothing to tangle it to."
);
}
const source = fs.readFileSync(file, 'utf8');
const hash = crypto.createHash('sha256').update(source).digest('hex');
const codeBlocks: string[] = [`// <[tangle hash]> ${hash}`];
marked.parse(source, {
walkTokens: token => {
if (token.type === 'code' && token.lang === outputType) {
codeBlocks.push(token.text);
codeBlocks.push('// ==<[tangle boundary]>==');
}
},
});
codeBlocks.push('');
const config = (await prettier.resolveConfig(outputFile)) ?? {};
fs.writeFileSync(
outputFile,
prettier.format(codeBlocks.join('\n'), {
filepath: outputFile,
...config,
})
);
console.log(colors.grey(`Tangled ${file} to ${outputFile}`));
}
})();