Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Creating CLI Apps #2

Open
lsjroberts opened this issue Nov 28, 2018 · 4 comments
Open

Creating CLI Apps #2

lsjroberts opened this issue Nov 28, 2018 · 4 comments
Labels
discussion Ideas open for discussion

Comments

@lsjroberts
Copy link
Contributor

lsjroberts commented Nov 28, 2018

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 Security & Package Authorship #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 lsjroberts added the discussion Ideas open for discussion label Nov 28, 2018
@lsjroberts
Copy link
Contributor Author

lsjroberts commented Nov 28, 2018

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
Copy link
Contributor Author

Defaults could be shortcut with:

  flags: {
    clean: false,
    outDir: './dist',
  },

@lsjroberts
Copy link
Contributor Author

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
Copy link
Contributor Author

And flags might be:

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

Positional arguments occur before flags.

Would that cause issues with object args?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion Ideas open for discussion
Projects
None yet
Development

No branches or pull requests

1 participant