Dumber but more reliable and more useful handling of schema input base directories

This commit is contained in:
Tony Garnock-Jones 2021-12-13 12:52:24 +01:00
parent bfa0b0bcb4
commit 944ac54414
4 changed files with 56 additions and 38 deletions

View File

@ -13,7 +13,7 @@
"types": "lib/index.d.ts", "types": "lib/index.d.ts",
"author": "Tony Garnock-Jones <tonyg@leastfixedpoint.com>", "author": "Tony Garnock-Jones <tonyg@leastfixedpoint.com>",
"scripts": { "scripts": {
"regenerate": "rm -rf ./src/gen && ./bin/preserves-schema-ts.js --output ./src/gen ../../../../schema/schema.prs", "regenerate": "rm -rf ./src/gen && ./bin/preserves-schema-ts.js --output ./src/gen ../../../../schema:schema.prs",
"clean": "rm -rf lib dist", "clean": "rm -rf lib dist",
"prepare": "tsc && rollup -c", "prepare": "tsc && rollup -c",
"rollupwatch": "rollup -c -w", "rollupwatch": "rollup -c -w",

View File

@ -1,7 +1,7 @@
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { glob } from 'glob'; import { glob } from 'glob';
import { formatPosition, Position } from '@preserves/core'; import { IdentitySet, formatPosition, Position } from '@preserves/core';
import { readSchema } from '../reader'; import { readSchema } from '../reader';
import chalk from 'chalk'; import chalk from 'chalk';
import * as M from '../meta'; import * as M from '../meta';
@ -13,6 +13,7 @@ export interface Diagnostic {
}; };
export type InputFile = { export type InputFile = {
inputBaseDir: string,
inputFilePath: string, inputFilePath: string,
text: string, text: string,
baseRelPath: string, baseRelPath: string,
@ -28,7 +29,7 @@ export type XrefFile = {
}; };
export type Expanded = { export type Expanded = {
base: string, baseDirs: Array<string>,
inputFiles: Array<InputFile>, inputFiles: Array<InputFile>,
xrefFiles: Array<XrefFile>, xrefFiles: Array<XrefFile>,
failures: Array<Diagnostic>, failures: Array<Diagnostic>,
@ -54,18 +55,29 @@ export function computeBase(paths: string[]): string {
} }
} }
export function expandInputGlob( export function findSchemas(userInput: string): ({
input: string[], names: string[],
xrefs: string[], base: string,
base0: string | undefined, }) {
): Expanded { const colonPos = userInput.lastIndexOf(':');
const matches = input.flatMap(i => glob.sync(i)); let [base, g] = (colonPos === -1)
? [userInput, '**/*.prs']
: [userInput.slice(0, colonPos), userInput.slice(colonPos + 1)];
if (base[base.length - 1] !== '/') {
base = base + '/';
}
const names = glob.sync(base + g);
return { names, base };
}
export function expandInputGlob(input: string[], xrefs: string[]): Expanded {
const failures: Array<Diagnostic> = []; const failures: Array<Diagnostic> = [];
const baseDirs = new IdentitySet<string>();
function processInputFile(base: string, filePath: string, xref: true): XrefFile[]; function processInputFile(base: string, filePath: string, xref: true): XrefFile[];
function processInputFile(base: string, filePath: string, xref: false): InputFile[]; function processInputFile(base: string, filePath: string, xref: false): InputFile[];
function processInputFile(base: string, filePath: string, xref: boolean): (InputFile | XrefFile)[] { function processInputFile(base: string, filePath: string, xref: boolean): (InputFile | XrefFile)[] {
if (!xref && !filePath.startsWith(base)) { if (!filePath.startsWith(base)) {
throw new Error(`Input filename ${filePath} falls outside base ${base}`); throw new Error(`Input filename ${filePath} falls outside base ${base}`);
} }
try { try {
@ -83,7 +95,7 @@ export function expandInputGlob(
if (xref) { if (xref) {
return [{ xrefFilePath: filePath, text, modulePath, schema }]; return [{ xrefFilePath: filePath, text, modulePath, schema }];
} else { } else {
return [{ inputFilePath: filePath, text, baseRelPath, modulePath, schema }]; return [{ inputBaseDir: base, inputFilePath: filePath, text, baseRelPath, modulePath, schema }];
} }
} catch (e) { } catch (e) {
failures.push({ type: 'error', file: filePath, detail: e as Error }); failures.push({ type: 'error', file: filePath, detail: e as Error });
@ -91,16 +103,22 @@ export function expandInputGlob(
} }
} }
const base = base0 ?? computeBase(matches); const inputFiles = input.flatMap(userInput => {
const { names, base } = findSchemas(userInput);
baseDirs.add(base);
return names.flatMap(f => processInputFile(base, f, false));
});
const xrefFiles = xrefs.flatMap(userInput => {
const { names, base } = findSchemas(userInput);
baseDirs.add(base);
return names.flatMap(f => processInputFile(base, f, true));
});
return { return {
base, baseDirs: Array.from(baseDirs),
inputFiles: matches.flatMap(f => processInputFile(base, f, false)), inputFiles,
xrefFiles: xrefs.flatMap(i => { xrefFiles,
const names = glob.sync(i);
const base = computeBase(names);
return names.flatMap(f => processInputFile(base, f, true));
}),
failures, failures,
}; };
} }

View File

@ -13,7 +13,7 @@ export type CommandLineArguments = {
inputs: string[]; inputs: string[];
xrefs: string[]; xrefs: string[];
base: string | undefined; base: string | undefined;
output: string | undefined; output: string;
stdout: boolean; stdout: boolean;
core: string; core: string;
watch: boolean; watch: boolean;
@ -25,11 +25,11 @@ export type CompilationResult = {
options: CommandLineArguments, options: CommandLineArguments,
inputFiles: Array<TranslatedFile>, inputFiles: Array<TranslatedFile>,
failures: Array<Diagnostic>, failures: Array<Diagnostic>,
base: string, baseDirs: Array<string>,
output: string,
}; };
export type TranslatedFile = { export type TranslatedFile = {
inputBaseDir: string,
inputFilePath: string, inputFilePath: string,
outputFilePath: string, outputFilePath: string,
schemaPath: M.ModulePath, schemaPath: M.ModulePath,
@ -62,14 +62,18 @@ export function run(options: CommandLineArguments): void {
chalk.greenBright('successfully'); chalk.greenBright('successfully');
console.log(chalk.gray(new Date().toISOString()) + console.log(chalk.gray(new Date().toISOString()) +
` Processed ${r.inputFiles.length} file(s) ${errorSummary}. Waiting for changes.`); ` Processed ${r.inputFiles.length} file(s) ${errorSummary}. Waiting for changes.`);
const watcher = chokidar.watch(r.base, { let triggered = false;
const watchers = r.baseDirs.map(base => chokidar.watch(base, {
ignoreInitial: true, ignoreInitial: true,
}).on('all', (_event, filename) => { }).on('all', (_event, filename) => {
if (options.inputs.some(i => minimatch(filename, i))) { if (options.inputs.some(i => minimatch(filename, i))) {
watcher.close(); if (!triggered) {
runWatch(); triggered = true;
watchers.map(w => w.close());
runWatch();
}
} }
}); }));
} }
runWatch(); runWatch();
} }
@ -86,9 +90,8 @@ function isAbsoluteOrExplicitlyRelative(p: string) {
} }
export function runOnce(options: CommandLineArguments): CompilationResult { export function runOnce(options: CommandLineArguments): CompilationResult {
const { base, failures, inputFiles: inputFiles0, xrefFiles: xrefFiles0 } = const { baseDirs, failures, inputFiles: inputFiles0, xrefFiles: xrefFiles0 } =
expandInputGlob(options.inputs, options.xrefs, options.base); expandInputGlob(options.inputs, options.xrefs);
const output = options.output ?? base;
let xrefFiles = xrefFiles0; // filtered during construction of extensionEnv let xrefFiles = xrefFiles0; // filtered during construction of extensionEnv
@ -112,9 +115,9 @@ export function runOnce(options: CommandLineArguments): CompilationResult {
}); });
const inputFiles: Array<TranslatedFile> = inputFiles0.map(i => { const inputFiles: Array<TranslatedFile> = inputFiles0.map(i => {
const { inputFilePath, baseRelPath, modulePath, schema } = i; const { inputBaseDir, inputFilePath, baseRelPath, modulePath, schema } = i;
const outputFilePath = path.join(output, changeExt(baseRelPath, '.ts')); const outputFilePath = path.join(options.output, changeExt(baseRelPath, '.ts'));
return { inputFilePath, outputFilePath, schemaPath: modulePath, schema }; return { inputBaseDir, inputFilePath, outputFilePath, schemaPath: modulePath, schema };
}); });
inputFiles.forEach(c => { inputFiles.forEach(c => {
@ -164,7 +167,7 @@ export function runOnce(options: CommandLineArguments): CompilationResult {
formatFailures(failures, options.traceback); formatFailures(failures, options.traceback);
return { options, inputFiles, failures, base, output }; return { options, inputFiles, failures, baseDirs };
} }
export function main(argv: Array<string>) { export function main(argv: Array<string>) {

View File

@ -7,12 +7,11 @@ import { expandInputGlob, formatFailures } from './cli-utils';
export type CommandLineArguments = { export type CommandLineArguments = {
inputs: string[]; inputs: string[];
base: string | undefined;
bundle: boolean; bundle: boolean;
}; };
export function run(options: CommandLineArguments): void { export function run(options: CommandLineArguments): void {
const { failures, inputFiles } = expandInputGlob(options.inputs, [], options.base); const { failures, inputFiles } = expandInputGlob(options.inputs, []);
if (!options.bundle && inputFiles.length !== 1) { if (!options.bundle && inputFiles.length !== 1) {
failures.push({ type: 'error', file: null, detail: { failures.push({ type: 'error', file: null, detail: {
@ -41,14 +40,12 @@ export function main(argv: Array<string>) {
new Command() new Command()
.arguments('[input...]') .arguments('[input...]')
.description('Compile textual Preserves schema definitions to binary format', { .description('Compile textual Preserves schema definitions to binary format', {
input: 'Input filename or glob', input: 'Input directory, with optional ":glob" appended (defaults to ":**/*.prs")',
}) })
.option('--no-bundle', 'Emit a single Schema instead of a schema Bundle') .option('--no-bundle', 'Emit a single Schema instead of a schema Bundle')
.option('--base <directory>', 'Base directory for sources (default: common prefix)')
.action((inputs: string[], rawOptions) => { .action((inputs: string[], rawOptions) => {
const options: CommandLineArguments = { const options: CommandLineArguments = {
inputs: inputs.map(i => path.normalize(i)), inputs: inputs.map(i => path.normalize(i)),
base: rawOptions.base,
bundle: rawOptions.bundle, bundle: rawOptions.bundle,
}; };
Error.stackTraceLimit = Infinity; Error.stackTraceLimit = Infinity;