llm
Download
529 lines 14.3 KiBMarkdownModified 2026-09-28 raw

Command Line Parsing

Tomo supports automatic command line argument parsing for programs. Here's a simple example:

# greet.tm
func main(name:Text, be_excited|E:Bool=no)
    if be_excited
        say("Hello $name!!!")
    else
        say("Hi $name.")

This program will automatically support command line argument parsing for the arguments to main():

$ tomo build greet.tm
Compiled executable: greet

$ ./greet
greet: Required argument 'name' was not provided!
Usage: greet [--help] <name> [--be-excited|-E|--no-be-exited]

$ ./greet --help
Usage: greet [--help] <name> [--be-excited|-E|--no-be-excited]

$ ./greet "Zaphod"
Hi Zaphod. 

$ ./greet "Zaphod" --be-excited
Hello Zaphod!!!

$ ./greet "Zaphod" -E
Hello Zaphod!!!

$ ./greet --no-be-excited --name="Zaphod"
Hi Zaphod.

$ ./greet --not-a-real-argument "Bob"
greet: Unrecognized argument: --not-a-real-argument
Usage: greet [--help] <name> [--be-excited|-E|--no-be-excited]

Underscores in argument names are converted to dashes when parsing command line arguments.

Running Programs Directly

If you want to run a program directly (instead of compiling to an executable with tomo build), you can run the program with tomo program.tm -- [program arguments...]. The -- is required to separate the arguments passed to the Tomo compiler from those being passed to your program. For example, tomo greet.tm -- --help will pass the argument --help to your program, whereas tomo greet.tm --help will pass --help to tomo.

Positional vs Default Arguments

Any argument can be given either by explicit --flag value (or --flag=value) or positionally. Positional values fill the arguments that no flag has already filled, in the order the arguments are declared:

func main(name:Text, count:Int=1)
    ...
$ ./greet Zaphod        # name="Zaphod", count=1
$ ./greet Zaphod 3      # name="Zaphod", count=3
$ ./greet --count=3 Zaphod   # same thing: --count is filled first, so the
                             # positional "Zaphod" goes to name

Note that this includes arguments that have a default value, like count above. If an argument does not have a default value it is required, and the program reports a usage error when nothing fills it.

The same command-line machinery runs both your compiled programs and the tomo compiler itself, so tomo --help and ./yourprogram --help are laid out the same way.

A value that starts with - is rejected wherever it would be read as a value, since it looks like a flag. To pass a text value that really does start with a dash, either escape it with a backslash (./greet '\-weird', which also works in the --name='\-weird' form) or put it after a bare --:

$ ./greet -weird        # error: Not a valid flag: -weird
$ ./greet '\-weird'     # name="-weird"
$ ./greet -- -weird     # name="-weird"

Negative numbers are the exception: a dashed value is read as a number, not a flag, when the argument it fills is a numeric one. Nothing else could be meant by it, and it needs no escaping:

func main(count|c:Int, scale:Num=1.0)
    ...
$ ./scale --count -1            # count=-1
$ ./scale --count=-1            # count=-1
$ ./scale -c-1 --scale=-1.5     # count=-1, scale=-1.5
$ ./scale -1                    # count=-1, filled positionally
$ ./stats --nums -1 2 -3 --verbose   # nums=[-1, 2, -3]

Supported Argument Types

Tomo automatically supports several argument types out of the box, but if there is a type that isn't supported, you can always fall back to accepting a Text argument and parsing it yourself.

Text

Text arguments are the simplest: the input arguments are taken verbatim.

Bool

For a boolean argument, foo, the argument can be passed in several ways:

  • --foo or --no-foo provide the argument as yes/no respectively
  • --foo=yes/--foo=on/--foo=true/--foo=1 all parse as yes (case insensitive)
  • --foo=no/--foo=off/--foo=false/--foo=0 all parse as no (case insensitive)

