woolts / wool

A typescript ecosystem - package manager, decentralised registries, monorepo compiler
MIT License
7 stars 0 forks source link

Creating CLI Apps #2

Open lsjroberts opened 5 years ago

lsjroberts commented 5 years ago

At the root of Wool's philosophy are three concepts:

  1. A single, good way to achieve a task
  2. A small number of high quality packages
  3. Sandboxed and secure by default (see #1)

To that end, the wool/terminal package will provide a way to create command line applications from simple single commands to git-style programs with sub-commands and curses / blessed style views.

All applications, be they cli or web-based, will be expected to export a default entry. This is much the same as a main function in many common languages such as C and Python.

Programs

All these programs would automatically support --help and --version, e.g.:

wool run hello/world --version
> 1.0.0

And as per #1 they would require the user to give them permission before being able to perform dangerous actions such as calling functions from wool/fs and similar.

Action

A single action that takes no input from the outside world.

// ~/hello-world/wool.json
{
  "name": "hello/world",
  "entry": "index.ts"
}
// ~/hello-world/index.ts
import * as Terminal from 'wool/terminal';

export default Terminal.action(() => {
  console.log('Hello, World!');
});
wool make ~/hello-world
wool run hello/world
> Hello, World!

Command

A command that can be given arguments and flags.

import { command, arg, flag, required, alias, boolean, string } from 'wool/terminal';

export default Terminal.command({
  args: [
    arg('entry', required()),
  ],
  flags: [
    flag('clean', alias('c'), boolean(false)),
    flag('outDir', alias('o'), string('./dist')),
  ],
  action: (cmd) => {
    if (cmd.clean) {
      // ... clean the `cmd.outDir` ...
    }

    // ... do something with `cmd.entry` ...
  },
});
wool run hello/world ./src/index.ts --outDir ./out

Application

An application that can call off to many commands.

import { application, command } from 'wool/terminal';

export default application({
  commands: {
    one: command({ ... }),
    two: command({ ... }),
  }
})
wool run hello/world one ./src/index.ts --outDir ./out

UI Program

A curses / blessed style view.

import * as Terminal from 'wool/terminal';
import { layout, el, text } from 'wool/ui';

export default Terminal.ui({
  args: [...],
  flags: [...],
  init: (cmd) => {},
  update: (msg, model) => {},
  view: (model) => layout([], el([], text`Hello, World!`)),
})
wool run hello/world

The syntax and functionality of wool/ui is a separate work-in-progress.

Questions

Command argument and flag syntax

Above, I've used a variable args syntax:

flag('clean');
flag('clean', alias('c'), boolean(false))

A strictly FP approach would look like:

boolean(alias(flag('clean'), 'c'), false)

But this is clearly a bit rubbish.

Another alternative would be chained functions, e.g.:

flag('clean').alias('c').boolean(false)
lsjroberts commented 5 years ago

Another alternative for args and flags would be:

export default command({
  args: {
    entry: required(),
  },
  flags: {
    clean: [alias('c'), boolean(false)],
    outDir: [alias('o'), string('./dist')],
  },
});
lsjroberts commented 5 years ago

Defaults could be shortcut with:

  flags: {
    clean: false,
    outDir: './dist',
  },
lsjroberts commented 5 years ago

How would terminal apps call other terminal apps, without requiring spawn permissions?

import say from 'hello/say';

export default Terminal.action(async () => {
  await say('Hello, World!');
});

(Since permissions are inherited from dependencies an app that imports the above app would also require approval for hello/say's permissions).

lsjroberts commented 5 years ago

And flags might be:

say('Hello', { colors: true })

Positional arguments occur before flags.

Would that cause issues with object args?