From 96e3811c75265b979170aac7f8b331838dd8f5d6 Mon Sep 17 00:00:00 2001 From: Corey Butler Date: Thu, 10 Feb 2022 19:43:52 -0600 Subject: [PATCH] Added data references. --- README.md | 34 ++++++++++++++++++++++++++++++++++ package.json | 3 ++- src/command.js | 10 +++++----- src/shell.js | 15 +++++++++++++-- tests/04-metadata.js | 27 +++++++++++++++++++++++++++ 5 files changed, 81 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index e9e8d43..2f7208b 100644 --- a/README.md +++ b/README.md @@ -331,6 +331,40 @@ _Output:_ > Notice the values from the known flags are _first_. +### Custom Handler Data References + +When building JavaScript applications, it may be desirable to pass data references to the `exec` method, making them available to handlers. For example: + +```javascript +const sh = new Shell({ + name: 'test', + commands: [{ + name: 'run', + async handler (meta) { + return meta.reference.test // reference data + } + }] +}) + +const ref = { test: true } +const result = await sh.exec('run', ref) +``` + +In the command above, the data object (line 1) is passed to the `exec()` method using a special object as the second argument. This object is made available in the handler using the `meta.reference` attribute. + +If a callback needs to be defined, the second argument of the `exec` method must have an attribute called `callback`, i.e.: + +```javascript +const ref = { + test: true, + callback: function() {...} +} + +const result = await sh.exec('run', ref) +``` + +The callback method is _not_ available in the `meta.reference`, but the callback will be executed when `exec()` is executed. + ## Plugins Plugins expose functions, objects, and primitives to shell handlers. diff --git a/package.json b/package.json index 281da23..ddd00c3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@author.io/shell", - "version": "1.8.18", + "version": "1.9.0", "description": "A micro-framework for creating CLI-like experiences. This supports Node.js and browsers.", "main": "./src/index.js", "module": "./index.js", @@ -16,6 +16,7 @@ "test:node:sanity": "dev test -rt node tests/01-sanity.js", "test:node:base": "dev test -rt node tests/02-base.js", "test:node:relationships": "dev test -rt node tests/06-relationships.js", + "test:node:metadata": "dev test -rt node tests/04-metadata.js", "test:node:regression": "dev test -rt node tests/100-regression.js", "test:browser": "dev test -rt browser tests/*.js", "test:browser:sanity": "dev test -rt browser tests/01-sanity.js", diff --git a/src/command.js b/src/command.js index 38f61b4..78872b3 100644 --- a/src/command.js +++ b/src/command.js @@ -529,9 +529,9 @@ export default class Command extends Base { return data } - async run (input, callback) { + async run (input, callback, reference = undefined) { const fn = (this.#fn || this.defaultHandler).bind(this) - const data = typeof input === 'string' ? this.parse(input) : input + const metadata = typeof input === 'string' ? this.parse(input) : input arguments[0] = this.deepParse(input) arguments[0].plugins = this.plugins @@ -558,7 +558,7 @@ export default class Command extends Base { // No subcommand was recognized if (this.middleware.size > 0) { - this.middleware.run(arguments[0], async meta => await Command.reply(fn(meta, callback))) + this.middleware.run(arguments[0], async meta => await Command.reply(fn(Object.assign(meta, { reference }), callback))) if (trailers.size > 0) { trailers.run(arguments[0]) @@ -568,8 +568,8 @@ export default class Command extends Base { } // Command.reply(fn(arguments[0], callback)) - data.plugins = this.plugins - const result = await Command.reply(fn(data, callback)) + metadata.plugins = this.plugins + const result = await Command.reply(fn(Object.assign(metadata, { reference }), callback)) if (trailers.size > 0) { trailers.run(arguments[0]) diff --git a/src/shell.js b/src/shell.js index 5f5de96..d127e16 100644 --- a/src/shell.js +++ b/src/shell.js @@ -164,6 +164,17 @@ export default class Shell extends Base { } async exec (input, callback) { + // Optionally apply reference data. Only for advanced use + // in applications (not strict CLIs). + let reference + if (typeof callback === 'object') { + if (!callback.hasOwnProperty('reference') && !callback.hasOwnProperty('callback')) { // eslint-disable-line no-prototype-builtins + throw new Error('exec method data references require a reference and/or callback attribute - recognized: ' + Object.keys(callback).join(', ')) + } + reference = callback.reference + callback = callback.callback + } + // The array check exists because people are passing process.argv.slice(2) into this // method, often forgetting to join the values into a string. if (Array.isArray(input)) { @@ -219,10 +230,10 @@ export default class Shell extends Base { const term = processor.getTerminalCommand(args) if (typeof callback === 'function') { - return callback(await Command.reply(await term.command.run(term.arguments, callback))) // eslint-disable-line standard/no-callback-literal + return callback(await Command.reply(await term.command.run(term.arguments, callback, reference))) // eslint-disable-line standard/no-callback-literal } - return await Command.reply(await term.command.run(term.arguments, callback)) + return await Command.reply(await term.command.run(term.arguments, callback, reference)) } getCommandMiddleware (cmd) { diff --git a/tests/04-metadata.js b/tests/04-metadata.js index 925ec13..c44d624 100644 --- a/tests/04-metadata.js +++ b/tests/04-metadata.js @@ -159,3 +159,30 @@ test('Ordered Named Arguments', t => { shell.exec('account create test@domain.com pwd') }) + + +test('Application reference data', async t => { + const data = { test: true } + const sh = new Shell({ + name: 'test', + commands: [{ + name: 'run', + async handler (meta) { + return meta.reference.test + } + }] + }) + + const result = await sh.exec('run', { reference: data }) + + t.expect(result, true, 'reference data detected in handler') + + try { + await sh.exec('run', { demo: data }) + t.fail('failure to provide reference and callback throws error') + } catch (e) { + t.pass('failure to provide reference and callback throws error') + } + + t.end() +})