An optional boolean (foo:Bool?) takes the same values, plus --foo=none for the third value. A bare --foo is still yes and --no-foo is still no.

  • Any other values will report a usage error

Integers and Numbers

Integer and number values can be passed and parsed automatically. Any failures to parse will cause a usage error. Integers support decimal (123), hexadecimal (0xFF), and octal values (0o644). Nums support regular (123 or 1.23) or scientific notation (1e99).

For fixed-size integers (Int64, Int32, Int16, Int8), arguments that exceed the representable range for those values are considered usage errors.

Structs

For structs, values can be passed using positional arguments for each struct field.

# foo.tm
struct Pair{x,y:Int}

func main(pair:Pair)
    >> pair


$ tomo foo.tm -- --pair 1 2
Pair{x=1, y=2}

Tomo does not currently support omitting fields with default values or passing individual struct fields by named flag.

Enums

For enums, values can be passed using the enum's tag name and each of its fields positionally (the same as for structs). Parsing is case-sensitive:

# foo.tm
enum Foo(Nothing, AnInteger{i:Int}, TwoThings{i:Int, text:Text})
func main(foo:Foo)
    >> foo

$ tomo foo.tm -- Nothing
Nothing

$ tomo foo.tm -- AnInteger 123
AnInteger{123}

$ tomo foo.tm -- TwoThings 123 hello
TwoThings{i=123, text="hello"}

Like structs, enums do not currently support passing fields as flags or omitting fields with default values.

Lists of Text

Currently, Tomo supports accepting arguments that take a list of text. List-of-text arguments can be passed like this:

# many-texts.tm
func main(args:[Text])
    >> args
$ tomo many-texts.tm
>> [] : [Text]

$ tomo many-texts.tm -- one two three
>> ["one", "two", "three"] : [Text]

$ tomo many-texts.tm -- one --not-a-flag 'a space'
>> ["one", "--not-a-flag", "a space"] : [Text]

Aliases and Flag Arguments

Each argument may optionally have an alias of the form name|alias. This allows you to specify a long-form argument and a single-letter flag like verbose|v = no. Single letter flags (whether as an alias or as a main flag name) have slightly different command line parsing rules:

  • Single letter flags use only a single dash: -v vs --verbose
  • Single letter flags can coalesce with other single letter flags: -abc is the same as -a -b -c

When single letter flags coalesce together, the first flags in the cluster must be boolean values, while the last one is allowed to be any type. This lets you specify several flags at once while still providing arguments:

func main(output|o:Path? = none, verbose|v:Bool = no)
    ...
