diff --git a/bin/finitio-js b/bin/finitio-js index 31bc7e1..5ad547a 100755 --- a/bin/finitio-js +++ b/bin/finitio-js @@ -1,135 +1,2 @@ #!/usr/bin/env node - -const fs = require('fs'); -const { program } = require('commander'); -const TypeError = require('../dist/finitio').TypeError; - -const Finitio = require('../dist/finitio').default; - -const options = program.version(`finitio.js ${Finitio.VERSION} (c) Bernard, Louis Lambeau & the University of Louvain`) - .usage('[options] SCHEMA.fio [DATA.json]') - .option('-b, --bundle', 'Bundle the input schema as a javascript loader') - .option('-l, --lang [lang]', 'The target language for the bundle [typescript, javascript] (defaults to javascript)') - .option('--url [url]', 'Specify the bundle global url') - .option('-v, --validate', 'Valid input data against the schema') - .option('-f, --fast', 'Stop on first validation error') - .option('--no-check', 'Do not try to check the system before bundling') - .option('--stack', 'Show stack trace on error') - .parse(process.argv) - .opts() - -const sourceUrl = function() { - if (typeof options.url === 'string') { - return options.url; - } else { - return `file://${program.args[0]}`; - } -}; - -const schemaFile = function() { - return program.args[0]; -}; - -const schemaSource = function() { - return fs.readFileSync(schemaFile()).toString(); -}; - -const world = function() { - const w = { - sourceUrl: sourceUrl(), - failfast: options.fast, - }; - if (options.check) { - w.check = true; - } - return w; -}; - -const schema = function() { - return Finitio.system(schemaSource(), world()); -}; - -const data = function() { - const dataFile = program.args[1]; - const dataSource = fs.readFileSync(dataFile).toString(); - return JSON.parse(dataSource); -}; - -const strategies = []; - -const errorManager = function(e) { - const results = []; - for (let i = 0, len = strategies.length; i < len; i++) { - const s = strategies[i]; - if (s[0](e)) { - s[1](e); - break; - } else { - results.push(void 0); - } - } - return results; -}; - -strategies.push([ - function(e) { - return e instanceof TypeError; - }, function(e) { - if (options.stack) { - return console.log(e.explainTree()); - } else { - return console.log(e.explain()); - } - }, -]); - -strategies.push([ - function(e) { - return e.name === 'SyntaxError'; - }, function(e) { - console.log(`[${e.line}:${e.column}] ${e.message}`); - if (options.stack) { - return console.log(e.expected); - } - }, -]); - -strategies.push([ - function(e) { - return true; - }, function(e) { - console.log(e.message); - if (options.stack) { - return console.log(e.stack); - } - }, -]); - -const actions = {}; - -actions.bundle = function() { - const lang = options.lang || 'javascript'; - return console.log(Finitio.bundleFile(schemaFile(), world(), lang)); -}; - -actions.validate = function() { - return console.log(schema().dress(data(), world())); -}; - -try { - let action; - if (options.bundle) { - action = actions.bundle; - } else if (options.validate) { - action = actions.validate; - } - if (action != null) { - action(); - } else { - program.outputHelp(); - } -} catch (error) { - const e = error; - errorManager(e); -} - +require('../dist/cli.js') diff --git a/src/cli.ts b/src/cli.ts new file mode 100755 index 0000000..27d3ba2 --- /dev/null +++ b/src/cli.ts @@ -0,0 +1,145 @@ +import fs from 'fs'; +import { program } from 'commander'; +import type { World } from './finitio'; +import { TypeError } from './finitio'; +import Finitio from './finitio'; + +const options = program.version(`finitio.js ${Finitio.VERSION} (c) Bernard, Louis Lambeau & the University of Louvain`) + .usage('[options] SCHEMA.fio [DATA.json]') + .option('-b, --bundle', 'Bundle the input schema as a javascript loader') + .option('-l, --lang [lang]', 'The target language for the bundle [typescript, javascript] (defaults to javascript)') + .option('--url [url]', 'Specify the bundle global url') + .option('-v, --validate', 'Valid input data against the schema') + .option('-f, --fast', 'Stop on first validation error') + .option('--no-check', 'Do not try to check the system before bundling') + .option('--stack', 'Show stack trace on error') + .parse(process.argv) + .opts() + +const sourceUrl = function() { + if (typeof options.url === 'string') { + return options.url; + } else { + return `file://${program.args[0]}`; + } +}; + +const schemaFile = function() { + return program.args[0]; +}; + +const schemaSource = function() { + return fs.readFileSync(schemaFile()).toString(); +}; + +const world = function() { + const w: World = { + sourceUrl: sourceUrl(), + failfast: options.fast, + }; + if (options.check) { + w.check = true; + } + return w; +}; + +const schema = function() { + return Finitio.system(schemaSource(), world()); +}; + +const data = function() { + const dataFile = program.args[1]; + const dataSource = fs.readFileSync(dataFile).toString(); + return JSON.parse(dataSource); +}; + +type ErrorMatcher = (err: Error|unknown) => boolean +type ErrorHandler = (err: Error|unknown) => void +type Strategy = [ErrorMatcher, ErrorHandler] +const strategies: Array = []; + +const errorManager = function(e) { + const results: Array = []; + for (let i = 0, len = strategies.length; i < len; i++) { + const s = strategies[i]; + if (s[0](e)) { + s[1](e); + break; + } else { + results.push(void 0); + } + } + return results; +}; + +strategies.push([ + function(e) { + return e instanceof TypeError; + }, function(e) { + const err = e as TypeError; + if (options.stack) { + return console.log(err.explainTree()); + } else { + return console.log(err.explain()); + } + }, +]); + +type SyntaxError = Error & { + line: number, + column: number + expected: unknown +} + +strategies.push([ + function(e) { + return (e as Error).name === 'SyntaxError'; + }, function(e) { + const err = e as SyntaxError; + console.log(`[${err.line}:${err.column}] ${err.message}`); + if (options.stack) { + return console.log(err.expected); + } + }, +]); + +strategies.push([ + function(_e) { + return true; + }, function(e) { + const err = e as Error; + console.log(err.message); + if (options.stack) { + return console.log(err.stack); + } + }, +]); + +const actions: Record void> = {}; + +actions.bundle = function() { + const lang = options.lang || 'javascript'; + return console.log(Finitio.bundleFile(schemaFile(), world(), lang)); +}; + +actions.validate = function() { + return console.log(schema().dress(data(), world())); +}; + +try { + let action; + if (options.bundle) { + action = actions.bundle; + } else if (options.validate) { + action = actions.validate; + } + if (action != null) { + action(); + } else { + program.outputHelp(); + } +} catch (error) { + const e = error; + errorManager(e); +} + diff --git a/src/finitio.ts b/src/finitio.ts index 7f1a666..7d100f4 100644 --- a/src/finitio.ts +++ b/src/finitio.ts @@ -48,7 +48,7 @@ class Finitio { 'importResolver': resolver, }; - static world(...args) { + static world(...args): World { const world = $u.clone(Finitio.World); for (const arg of args) { if (arg) { extendWorld(world, arg); } @@ -60,31 +60,29 @@ class Finitio { return Parser.parse(source, options); } - static system(source: string|SystemAst, world?: World): System { + static system(source: string|SystemAst, world?: Partial): System { if (typeof(source) === 'string') { source = this.parse(source); } return Meta.System.dress(source, this.world(world)); } - static bundleFile(path: string, world: World, lang: TargetLanguage = TargetLanguage.Javascript) { + static bundleFile(path: string, world?: Partial, lang: TargetLanguage = TargetLanguage.Javascript) { return (getBundler(lang, this.world(world))).addFile(path).flush(); } - static bundleSource(source: string, world: World, lang: TargetLanguage = TargetLanguage.Javascript) { + static bundleSource(source: string, world?: Partial, lang: TargetLanguage = TargetLanguage.Javascript) { return (getBundler(lang, this.world(world))).addSource(source).flush(); } } -const extendWorld = (world: World, ext: Record) => { - const result = []; +const extendWorld = (world: World, ext: Record) => { for (const k in ext) { const v = ext[k]; if (k === 'JsTypes') { - result.push(world[k] = $u.extend(world[k], v)); + world[k] = $u.extend(world[k], v); } else { - result.push(world[k] = v); + world[k] = v; } } - return result; }; export { diff --git a/src/finitio/bundler.ts b/src/finitio/bundler.ts index 5d6e42f..7d3c653 100644 --- a/src/finitio/bundler.ts +++ b/src/finitio/bundler.ts @@ -1,6 +1,8 @@ import { lstatSync, readFileSync } from 'fs'; -import type System from './system'; -import type { World } from '../types'; +import type { SystemAst, World } from '../types'; +import Finitio from '../finitio'; + +type WorldWithSource = World & Required> class Bundler { @@ -34,9 +36,9 @@ class Bundler { })(); `; - systems: Record = {} + systems: Record = {} - constructor(public world: World) { + constructor(public world: WorldWithSource) { this.world = world; } @@ -62,13 +64,13 @@ class Bundler { addSource(source) { // recursively resolve every import - this._bundle(this.world.Finitio.parse(source), this.world); + this._bundle(Finitio.parse(source), this.world); return this; } - _bundle(system, world) { + _bundle(system: SystemAst, world: WorldWithSource) { // dress the system to catch any error immediately - if (world.check) { world.Finitio.system(system, world); } + if (world.check) { Finitio.system(system, world); } // save it under url in systems this.systems[world.sourceUrl] = system; @@ -76,15 +78,15 @@ class Bundler { // recursively resolve imports return (() => { - const result = []; + const result: Array<[string, SystemAst]|unknown> = []; for (const imp of [...system.imports]) { // resolve in raw mode - const pair = world.importResolver(imp.from, world, { raw: true }); + const pair = world.importResolver?.(imp.from, world, { raw: true }); // set the resolved URL, dress the system for catching errors - imp.from = pair[0]; + imp.from = pair![0]; // recurse on sub-imports - const newWorld = world.Finitio.world(world, { sourceUrl: pair[0] }); - result.push(this._bundle(pair[1], newWorld)); + const newWorld = Finitio.world(world, { sourceUrl: pair![0] }); + result.push(this._bundle(pair![1], newWorld as WorldWithSource)); } return result; })(); diff --git a/src/finitio/bundlers/AbstractBundler.ts b/src/finitio/bundlers/AbstractBundler.ts index 4eb82b3..19a9f22 100644 --- a/src/finitio/bundlers/AbstractBundler.ts +++ b/src/finitio/bundlers/AbstractBundler.ts @@ -1,3 +1,4 @@ +import Finitio from '../../finitio'; import { lstatSync, readFileSync } from 'fs'; import type { World } from '../../types'; import type { SystemAst } from '../parser'; @@ -30,7 +31,7 @@ export default abstract class AbstractBundler { addSource(source: string) { // recursively resolve every import - this._bundle(this.world.Finitio.parse(source), this.world); + this._bundle(Finitio.parse(source), this.world); return this; } diff --git a/src/finitio/errors.ts b/src/finitio/errors.ts index 2b6306e..b9254e7 100644 --- a/src/finitio/errors.ts +++ b/src/finitio/errors.ts @@ -51,7 +51,7 @@ class TypeError extends Error { return str; } - explainTree(depth) { + explainTree(depth?: number) { let str = ''; if (depth == null) { depth = 0; } for (let i = 0, end = depth, asc = 0 <= end; asc ? i < end : i > end; asc ? i++ : i--) { diff --git a/src/types.ts b/src/types.ts index 3759a3c..cf02a30 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,3 @@ -import type Finitio from './finitio' import type { System, Type } from './finitio' import type { Resolver } from './finitio/resolver'; import { notImplemented } from './finitio/support/utils' @@ -9,11 +8,10 @@ export type Scalar = boolean|number|string|Date|undefined|null; export type TypeMetadata = Record export type World = { - Finitio: typeof Finitio failfast?: boolean, JsTypes?: Record sourceUrl?: string, - importResolver: Resolver, + importResolver?: Resolver, [k: string]: unknown } diff --git a/tsconfig.json b/tsconfig.json index 98823ee..baac945 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,6 +11,7 @@ "module": "commonjs" }, "include": [ + "./bin/*", "./src/*", "./src/**/*", "./specs/*", diff --git a/tsup.config.ts b/tsup.config.ts index a92d69d..8e4f640 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -10,6 +10,7 @@ export default defineConfig({ dts: true, entry: { finitio: "src/finitio.ts", + cli: "src/cli.ts", }, loader: { '.fio': 'copy',