UNPKG

showandtell

Version:

A Javascript library providing debugger-like command-line interactivity for program state inspection and modification

106 lines (80 loc) 4.32 kB
# showandtell The goal of show-and-tell is to make it easier to write programs that stop to allow the user the opportunity to inspect some state and modify it. Show-and-tell functions much like debuggers that allow for program state to be inspected and tampered with before resuming program execution. ## Stories Stories manage interactive sessions with users, providing access to a set of commands and updating state. A story is created with an array of commands to make available, and then the `start` method is invoked to defer to the user. ```js const showandtell = require('showandtell') const story = new showandtell.Story([ showandtell.commands.set, showandtell.commands.show ]) let state = {test: 32} story.start(state) .then((newState) => { console.log('new test value:', parseInt(state.test)) }) .catch(console.error) ``` Once the user invokes the **QUIT** command, `start` will resolve to the new state after all of the modifications have taken place. ## Commands ### Default Commands SET <variable> <value> The **SET** command allows a value to be assigned in the state. `variable` is a "path" to the state value to update, like `test.values.number`, which would update a value a state like `{test: {values: {number: <gets updated>}}}`. The `value` is simply the new value to assign. Set does not know the type of the value and will not try to guess it, so the value will be assigned as a string. If the full path does not already exist, it will be created. For example. a state `{test: null}` updated with the variable `test.values.number` would be transformed to `{test: {values: {number: <value>}}}`. SHOW [variable] The **SHOW** command simply logs the current value of a value in the state. `variable` here is also a "path", just like with **SET**. If no argument is passed to **SHOW** then it will display the entire state available as a JSON structure, so that it is easy to construct the path to the variable one might like to set. ### Creating Commands It is easy to define your own command, which you can supply to a story in order to make it available to the user. A command should be configured with four values. 1. `name` is the name of the command, in upper case, that the user references 2. `help` is a string describing the argument format for the command 3. `args` is an array of objects describing the argument the command expects, in order. Each can contain * `name`: the name that the variable will be given in the args object passed to `func`. * `help`: a string explaining what the argument is expected to be. Optional. * `default`: a default value to supply to the variable if one is not present. Optional. * `parser`: a function that will convert the value supplied by the user to a desired type. Optional. 4. `func` is a state transition function The state transition function, `func`, should accept three arguments: 1. `state` is an object containing the state available to the user for inspection/modification 2. `args` is an object containing values for the variables described in the command's `args` 3. `next` is a function that should be invoked with an error (or null) if any, and the new state after the command finishes For example, a command to square a value at the top-level of the state might look like: ```js const square = new showandtell.Command({ name: 'SQUARE', help: 'SQUARE <variable>', args: [{name: 'variable', help: 'The name of a top-level value to compute the square of'}], func: function (state, args, next) { if (!state.hasOwnProperty(args.variable)) { next(new Error(`State does not have top-level key ${args.variable}`), state) } else { state[args.variable] *= state[args.variable] next(null, state) } } }) ``` #### A note on argument parsing The argument parser functions in a very simple way. If your command accepts N arguments but only M < N are provided by the user, then only the first M arguments will be assigned their respective values. The remaining N - M values will be assigned their defaults if any are provided, otherwise set to `undefined`. Arguments can also be provided as strings, so the value `"hello world"` will be interpreted as a value for *one* argument, instead of two arguments containing `"hello` and `world"` respectively.