UNPKG

@stencila/jesta

Version:

Stencila plugin for executable documents using JavaScript

321 lines (297 loc) 12.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.run = exports.cli = void 0; const schema_1 = require("@stencila/schema"); const minimist_1 = __importDefault(require("minimist")); const path_1 = __importDefault(require("path")); const readline_1 = __importDefault(require("readline")); const types_1 = require("./types"); const readline_2 = require("./util/readline"); /** * Expose a command line interface for the plugin. * * The command line interface is intended to be simple and mainly * for use by plugin developers and users that want to use * plugins standalone. * * Note: This function is a simple wrapper around the `run` function that * passes it the args vector and exits on error. */ // istanbul ignore next function cli() { run .bind(this)(process.argv.slice(2)) .then(() => process.exit(0)) .catch((error) => { console.error(error instanceof Error && !process.argv.includes('--debug') ? error.message : error); process.exit(1); }); } exports.cli = cli; /** * Run a command. * * Note: This function is separate from the `cli` function to facilitate testing. * * @param plugin The plugin to run the command on (Jesta or a derived plugin) * @param argv The vector of string arguments */ async function run(argv) { var _a; const { name: pluginName, softwareVersion, description } = this.manifest(); let { _: [method, ...args], ...options } = minimist_1.default(argv, { boolean: ['force', 'debug', 'interact', 'update'], }); let calls = []; if (method === null || method === void 0 ? void 0 : method.includes('+')) { calls = method.split('+'); method = types_1.Method.pipe; } switch (method) { // Methods are arranged in groups below according to the // arguments they require // Note: ...options should be sent to all `dispatcher` calls // Some of the assignments below from options are type unsafe // but we allow these as the dispatch function handles type checking /* eslint-disable @typescript-eslint/no-unsafe-assignment */ case 'manifest': return console.log(JSON.stringify(this.manifest(), null, ' ')); case 'serve': return this.serve(); case types_1.Method.convert: case types_1.Method.pull: { const input = url(args[0]); const output = url(args[1]); await this.dispatch(method, { input, output, ...options, }); return; } case types_1.Method.select: { const input = url(args[0]); const query = args[1]; const output = url(args[2]); const node = await this.dispatch(types_1.Method.import, { input, ...options, format: options.from, }); const select = async (query, output) => { const selected = await this.dispatch(types_1.Method.select, { node, query, ...options, }); if (selected !== undefined && output !== undefined) await this.dispatch(types_1.Method.export, { node: selected, output, ...options, format: options.to, }); else console.log(selected); }; if (options.interact === true) { const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout, prompt: '>> ', }); readline_2.persist(rl, pluginName, 'select'); rl.prompt(); for await (const line of rl) { select(line, output) .then(() => rl.prompt()) .catch(console.error); } } else { await select(query, output); } return; } case types_1.Method.build: case types_1.Method.clean: case types_1.Method.compile: case types_1.Method.enrich: case types_1.Method.pipe: case types_1.Method.upcast: case types_1.Method.downcast: case types_1.Method.validate: { const input = url(args[0]); const output = url((_a = args[1]) !== null && _a !== void 0 ? _a : input); cd(input); const node = await this.dispatch(types_1.Method.import, { input, ...options, format: options.from, }); const result = await this.dispatch(method, { node, ...options, calls }); await this.dispatch(types_1.Method.export, { node: result, output, ...options, format: options.to, }); return; } case 'run': case types_1.Method.execute: case types_1.Method.vars: case types_1.Method.get: case types_1.Method.set: case types_1.Method.delete: case types_1.Method.funcs: case types_1.Method.call: { const input = url(args[0]); const name = args[1]; const value = args[2]; // for set if (method === types_1.Method.execute && options.interact === true) { const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout, prompt: 'js> ', }); readline_2.persist(rl, pluginName, 'execute'); rl.prompt(); for await (const line of rl) { const node = schema_1.codeChunk({ programmingLanguage: 'js', text: line, }); this.dispatch(types_1.Method.execute, { node, ...options, }) .then((node) => { const { outputs, errors } = node; if (outputs) { for (const output of outputs) console.log(output); } if (errors) { for (const error of errors) { const { errorType, errorMessage } = error; console.error(`${errorType !== null && errorType !== void 0 ? errorType : 'Error'}: ${errorMessage}`); } } rl.prompt(); }) .catch(console.error); } return; } const node = await this.dispatch(types_1.Method.import, { input, ...options, }); await this.dispatch(types_1.Method.execute, { node, ...options }); if (method === 'run') await this.serve(); else { let args_ = {}; if (method === types_1.Method.call) { args_ = args.slice(2).reduce((prev, curr, index) => { const match = /\w+=.*/.exec(curr); const [name, json] = match ? match.slice(1) : [`${index}`, curr]; const value = JSON.parse(json); return { ...prev, [name]: value }; }, {}); } const result = await this.dispatch(method, { name, value, args: args_, ...options, }); console.log(result); } return; } /* eslint-enable @typescript-eslint/no-unsafe-assignment */ case 'help': case undefined: { const methodName = args[0]; // Method-specific help if (methodName !== undefined) { // Just print out the schema for the method const schema = this[methodName].schema; if (schema === undefined) { return console.log(`No schema yet defined for method "${methodName}", sorry.`); } return console.log(JSON.stringify(schema, null, ' ')); } // Generic help return console.log(` ${pluginName} ${softwareVersion}: ${description} Usage: ${pluginName} <command> Primary commands (required for plugin integration) manifest Get ${pluginName}'s manifest serve Serve ${pluginName} over stdio Secondary commands (mainly for plugin testing and development) help Print this message help <command> Print the JSON Schema for a command update [codemeta.json] Update and get ${pluginName}'s manifest pull <in> <out> Pull content from <in> to <out> convert <in> <out> Convert document <in> to <out> validate <in> [out] Validate document <in> (save as [out]) reshape <in> [out] Reshape document <in> (save as [out]) enrich <in> [out] Enrich document <in> (save as [out]) select <in> <query> [out] Select nodes from document <in> (save as [out]) compile <in> [out] Compile document <in> (save as [out]) build <in> [out] Build document <in> (save as [out]) execute <in> [out] Execute document <in> (save as [out]) clean <in> [out] Clean document <in> (save as [out]) vars <in> List variables in document <in> get <in> <name> Get a variable from document <in> set <in> <name> <value> Set a variable in document <in> delete <in> <name> Delete a variable from document <in> funcs <in> List functions in document <in> call <in> [func] [name=val] Call document <in> (or a function within it) run <in> Run document <in> (execute and serve) Notes: - use the --debug option for debug level logging and error stack traces - \`in\` and \`out\` are file paths or URLs (e.g. http://..., file://...); but only some URL protocols may be supported (see manifest) - most commands with an \`in\` or \`out\` argument support the \`--format\` option - commands with both \`in\` and \`out\` arguments support \`--from\` and \`--to\` options for the respective formats - \`select\` supports the \`--lang\` option for the query language - \`validate\`, \`compile\`, \`build\` and \`execute\` support the \`--force\` option - \`call\` accepts \`name=val\` pairs for string parameters and \`name:=json\` for other parameters - some methods can be piped together, e.g. \`clean+compile+build+execute\` For a more advanced command line interface install ${pluginName} as a Stencila plugin and use it from there: \`stencila plugins install ${pluginName}\`. `.trim()); } default: console.error(`Unknown command "${method}"`); } } exports.run = run; function url(value) { if (value === undefined) return undefined; if (value === '-') return 'stdio://'; return /^([a-z]{2,6}):\/\//.test(value) ? value : `file://${path_1.default.resolve(value)}`; } /** * Change the working directory to directory of a document. */ function cd(url) { if (url === null || url === void 0 ? void 0 : url.startsWith('file://')) process.chdir(path_1.default.dirname(url.slice(7))); }