$ tomo build program.tm && ./program -vo outfile.txt`

Return Values

main() may return a value, which the program prints when it runs:

# add.tm
func main(x,y:Int -> Int)
    return x + y
$ ./add 2 3
5

Any type can be returned. The value is printed the way >> prints one, with syntax highlighting when the output is going to a terminal and without it when the output is piped into another program or NO_COLOR is set:

# point.tm
struct Point{x,y:Int}

func main(-> Point)
    return Point{1, 2}
$ ./point
Point{x=1, y=2}

Subcommands

For git-style CLIs, you can define subcommands by declaring functions named main.<command> instead of a single main() function:

# mygit.tm

# Initialize a new repository
func main.init(bare:Bool=no)
    ...

# Add file contents to the index
func main.add(files:[Text], force|f:Bool=no)
    ...

# Initialize submodules
func main.submodule.init(paths:[Text])
    ...

Each subcommand function gets the same automatic argument parsing that main() gets, including flags, defaults, aliases, --help, and printing whatever the function returns:

$ tomo build mygit.tm

$ ./mygit add --force a.txt b.txt
$ ./mygit submodule init vendor/foo

$ ./mygit add --help
./mygit add: Add file contents to the index

Usage: ./mygit add --files text1 text2... [--force|-f]

Flags:
  --files text1 text2...
  -f, --force|--no-force (default:no)

Running the program with no command (or an unknown one) prints a listing of the available commands, using the comment above each function as its one-line summary:

$ ./mygit
./mygit

Usage: ./mygit <command> ...

Commands:
  init       Initialize a new repository
  add        Add file contents to the index
  submodule  <command> ...

Subcommands can be nested arbitrarily deep (main.submodule.init handles mygit submodule init), and a command can both do something itself and have sub-subcommands, like git stash and git stash pop:

# Stash changes away
func main.stash(message:Text? = none)
    ...

# Remove a stash entry
func main.stash.pop(index:Int = 0)
    ...

Dispatch descends into the longest matching command path: mygit stash pop runs main.stash.pop(), while mygit stash runs main.stash().

A program with subcommands can also define a plain main(), which runs when the first argument doesn't name a subcommand:

# Greet someone by name
func main(name:Text="world")
    say("hello $name")

# Initialize a new repository
func main.init(bare:Bool=no)
    ...
$ ./myprog             # no subcommand: runs main()
hello world
$ ./myprog --name=Ford  # still main()
hello Ford
$ ./myprog init         # runs main.init()

This is the same longest-match rule used for nested commands: if the next word names a subcommand, dispatch descends into it, otherwise the current command's own handler runs with the remaining arguments. main()'s flags appear in the root --help above the command listing.

If a positional argument's value happens to collide with a subcommand name, the subcommand wins; pass the value after -- (./myprog -- init) or as an explicit flag (./myprog --word=init) to disambiguate.

Each flag belongs to its own subcommand: main()'s flags are not accepted after a subcommand name, so there are no program-wide "global flags".

Underscores in subcommand names are converted to dashes on the command line, the same as flag names: func main.dry_run() handles myprogram dry-run.

Subcommands Can Be Split Across Files

A subcommand belongs to the interface of whatever program can see it, so useing a file brings its subcommands along with the rest of its namespace:

# commands.tm

# Add file contents to the index
func main.add(files:[Text], force|f:Bool=no)
    ...
# mygit.tm
use ./commands.tm

# Record changes to the repository
func main.commit(message|m:Text)
    ...
$ ./mygit --help
...
Commands:
  add     Add file contents to the index
  commit  Record changes to the repository

$ ./mygit add --force a.txt   # defined in commands.tm

Imported commands are listed before the program's own, in the order the use statements appear. Two definitions of one command can't both be dispatched to, so defining a command that a used file already defines is a compile error.

A module imported under a name keeps its subcommands to itself, since its bindings stay in its own namespace: commands := use ./commands.tm makes commands.main.add(...) callable from Tomo code without adding an add command to this program's command line.

Subcommands Are Ordinary Functions

Subcommand functions are regular functions that the compiler additionally wires into the command-line dispatcher. You can call them directly, use them as function values, and access them from other files:

func main.commit(message:Text, all|a:Bool=no)
    if all
        main.add(files=modified_files(), force=no)
    ...

func main.show()
    fn := main.add
    fn(["via a function pointer"], yes)

Calling a subcommand function directly bypasses command-line parsing entirely: the arguments are ordinary typed function arguments. This also makes subcommands easy to test:

# test.tm
mygit := use ./mygit.tm

test "adding files"
    mygit.main.add([(./foo.txt)], force=yes)
    ...

Subcommand functions are constants: assigning to main.add is a compile-time error, and a command path prefix like main.submodule is not a value by itself.

Help and Manpages

When your program is generated, it will also come with a --help flag (unless you have one defined) with automatically generated usage information. If you add comments in front of your main function arguments, they will appear in the --help output. Additionally, when your program is compiled, Tomo will also build a Manpage for your program in .tomo/yourprogram.1, which will get installed if you install your program.

func main(
    # Whether or not to frob your gropnoggles
    frob: Bool = no
)
    pass
$ tomo build myprogram.tm
$ ./myprogram --help
# Usage: ./myprogram [--help] [--frob|--no-frob]
#
#  --frob|--no-frob Whether or not to frob your gropnoggles (default:no)
#
$ man .tomo/myprogram.1
MYPROGRAM(1)

NAME
       myprogram - a Tomo program

OPTIONS
       --frob | --no-frob
              Whether or not to frob your gropnoggles

Metadata

You can specify metadata for a program, which is used for CLI messages like --help, as well as manpage documentation. Metadata can be specified as either a text literal (no interpolation) or as a file path literal.

USAGE: "--foo <n>"
HELP: "
    This is some custom help text.
    You can use these flags:

    --foo <n>  The foo parameter
    --help     Show this message
"
MANPAGE_DESCRIPTION: (./description.roff)

Supported metadata:

  • EXECUTABLE: the name of the executable to compile. If not provided, the name will be the name of the Tomo source file without the ".tm" extension.

  • USAGE: the short form usage shown in CLI parsing errors and help pages. This should be a single line without the name of the program, so USAGE: "--foo" would translate to the error message Usage: myprogram --foo. If this is not present, it will be generated automatically.

  • HELP: The help message displayed when the --help flag is used or when there is an argument parsing error. This should be a description of the program with a multi-line documentation of commonly used flags.

  • MANPAGE: the full manpage (overrules the options below).

  • MANPAGE_SYNOPSYS: the synopsis section of the manpage (inserted literally).

  • MANPAGE_DESCRIPTION: the description section of the manpage (inserted literally).

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
# Command Line Parsing

Tomo supports automatic command line argument parsing for programs.
Here's a simple example:

```tomo
# greet.tm
func main(name:Text, be_excited|E:Bool=no)
    if be_excited
        say("Hello $name!!!")
    else
        say("Hi $name.")
