2021-01-18 22:11:53 +00:00
|
|
|
import yargs from 'yargs/yargs';
|
|
|
|
|
|
|
|
import fs from 'fs';
|
2021-01-19 14:13:42 +00:00
|
|
|
import path from 'path';
|
|
|
|
import { glob } from 'glob';
|
|
|
|
|
2021-01-18 22:11:53 +00:00
|
|
|
import { compile } from '@syndicate-lang/compiler';
|
2021-01-19 23:52:40 +00:00
|
|
|
import { dataURL, sourceMappingComment} from './util.js';
|
2021-01-18 22:11:53 +00:00
|
|
|
|
|
|
|
export type ModuleChoice = 'es6' | 'require' | 'global';
|
|
|
|
const moduleChoices: ReadonlyArray<ModuleChoice> = ['es6', 'require', 'global'];
|
|
|
|
|
|
|
|
export type CommandLineArguments = {
|
|
|
|
input: string | undefined;
|
2021-01-19 14:13:42 +00:00
|
|
|
outputDirectory?: string | undefined;
|
|
|
|
rootDirectory?: string;
|
|
|
|
rename: string | undefined;
|
2021-01-18 22:11:53 +00:00
|
|
|
map: boolean;
|
|
|
|
mapExtension?: string;
|
|
|
|
runtime: string;
|
|
|
|
module: ModuleChoice;
|
2021-01-19 23:52:40 +00:00
|
|
|
typed: boolean;
|
2021-01-18 22:11:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function checkModuleChoice<T>(t: T & { module: string }): T & { module: ModuleChoice } {
|
|
|
|
const mc = t.module as ModuleChoice;
|
|
|
|
if (moduleChoices.indexOf(mc) !== -1) return { ... t, module: mc };
|
|
|
|
throw new Error("Illegal --module argument: " + t.module);
|
|
|
|
}
|
|
|
|
|
2021-01-19 14:13:42 +00:00
|
|
|
function makeRenamer(outputDir: string,
|
|
|
|
rootDir: string,
|
|
|
|
renamePattern: string | undefined): (f: string) => string
|
|
|
|
{
|
|
|
|
const rewrites: Array<(f: string) => (string | null)> =
|
|
|
|
(renamePattern === void 0 ? [] : renamePattern.split(/,/)).map(p => {
|
|
|
|
const [ from, to ] = p.split(/:/);
|
|
|
|
let mFrom = /([^%]*)%([^%]*)/.exec(from);
|
|
|
|
let mTo = /([^%]*)%([^%]*)/.exec(to);
|
|
|
|
if (mFrom === null && mTo === null) {
|
|
|
|
return f => (f === from) ? to : null;
|
|
|
|
} else if (mFrom === null || mTo === null) {
|
|
|
|
throw new Error(`Invalid --rename pattern: ${JSON.stringify(p)}`);
|
|
|
|
} else {
|
|
|
|
const [fh, ft] = mFrom.slice(1);
|
|
|
|
const [th, tt] = mTo.slice(1);
|
|
|
|
return f =>
|
|
|
|
(f.startsWith(fh) && f.endsWith(ft))
|
|
|
|
? th + f.substring(fh.length, f.length - ft.length) + tt
|
|
|
|
: null;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
const relocate = (f: string) => path.join(outputDir, path.relative(rootDir, f));
|
|
|
|
return f => {
|
|
|
|
for (const rewrite of rewrites) {
|
|
|
|
const t = rewrite(f);
|
|
|
|
if (t !== null) return relocate(t);
|
|
|
|
}
|
|
|
|
return relocate(f);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-01-18 22:11:53 +00:00
|
|
|
export function main(argv: string[]) {
|
|
|
|
const options: CommandLineArguments = checkModuleChoice(yargs(argv)
|
|
|
|
.command('$0 [input]',
|
2021-01-19 14:13:42 +00:00
|
|
|
'Compile away Syndicate extensions',
|
2021-01-18 22:11:53 +00:00
|
|
|
yargs => yargs
|
|
|
|
.positional('input', {
|
|
|
|
type: 'string',
|
2021-01-19 14:13:42 +00:00
|
|
|
description: 'Input filename or glob (stdin if omitted)',
|
|
|
|
})
|
|
|
|
.option('root-directory', {
|
|
|
|
alias: 'b',
|
|
|
|
type: 'string',
|
|
|
|
description: 'Root directory for input files',
|
|
|
|
default: '.',
|
2021-01-18 22:11:53 +00:00
|
|
|
})
|
2021-01-19 14:13:42 +00:00
|
|
|
.option('output-directory', {
|
|
|
|
alias: 'd',
|
2021-01-18 22:11:53 +00:00
|
|
|
type: 'string',
|
2021-01-19 14:13:42 +00:00
|
|
|
description: 'Output directory (if omitted: stdout if stdin as input, else cwd)',
|
|
|
|
})
|
|
|
|
.option('rename', {
|
|
|
|
type: 'string',
|
|
|
|
description: 'Rewrite input filenames',
|
|
|
|
default: '%.syndicate.js:%.js,%.syndicate.ts:%.ts',
|
2021-01-18 22:11:53 +00:00
|
|
|
})
|
|
|
|
.option('map', {
|
|
|
|
type: 'boolean',
|
|
|
|
description: 'Generate source maps',
|
|
|
|
default: true,
|
|
|
|
})
|
|
|
|
.option('map-extension', {
|
|
|
|
type: 'string',
|
|
|
|
description: 'Extension (e.g. ".map") to add to source map files; if omitted, source maps are generated inline',
|
|
|
|
})
|
|
|
|
.option('runtime', {
|
|
|
|
type: 'string',
|
|
|
|
description: 'Path to require or import to get the Syndicate runtime',
|
|
|
|
default: '@syndicate-lang/core',
|
|
|
|
})
|
2021-01-19 23:52:40 +00:00
|
|
|
.option('typed', {
|
|
|
|
alias: 't',
|
|
|
|
type: 'boolean',
|
|
|
|
description: 'Enable TypeScript-typed translation',
|
|
|
|
default: false,
|
|
|
|
})
|
2021-01-18 22:11:53 +00:00
|
|
|
.option('module', {
|
|
|
|
choices: moduleChoices,
|
|
|
|
type: 'string',
|
|
|
|
description: 'Style of import/export definition to prefer',
|
|
|
|
default: moduleChoices[0],
|
|
|
|
}),
|
|
|
|
argv => argv)
|
|
|
|
.argv);
|
|
|
|
|
2021-01-19 14:13:42 +00:00
|
|
|
const rename = makeRenamer(options.outputDirectory ?? '',
|
|
|
|
options.rootDirectory ?? '.',
|
|
|
|
options.rename);
|
2021-01-18 22:11:53 +00:00
|
|
|
|
2021-01-19 14:13:42 +00:00
|
|
|
const STDIN = '/dev/stdin';
|
2021-01-18 22:11:53 +00:00
|
|
|
|
2021-01-19 14:13:42 +00:00
|
|
|
const inputGlob = options.input ?? STDIN;
|
|
|
|
const inputFilenames = glob.sync(inputGlob);
|
2021-01-18 22:11:53 +00:00
|
|
|
|
2021-01-19 14:13:42 +00:00
|
|
|
for (const inputFilename of inputFilenames) {
|
|
|
|
const outputFilename =
|
|
|
|
(inputFilename === STDIN) ? '/dev/stdout' :
|
|
|
|
(inputFilename[0] === '/') ? (() => { throw new Error("Absolute input paths are not supported"); })() :
|
|
|
|
rename(inputFilename);
|
|
|
|
|
|
|
|
if (inputFilenames.indexOf(outputFilename) !== -1) {
|
|
|
|
throw new Error(`Output from ${JSON.stringify(inputFilename)} would trample on existing input file ${JSON.stringify(outputFilename)}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
const source = fs.readFileSync(inputFilename, 'utf-8');
|
|
|
|
|
|
|
|
const { text, map } = compile({
|
|
|
|
source,
|
|
|
|
name: inputFilename,
|
|
|
|
runtime: options.runtime,
|
|
|
|
module: options.module,
|
2021-01-19 23:52:40 +00:00
|
|
|
typescript: options.typed,
|
2021-01-19 14:13:42 +00:00
|
|
|
});
|
|
|
|
map.sourcesContent = [source];
|
|
|
|
|
|
|
|
if (inputFilename !== STDIN) {
|
|
|
|
fs.mkdirSync(path.dirname(outputFilename), { recursive: true });
|
2021-01-18 22:11:53 +00:00
|
|
|
}
|
2021-01-19 14:13:42 +00:00
|
|
|
|
2021-01-18 22:11:53 +00:00
|
|
|
if (!options.map) {
|
2021-01-19 14:13:42 +00:00
|
|
|
fs.writeFileSync(outputFilename, text);
|
|
|
|
} else if (options.mapExtension && inputFilename !== STDIN) {
|
|
|
|
const mapFilename = outputFilename + options.mapExtension;
|
2021-01-19 23:52:40 +00:00
|
|
|
fs.writeFileSync(outputFilename, text + sourceMappingComment(mapFilename));
|
2021-01-19 14:13:42 +00:00
|
|
|
fs.writeFileSync(mapFilename, JSON.stringify(map));
|
2021-01-18 22:11:53 +00:00
|
|
|
} else {
|
2021-01-19 23:52:40 +00:00
|
|
|
fs.writeFileSync(outputFilename, text + sourceMappingComment(dataURL(JSON.stringify(map))));
|
2021-01-18 22:11:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|