Skip to content

Commit

Permalink
CLI is in Typescript
Browse files Browse the repository at this point in the history
  • Loading branch information
llambeau committed Feb 24, 2024
1 parent 3b31116 commit 654cbfe
Show file tree
Hide file tree
Showing 9 changed files with 173 additions and 160 deletions.
135 changes: 1 addition & 134 deletions 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')
145 changes: 145 additions & 0 deletions 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<Strategy> = [];

const errorManager = function(e) {
const results: Array<unknown> = [];
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<string, () => 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) {

Check warning on line 136 in src/cli.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

Expected '!==' and instead saw '!='

Check warning on line 136 in src/cli.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Expected '!==' and instead saw '!='
action();
} else {
program.outputHelp();
}
} catch (error) {
const e = error;
errorManager(e);
}

16 changes: 7 additions & 9 deletions src/finitio.ts
Expand Up @@ -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); }
Expand All @@ -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<World>): 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<World>, 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<World>, lang: TargetLanguage = TargetLanguage.Javascript) {
return (getBundler(lang, this.world(world))).addSource(source).flush();
}
}

const extendWorld = (world: World, ext: Record<string, unknown>) => {
const result = [];
const extendWorld = <K extends keyof World>(world: World, ext: Record<K, World[K]>) => {
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 {
Expand Down
26 changes: 14 additions & 12 deletions 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<Pick<World, 'sourceUrl'>>

class Bundler {

Expand Down Expand Up @@ -34,9 +36,9 @@ class Bundler {
})();
`;

systems: Record<string, System> = {}
systems: Record<string, SystemAst> = {}

constructor(public world: World) {
constructor(public world: WorldWithSource) {
this.world = world;
}

Expand All @@ -62,29 +64,29 @@ 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;
if (!system.imports) { return; }

// 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;
})();
Expand Down

0 comments on commit 654cbfe

Please sign in to comment.