```

This program will automatically support command line argument parsing
for the arguments to `main()`:

```bash
$ tomo build greet.tm
Compiled executable: greet

$ ./greet
greet: Required argument 'name' was not provided!
Usage: greet [--help] <name> [--be-excited|-E|--no-be-exited]

$ ./greet --help
Usage: greet [--help] <name> [--be-excited|-E|--no-be-excited]

$ ./greet "Zaphod"
Hi Zaphod. 

$ ./greet "Zaphod" --be-excited
Hello Zaphod!!!

$ ./greet "Zaphod" -E
Hello Zaphod!!!

$ ./greet --no-be-excited --name="Zaphod"
Hi Zaphod.

$ ./greet --not-a-real-argument "Bob"
greet: Unrecognized argument: --not-a-real-argument
Usage: greet [--help] <name> [--be-excited|-E|--no-be-excited]
```

Underscores in argument names are converted to dashes when parsing command line
arguments.

## Running Programs Directly

If you want to run a program directly (instead of compiling to an executable
with `tomo build`), you can run the program with `tomo program.tm -- [program
arguments...]`. The `--` is required to separate the arguments passed to the
Tomo compiler from those being passed to your program. For example, `tomo
greet.tm -- --help` will pass the argument `--help` to your program, whereas
`tomo greet.tm --help` will pass `--help` to `tomo`.

## Positional vs Default Arguments

Any argument can be given either by explicit `--flag value` (or `--flag=value`)
or positionally. Positional values fill the arguments that no flag has already
filled, in the order the arguments are declared:

```tomo
func main(name:Text, count:Int=1)
    ...
```
```bash
$ ./greet Zaphod        # name="Zaphod", count=1
$ ./greet Zaphod 3      # name="Zaphod", count=3
$ ./greet --count=3 Zaphod   # same thing: --count is filled first, so the
                             # positional "Zaphod" goes to name
