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",
"author": "Tony Garnock-Jones <tonyg@leastfixedpoint.com>",
"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",
"prepare": "tsc && rollup -c",
"rollupwatch": "rollup -c -w",

View File

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

View File

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

View File

@ -7,12 +7,11 @@ import { expandInputGlob, formatFailures } from './cli-utils';
export type CommandLineArguments = {
inputs: string[];
base: string | undefined;
bundle: boolean;
};
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) {
failures.push({ type: 'error', file: null, detail: {
@ -41,14 +40,12 @@ export function main(argv: Array<string>) {
new Command()
.arguments('[input...]')
.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('--base <directory>', 'Base directory for sources (default: common prefix)')
.action((inputs: string[], rawOptions) => {
const options: CommandLineArguments = {
inputs: inputs.map(i => path.normalize(i)),
base: rawOptions.base,
bundle: rawOptions.bundle,
};
Error.stackTraceLimit = Infinity;