showandtell
Version:
A Javascript library providing debugger-like command-line interactivity for program state inspection and modification
106 lines (80 loc) • 4.32 kB
Markdown
# 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.