```

Note that this includes arguments that have a default value, like `count`
above. If an argument does *not* have a default value it is required, and the
program reports a usage error when nothing fills it.

The same command-line machinery runs both your compiled programs and the
`tomo` compiler itself, so `tomo --help` and `./yourprogram --help` are laid
out the same way.

A value that starts with `-` is rejected wherever it would be read as a value,
since it looks like a flag. To pass a text value that really does start with a
dash, either escape it with a backslash (`./greet '\-weird'`, which also
works in the `--name='\-weird'` form) or put it after a bare `--`:

```bash
$ ./greet -weird        # error: Not a valid flag: -weird
$ ./greet '\-weird'     # name="-weird"
$ ./greet -- -weird     # name="-weird"
```

Negative numbers are the exception: a dashed value is read as a number, not a
flag, when the argument it fills is a numeric one. Nothing else could be meant
by it, and it needs no escaping:

```tomo
func main(count|c:Int, scale:Num=1.0)
    ...
```
```bash
$ ./scale --count -1            # count=-1
$ ./scale --count=-1            # count=-1
$ ./scale -c-1 --scale=-1.5     # count=-1, scale=-1.5
$ ./scale -1                    # count=-1, filled positionally
```

```bash
$ ./stats --nums -1 2 -3 --verbose   # nums=[-1, 2, -3]
```

## Supported Argument Types

Tomo automatically supports several argument types out of the box, but if there
is a type that isn't supported, you can always fall back to accepting a `Text`
argument and parsing it yourself.

### Text

Text arguments are the simplest: the input arguments are taken verbatim.

### Bool

For a boolean argument, `foo`, the argument can be passed in several ways:

- `--foo` or `--no-foo` provide the argument as `yes`/`no` respectively
- `--foo=yes`/`--foo=on`/`--foo=true`/`--foo=1` all parse as `yes` (case insensitive)
- `--foo=no`/`--foo=off`/`--foo=false`/`--foo=0` all parse as `no` (case insensitive)

An optional boolean (`foo:Bool?`) takes the same values, plus `--foo=none`
for the third value. A bare `--foo` is still `yes` and `--no-foo` is still `no`.

- Any other values will report a usage error

### Integers and Numbers

Integer and number values can be passed and parsed automatically. Any failures
to parse will cause a usage error. Integers support decimal (`123`),
hexadecimal (`0xFF`), and octal values (`0o644`). Nums support regular (`123`
or `1.23`) or scientific notation (`1e99`).

For fixed-size integers (`Int64`, `Int32`, `Int16`, `Int8`), arguments that
exceed the representable range for those values are considered usage errors.

### Structs

For structs, values can be passed using positional arguments for each struct
field.

```tomo
# foo.tm
struct Pair{x,y:Int}

func main(pair:Pair)
    >> pair


$ tomo foo.tm -- --pair 1 2
Pair{x=1, y=2}
```

Tomo does not currently support omitting fields with default values or passing
individual struct fields by named flag.

### Enums

For enums, values can be passed using the enum's tag name and each of its
fields positionally (the same as for structs). Parsing is case-sensitive:

```tomo
# foo.tm
enum Foo(Nothing, AnInteger{i:Int}, TwoThings{i:Int, text:Text})
func main(foo:Foo)
    >> foo

$ tomo foo.tm -- Nothing
Nothing

$ tomo foo.tm -- AnInteger 123
AnInteger{123}

$ tomo foo.tm -- TwoThings 123 hello
TwoThings{i=123, text="hello"}
```

Like structs, enums do not currently support passing fields as flags or
omitting fields with default values.

### Lists of Text

Currently, Tomo supports accepting arguments that take a list of text.
List-of-text arguments can be passed like this:

```tomo
# many-texts.tm
func main(args:[Text])
    >> args
```

```bash
$ tomo many-texts.tm
>> [] : [Text]

$ tomo many-texts.tm -- one two three
>> ["one", "two", "three"] : [Text]

