Enhance preserves-schemac to emit bundles
This commit is contained in:
parent
55fab35073
commit
3ad56a5275
|
@ -0,0 +1,86 @@
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { glob } from 'glob';
|
||||||
|
import { formatPosition, Position } from '@preserves/core';
|
||||||
|
import { readSchema } from '../reader';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
|
||||||
|
export interface Diagnostic {
|
||||||
|
type: 'warn' | 'error';
|
||||||
|
file: string | null;
|
||||||
|
detail: Error | { message: string, pos: Position | null };
|
||||||
|
};
|
||||||
|
|
||||||
|
export function computeBase(paths: string[]): string {
|
||||||
|
if (paths.length === 0) {
|
||||||
|
return '';
|
||||||
|
} else if (paths.length === 1) {
|
||||||
|
const d = path.dirname(paths[0]);
|
||||||
|
return (d === '.') ? '' : d + '/';
|
||||||
|
} else {
|
||||||
|
let i = 0;
|
||||||
|
while (true) {
|
||||||
|
let ch: string | null = null
|
||||||
|
for (const p of paths) {
|
||||||
|
if (i >= p.length) return p.slice(0, i);
|
||||||
|
if (ch === null) ch = p[i];
|
||||||
|
if (p[i] !== ch) return p.slice(0, i);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function expandInputGlob(input: string, base0: string | undefined) {
|
||||||
|
const matches = glob.sync(input);
|
||||||
|
const base = base0 ?? computeBase(matches);
|
||||||
|
const failures: Array<Diagnostic> = [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
base,
|
||||||
|
inputFiles: matches.flatMap(inputFilePath => {
|
||||||
|
if (!inputFilePath.startsWith(base)) {
|
||||||
|
throw new Error(`Input filename ${inputFilePath} falls outside base ${base}`);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const text = fs.readFileSync(inputFilePath, 'utf-8');
|
||||||
|
const baseRelPath = inputFilePath.slice(base.length);
|
||||||
|
const modulePath = baseRelPath.split('/').map(p => p.split('.')[0]).map(Symbol.for);
|
||||||
|
const schema = readSchema(text, {
|
||||||
|
name: inputFilePath,
|
||||||
|
readInclude(includePath: string): string {
|
||||||
|
return fs.readFileSync(
|
||||||
|
path.resolve(path.dirname(inputFilePath), includePath),
|
||||||
|
'utf-8');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return [{ inputFilePath, text, baseRelPath, modulePath, schema }];
|
||||||
|
} catch (e) {
|
||||||
|
failures.push({ type: 'error', file: inputFilePath, detail: e });
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
failures,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function changeExt(p: string, newext: string): string {
|
||||||
|
return p.slice(0, -path.extname(p).length) + newext;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatFailures(failures: Array<Diagnostic>, traceback = false): void {
|
||||||
|
for (const d of failures) {
|
||||||
|
console.log(
|
||||||
|
(d.type === 'error' ? chalk.redBright('[ERROR]') : chalk.yellowBright('[WARNING]'))
|
||||||
|
+ ' '
|
||||||
|
+ chalk.blueBright(formatPosition((d.detail as any).pos ?? d.file))
|
||||||
|
+ ': '
|
||||||
|
+ d.detail.message
|
||||||
|
+ (traceback && (d.detail instanceof Error)
|
||||||
|
? '\n' + d.detail.stack
|
||||||
|
: ''));
|
||||||
|
}
|
||||||
|
if (failures.length > 0) {
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,13 @@
|
||||||
import { compile, readSchema } from '../index';
|
import { compile } from '../index';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { glob } from 'glob';
|
|
||||||
import minimatch from 'minimatch';
|
import minimatch from 'minimatch';
|
||||||
import yargs from 'yargs/yargs';
|
import yargs from 'yargs/yargs';
|
||||||
import * as M from '../meta';
|
import * as M from '../meta';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { formatPosition, Position } from '@preserves/core';
|
import { Position } from '@preserves/core';
|
||||||
import chokidar from 'chokidar';
|
import chokidar from 'chokidar';
|
||||||
|
import { changeExt, Diagnostic, expandInputGlob, formatFailures } from './cli-utils';
|
||||||
|
|
||||||
export type CommandLineArguments = {
|
export type CommandLineArguments = {
|
||||||
input: string;
|
input: string;
|
||||||
|
@ -20,12 +20,6 @@ export type CommandLineArguments = {
|
||||||
module: string[];
|
module: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface Diagnostic {
|
|
||||||
type: 'warn' | 'error';
|
|
||||||
file: string;
|
|
||||||
detail: Error | { message: string, pos: Position | null };
|
|
||||||
};
|
|
||||||
|
|
||||||
export type CompilationResult = {
|
export type CompilationResult = {
|
||||||
options: CommandLineArguments,
|
options: CommandLineArguments,
|
||||||
inputFiles: Array<InputFile>,
|
inputFiles: Array<InputFile>,
|
||||||
|
@ -41,25 +35,6 @@ export type InputFile = {
|
||||||
schema: M.Schema,
|
schema: M.Schema,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function computeBase(paths: string[]): string {
|
|
||||||
if (paths.length === 0) {
|
|
||||||
return '';
|
|
||||||
} else if (paths.length === 1) {
|
|
||||||
return path.dirname(paths[0]) + '/';
|
|
||||||
} else {
|
|
||||||
let i = 0;
|
|
||||||
while (true) {
|
|
||||||
let ch: string | null = null
|
|
||||||
for (const p of paths) {
|
|
||||||
if (i >= p.length) return p.slice(0, i);
|
|
||||||
if (ch === null) ch = p[i];
|
|
||||||
if (p[i] !== ch) return p.slice(0, i);
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function failureCount(type: 'warn' | 'error', r: CompilationResult): number {
|
function failureCount(type: 'warn' | 'error', r: CompilationResult): number {
|
||||||
return r.failures.filter(f => f.type === type).length;
|
return r.failures.filter(f => f.type === type).length;
|
||||||
}
|
}
|
||||||
|
@ -99,10 +74,6 @@ export function run(options: CommandLineArguments): void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeExt(p: string, newext: string): string {
|
|
||||||
return p.slice(0, -path.extname(p).length) + newext;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function modulePathTo(file1: string, file2: string): string | null {
|
export function modulePathTo(file1: string, file2: string): string | null {
|
||||||
if (file1 === file2) return null;
|
if (file1 === file2) return null;
|
||||||
let naive = path.relative(path.dirname(file1), file2);
|
let naive = path.relative(path.dirname(file1), file2);
|
||||||
|
@ -111,10 +82,8 @@ export function modulePathTo(file1: string, file2: string): string | null {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function runOnce(options: CommandLineArguments): CompilationResult {
|
export function runOnce(options: CommandLineArguments): CompilationResult {
|
||||||
const matches = glob.sync(options.input);
|
const { base, failures, inputFiles: inputFiles0 } =
|
||||||
const failures: Array<Diagnostic> = [];
|
expandInputGlob(options.input, options.base);
|
||||||
|
|
||||||
const base = options.base ?? computeBase(matches);
|
|
||||||
const output = options.output ?? base;
|
const output = options.output ?? base;
|
||||||
|
|
||||||
const extensionEnv: M.Environment = options.module.map(arg => {
|
const extensionEnv: M.Environment = options.module.map(arg => {
|
||||||
|
@ -129,28 +98,10 @@ export function runOnce(options: CommandLineArguments): CompilationResult {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const inputFiles: Array<InputFile> = matches.flatMap(inputFilePath => {
|
const inputFiles: Array<InputFile> = inputFiles0.map(i => {
|
||||||
if (!inputFilePath.startsWith(base)) {
|
const { inputFilePath, baseRelPath, modulePath, schema } = i;
|
||||||
throw new Error(`Input filename ${inputFilePath} falls outside base ${base}`);
|
const outputFilePath = path.join(output, changeExt(baseRelPath, '.ts'));
|
||||||
}
|
return { inputFilePath, outputFilePath, schemaPath: modulePath, schema };
|
||||||
const relPath = inputFilePath.slice(base.length);
|
|
||||||
const outputFilePath = path.join(output, changeExt(relPath, '.ts'));
|
|
||||||
try {
|
|
||||||
const src = fs.readFileSync(inputFilePath, 'utf-8');
|
|
||||||
const schema = readSchema(src, {
|
|
||||||
name: inputFilePath,
|
|
||||||
readInclude(includePath: string): string {
|
|
||||||
return fs.readFileSync(
|
|
||||||
path.resolve(path.dirname(inputFilePath), includePath),
|
|
||||||
'utf-8');
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const schemaPath = relPath.split('/').map(p => p.split('.')[0]).map(Symbol.for);
|
|
||||||
return [{ inputFilePath, outputFilePath, schemaPath, schema }];
|
|
||||||
} catch (e) {
|
|
||||||
failures.push({ type: 'error', file: inputFilePath, detail: e });
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
inputFiles.forEach(c => {
|
inputFiles.forEach(c => {
|
||||||
|
@ -190,20 +141,7 @@ export function runOnce(options: CommandLineArguments): CompilationResult {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const d of failures) {
|
formatFailures(failures, options.traceback);
|
||||||
console.log(
|
|
||||||
(d.type === 'error' ? chalk.redBright('[ERROR]') : chalk.yellowBright('[WARNING]'))
|
|
||||||
+ ' '
|
|
||||||
+ chalk.blueBright(formatPosition((d.detail as any).pos ?? d.file))
|
|
||||||
+ ': '
|
|
||||||
+ d.detail.message
|
|
||||||
+ (options.traceback && (d.detail instanceof Error)
|
|
||||||
? '\n' + d.detail.stack
|
|
||||||
: ''));
|
|
||||||
}
|
|
||||||
if (failures.length > 0) {
|
|
||||||
console.log();
|
|
||||||
}
|
|
||||||
|
|
||||||
return { options, inputFiles, failures, base, output };
|
return { options, inputFiles, failures, base, output };
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,61 @@
|
||||||
import { canonicalEncode, underlying } from '@preserves/core';
|
import yargs from 'yargs/yargs';
|
||||||
|
import { canonicalEncode, KeyedDictionary, underlying } from '@preserves/core';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { readSchema } from '../reader';
|
import path from 'path';
|
||||||
import { fromSchema } from '../meta';
|
import { fromSchema, fromBundle } from '../meta';
|
||||||
|
import { expandInputGlob, formatFailures } from './cli-utils';
|
||||||
|
|
||||||
export function run(): void {
|
export type CommandLineArguments = {
|
||||||
const src = fs.readFileSync('/dev/stdin', 'utf-8');
|
input: string;
|
||||||
fs.writeSync(1, underlying(canonicalEncode(fromSchema(readSchema(src)))))
|
base: string | undefined;
|
||||||
|
bundle: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function run(options: CommandLineArguments): void {
|
||||||
|
const { failures, inputFiles } = expandInputGlob(options.input, options.base);
|
||||||
|
|
||||||
|
if (!options.bundle && inputFiles.length !== 1) {
|
||||||
|
failures.push({ type: 'error', file: null, detail: {
|
||||||
|
message: 'Cannot emit non-bundle with anything other than exactly one input file',
|
||||||
|
pos: null,
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
formatFailures(failures);
|
||||||
|
|
||||||
|
if (failures.length === 0) {
|
||||||
|
if (options.bundle) {
|
||||||
|
fs.writeSync(1, underlying(canonicalEncode(fromBundle({
|
||||||
|
modules: new KeyedDictionary(inputFiles.map(i => [i.modulePath, i.schema])),
|
||||||
|
}))));
|
||||||
|
} else {
|
||||||
|
fs.writeSync(1, underlying(canonicalEncode(fromSchema(inputFiles[0].schema))));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function main(_argv: Array<string>) {
|
export function main(argv: Array<string>) {
|
||||||
|
const options: CommandLineArguments = yargs(argv)
|
||||||
|
.command('$0 <input>',
|
||||||
|
'Compile textual Preserves schema definitions to binary format',
|
||||||
|
yargs => yargs
|
||||||
|
.positional('input', {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Input filename or glob',
|
||||||
|
demandOption: true,
|
||||||
|
})
|
||||||
|
.option('bundle', {
|
||||||
|
type: 'boolean',
|
||||||
|
description: 'Determines whether to emit a schema Bundle or a lone Schema',
|
||||||
|
default: true,
|
||||||
|
})
|
||||||
|
.option('base', {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Base directory for sources (default: common prefix)',
|
||||||
|
}),
|
||||||
|
argv => argv)
|
||||||
|
.argv;
|
||||||
|
options.input = path.normalize(options.input);
|
||||||
Error.stackTraceLimit = Infinity;
|
Error.stackTraceLimit = Infinity;
|
||||||
run();
|
run(options);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
all: schema.bin
|
all: schema.bin
|
||||||
|
|
||||||
schema.bin: schema.prs
|
schema.bin: schema.prs
|
||||||
../implementations/javascript/packages/schema/bin/preserves-schemac.js < $< > $@.tmp || (rm -f $@.tmp; false)
|
../implementations/javascript/packages/schema/bin/preserves-schemac.js --no-bundle $< > $@.tmp || (rm -f $@.tmp; false)
|
||||||
mv $@.tmp $@
|
mv $@.tmp $@
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
|
Loading…
Reference in New Issue