Something went wrong. Try again.
yet another localization library
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116#!/usr/bin/env nodeimport { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync,} from 'node:fs';import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
import { run } from '@optique/run';
import { compile, CompileError, formatDiagnostic } from './compiler.ts';import { parser } from './config.ts';
// permits replacing a non-empty output directory.const MARKER = '.polyglot';
const MARKER_TEXT = 'generated by @oomfware/polyglot; this directory is replaced on every run.\n';
const polyFiles = (dir: string): string[] => readdirSync(dir, { recursive: true, withFileTypes: true }) .filter((entry) => entry.isFile() && entry.name.endsWith('.poly')) .map((entry) => join(entry.parentPath, entry.name));
// generated modules import the functions module relative to the output root.const functionsSpecifier = ({ file, output }: { file: string; output: string }): string => { const path = relative(output, resolve(file)).split(sep).join('/'); return path.startsWith('../') ? path : `./${path}`;};
const isWithin = (parent: string, child: string): boolean => { const path = relative(parent, child); return path === '' || (path !== '..' && !path.startsWith(`..${sep}`) && !isAbsolute(path));};
const checkOutput = (output: string, sources: string[]): void => { for (const source of sources) { if (isWithin(output, resolve(source))) { throw new Error(`refusing to replace ${output}: it contains the input ${source}`); } } const stats = statSync(output, { throwIfNoEntry: false }); if (stats === undefined) { return; } if (!stats.isDirectory()) { throw new Error(`refusing to replace ${output}: it is not a directory`); } if (readdirSync(output).length > 0 && !existsSync(join(output, MARKER))) { throw new Error( `refusing to replace ${output}: non-empty directory without a ${MARKER} marker; choose a new or empty directory`, ); }};
// Optique handles help and argument errors by exiting.const config = run(parser, { help: 'option', programName: 'polyglot' });
try { const files = polyFiles(config.input); if (files.length === 0) { throw new Error(`no .poly files found in ${config.input}`); } const output = resolve(config.output); checkOutput(output, config.functions === undefined ? files : [...files, config.functions]); // preserve existing output if compilation fails. const generated = compile({ baseLocale: config.baseLocale, functions: config.functions === undefined ? undefined : functionsSpecifier({ file: config.functions, output }), resources: files.map((file) => ({ path: file, text: readFileSync(file, 'utf8') })), }); // stage writes on the same filesystem for renaming; write failures preserve existing output. mkdirSync(dirname(output), { recursive: true }); const staging = mkdtempSync(join(dirname(output), `.${basename(output)}-`)); try { for (const file of generated.files) { const target = join(staging, file.path); mkdirSync(dirname(target), { recursive: true }); writeFileSync(target, file.content); } writeFileSync(join(staging, MARKER), MARKER_TEXT); } catch (error) { rmSync(staging, { force: true, recursive: true }); throw error; } // replace the whole directory to remove stale modules; delete the old output after the swap. const previous = `${staging}-previous`; const hadOutput = existsSync(output); if (hadOutput) { renameSync(output, previous); } renameSync(staging, output); if (hadOutput) { rmSync(previous, { force: true, recursive: true }); } for (const warning of generated.warnings) { process.stderr.write(`${formatDiagnostic(warning, { stream: process.stderr })}\n\n`); } process.stdout.write( `polyglot: generated ${generated.files.length} files from ${files.length} resources into ${config.output}\n`, );} catch (error) { if (error instanceof CompileError) { process.stderr.write(`${formatDiagnostic(error, { stream: process.stderr })}\n`); } else { process.stderr.write(`polyglot: ${error instanceof Error ? error.message : String(error)}\n`); } process.exitCode = 1;}