$ tomo many-texts.tm -- one --not-a-flag 'a space'
>> ["one", "--not-a-flag", "a space"] : [Text]
```

## Aliases and Flag Arguments

Each argument may optionally have an alias of the form `name|alias`. This allows
you to specify a long-form argument and a single-letter flag like `verbose|v =
no`. Single letter flags (whether as an alias or as a main flag name) have
slightly different command line parsing rules:

- Single letter flags use only a single dash: `-v` vs `--verbose`
- Single letter flags can coalesce with other single letter flags: `-abc` is the
same as `-a -b -c`

When single letter flags coalesce together, the first flags in the cluster must
be boolean values, while the last one is allowed to be any type. This lets you
specify several flags at once while still providing arguments:

```tomo
func main(output|o:Path? = none, verbose|v:Bool = no)
    ...
```
```bash
$ tomo build program.tm && ./program -vo outfile.txt`
```

## Return Values

`main()` may return a value, which the program prints when it runs:

```tomo
# add.tm
func main(x,y:Int -> Int)
    return x + y
```

```bash
$ ./add 2 3
5
```

Any type can be returned. The value is printed the way `>>` prints one, with
syntax highlighting when the output is going to a terminal and without it when
the output is piped into another program or `NO_COLOR` is set:

```tomo
# point.tm
struct Point{x,y:Int}

func main(-> Point)
    return Point{1, 2}
```

```bash
$ ./point
Point{x=1, y=2}
```

## Subcommands

For git-style CLIs, you can define subcommands by declaring functions named
`main.<command>` instead of a single `main()` function:

```tomo
# mygit.tm

# Initialize a new repository
func main.init(bare:Bool=no)
    ...

# Add file contents to the index
func main.add(files:[Text], force|f:Bool=no)
    ...

# Initialize submodules
func main.submodule.init(paths:[Text])
    ...
```

Each subcommand function gets the same automatic argument parsing that
`main()` gets, including flags, defaults, aliases, `--help`, and printing
whatever the function returns:

```bash
$ tomo build mygit.tm

$ ./mygit add --force a.txt b.txt
$ ./mygit submodule init vendor/foo

$ ./mygit add --help
./mygit add: Add file contents to the index

Usage: ./mygit add --files text1 text2... [--force|-f]

Flags:
  --files text1 text2...
  -f, --force|--no-force (default:no)
```

Running the program with no command (or an unknown one) prints a listing of
the available commands, using the comment above each function as its one-line
summary:

```bash
$ ./mygit
./mygit

Usage: ./mygit <command> ...

Commands:
  init       Initialize a new repository
  add        Add file contents to the index
  submodule  <command> ...
```

Subcommands can be nested arbitrarily deep (`main.submodule.init` handles
`mygit submodule init`), and a command can both do something itself *and* have
sub-subcommands, like `git stash` and `git stash pop`:

```tomo
# Stash changes away
func main.stash(message:Text? = none)
    ...

# Remove a stash entry
func main.stash.pop(index:Int = 0)
    ...
```

Dispatch descends into the longest matching command path: `mygit stash pop`
runs `main.stash.pop()`, while `mygit stash` runs `main.stash()`.

A program with subcommands can also define a plain `main()`, which runs when
the first argument doesn't name a subcommand:

```tomo
# Greet someone by name
func main(name:Text="world")
    say("hello $name")

# Initialize a new repository
func main.init(bare:Bool=no)
    ...
```

```bash
$ ./myprog             # no subcommand: runs main()
hello world
$ ./myprog --name=Ford  # still main()
hello Ford
$ ./myprog init         # runs main.init()
```

This is the same longest-match rule used for nested commands: if the next word
names a subcommand, dispatch descends into it, otherwise the current command's
own handler runs with the remaining arguments. `main()`'s flags appear in the
root `--help` above the command listing.

If a positional argument's value happens to collide with a subcommand name,
the subcommand wins; pass the value after `--` (`./myprog -- init`) or as an
explicit flag (`./myprog --word=init`) to disambiguate.

Each flag belongs to its own subcommand: `main()`'s flags are *not* accepted
after a subcommand name, so there are no program-wide "global flags".

Underscores in subcommand names are converted to dashes on the command line,
the same as flag names: `func main.dry_run()` handles `myprogram dry-run`.

### Subcommands Can Be Split Across Files

