diff --git a/implementations/javascript/packages/schema/package.json b/implementations/javascript/packages/schema/package.json index 16ddcf4..f646854 100644 --- a/implementations/javascript/packages/schema/package.json +++ b/implementations/javascript/packages/schema/package.json @@ -13,7 +13,7 @@ "types": "lib/index.d.ts", "author": "Tony Garnock-Jones ", "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", diff --git a/implementations/javascript/packages/schema/src/bin/cli-utils.ts b/implementations/javascript/packages/schema/src/bin/cli-utils.ts index abbc1bb..97a8939 100644 --- a/implementations/javascript/packages/schema/src/bin/cli-utils.ts +++ b/implementations/javascript/packages/schema/src/bin/cli-utils.ts @@ -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, inputFiles: Array, xrefFiles: Array, failures: Array, @@ -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 = []; + const baseDirs = new IdentitySet(); 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, }; } diff --git a/implementations/javascript/packages/schema/src/bin/preserves-schema-ts.ts b/implementations/javascript/packages/schema/src/bin/preserves-schema-ts.ts index bfb5d74..8289e83 100644 --- a/implementations/javascript/packages/schema/src/bin/preserves-schema-ts.ts +++ b/implementations/javascript/packages/schema/src/bin/preserves-schema-ts.ts @@ -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, failures: Array, - base: string, - output: string, + baseDirs: Array, }; 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 = 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) { diff --git a/implementations/javascript/packages/schema/src/bin/preserves-schemac.ts b/implementations/javascript/packages/schema/src/bin/preserves-schemac.ts index eec9fa2..0c3025f 100644 --- a/implementations/javascript/packages/schema/src/bin/preserves-schemac.ts +++ b/implementations/javascript/packages/schema/src/bin/preserves-schemac.ts @@ -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) { 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 ', '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;