- Go 99.9%
- Shell 0.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
WasPassed guards against a Flags whose FlagSet was never set, which is what a caller building one by hand has. SourceOf called Lookup first, which dereferences it, so the same value panicked in one method and was answered in the other. Guard it the same way, and say in both that the answer is only meaningful after Parse: a flag cannot be known to have been passed before the flag package has read the arguments. |
||
| cmd/test | ||
| mtool | ||
| .gitignore | ||
| btest.sh | ||
| go.mod | ||
| go.sum | ||
| readme.md | ||
gocli
A package for command line programs in Go:
mtool— a multitool: many programs under one command, likegitorbb.
It has no dependencies.
go get codeberg.org/mojosa/gocli
Go 1.18 or later.
mtool
Most of the work happens here. A tree of commands, each with its own flags, descriptions and usage, and a help screen built from them:
package main
import (
"fmt"
"os"
"codeberg.org/mojosa/gocli/mtool"
)
func main() {
var repeat int
root := mtool.T("greet").Desc("say hello").Subs(
mtool.T("hello").Func(func(flags *mtool.Flags) {
flags.IntVar(&repeat, "n", 1, "how many times", "GREET_N")
args := flags.Parse()
for i := 0; i < repeat; i++ {
fmt.Println("hello", args)
}
}).Desc("greet someone").
Usage("<name>").
Ldesc("Prints a greeting, once by default."),
)
root.Run(os.Args[1:])
}
That gives you the subcommand, the flags, and the help:
$ greet
Name:
greet - say hello
Usage:
greet <command>
Commands:
hello greet someone
$ greet hello -h
Name:
greet hello - greet someone
Usage:
greet hello [options] <name>
Description:
Prints a greeting, once by default.
Options:
-- option terminator
-n int
how many times
($GREET_N) (default 1)
Three things are worth knowing.
Every flag can come from the environment. The names you pass after the usage string are tried in order, and a flag passed on the command line always wins:
flags.IntVar(&repeat, "n", 1, "how many times", "GREET_N", "GREET_COUNT")
A variable holding something the flag cannot take is reported rather than silently ignored, and the flag keeps the value it had.
You can ask where a value came from. flags.WasPassed(name) is true only
when the option was given on the command line, which is not the same as the
value differing from the default: -n 1 counts as passed even though 1 is
also the default. flags.SourceOf(name) answers "flag", "env" or
"default" directly.
flags.StringVar(&addr, "addr", "", "address to listen on", "PROG_ADDR")
flags.Parse()
if !flags.WasPassed("addr") && addr == "" {
addr = "0.0.0.0:8080" // neither the operator nor the environment said
}
That is the difference between "nobody said" and "somebody said empty", which matters when an empty value is a legitimate way to unset something.
Both answer for the parse that has already happened, so ask after Parse. A
flag is only known to have been passed once the flag package has read the
arguments; before that SourceOf reports the environment or the default.
Failures come back through flags.ParseErr. In the default mode a bad
command line ends the process the way the flag package does, since there is
no sane way to carry on. A tool that would rather decide for itself asks for
the quiet mode:
root := mtool.T("prog").SoftErrors().Subs(/* ... */)
// inside a handler:
args, err := flags.ParseArgs()
The help is plain text. There are no escape sequences anywhere in it, on a terminal or in a pipe, so the output is the same in a log, a pager or a screenshot.