A subcommand belongs to the interface of whatever program can see it, so
`use`ing a file brings its subcommands along with the rest of its namespace:

```tomo
# commands.tm

# Add file contents to the index
func main.add(files:[Text], force|f:Bool=no)
    ...
```

```tomo
# mygit.tm
use ./commands.tm

# Record changes to the repository
func main.commit(message|m:Text)
    ...
```

```bash
$ ./mygit --help
...
Commands:
  add     Add file contents to the index
  commit  Record changes to the repository

$ ./mygit add --force a.txt   # defined in commands.tm
```

Imported commands are listed before the program's own, in the order the `use`
statements appear. Two definitions of one command can't both be dispatched to,
so defining a command that a `use`d file already defines is a compile error.

A module imported under a name keeps its subcommands to itself, since its
bindings stay in its own namespace: `commands := use ./commands.tm` makes
`commands.main.add(...)` callable from Tomo code without adding an `add` command
to this program's command line.

### Subcommands Are Ordinary Functions

Subcommand functions are regular functions that the compiler additionally
wires into the command-line dispatcher. You can call them directly, use them
as function values, and access them from other files:

```tomo
func main.commit(message:Text, all|a:Bool=no)
    if all
        main.add(files=modified_files(), force=no)
    ...

func main.show()
    fn := main.add
    fn(["via a function pointer"], yes)
```

Calling a subcommand function directly bypasses command-line parsing entirely:
the arguments are ordinary typed function arguments. This also makes
subcommands easy to test:

```tomo
# test.tm
mygit := use ./mygit.tm

test "adding files"
    mygit.main.add([(./foo.txt)], force=yes)
    ...
```

Subcommand functions are constants: assigning to `main.add` is a compile-time
error, and a command path prefix like `main.submodule` is not a value by
itself.

## Help and Manpages

When your program is generated, it will also come with a `--help` flag (unless
you have one defined) with automatically generated usage information. If you
add comments in front of your main function arguments, they will appear in the
`--help` output. Additionally, when your program is compiled, Tomo will also
build a Manpage for your program in `.tomo/yourprogram.1`, which will get
installed if you install your program.

```tomo
func main(
    # Whether or not to frob your gropnoggles
    frob: Bool = no
)
    pass
```

```bash
$ tomo build myprogram.tm
$ ./myprogram --help
# Usage: ./myprogram [--help] [--frob|--no-frob]
#
#  --frob|--no-frob Whether or not to frob your gropnoggles (default:no)
#
```

```bash
$ man .tomo/myprogram.1
```

```
MYPROGRAM(1)

NAME
       myprogram - a Tomo program

OPTIONS
       --frob | --no-frob
              Whether or not to frob your gropnoggles
```

## Metadata

You can specify metadata for a program, which is used for CLI messages like
`--help`, as well as manpage documentation. Metadata can be specified as either
a text literal (no interpolation) or as a file path literal.

```
USAGE: "--foo <n>"
HELP: "
    This is some custom help text.
    You can use these flags:

    --foo <n>  The foo parameter
    --help     Show this message
"
MANPAGE_DESCRIPTION: (./description.roff)
```

Supported metadata:

- `EXECUTABLE`: the name of the executable to compile. If not provided, the name
  will be the name of the Tomo source file without the ".tm" extension.

- `USAGE`: the short form usage shown in CLI parsing errors and help pages. This
  should be a single line without the name of the program, so `USAGE: "--foo"`
  would translate to the error message `Usage: myprogram --foo`. If this is not
  present, it will be generated automatically.

- `HELP`: The help message displayed when the `--help` flag is used or when there
  is an argument parsing error. This should be a description of the program with
  a multi-line documentation of commonly used flags.

- `MANPAGE`: the full manpage (overrules the options below).

- `MANPAGE_SYNOPSYS`: the synopsis section of the manpage (inserted literally).

- `MANPAGE_DESCRIPTION`: the description section of the manpage (inserted literally).