# 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.
