funcv helps you create CLI tools with Go.
funcv offers a different approach for dealing with command line arguments and flags.
funcv supplies an easy to use command builder, you use that builder to build your set of commands, each such command can be tested against a slice of string arguments, if the arguments are compatible with the command, a given action function is called, the parameters for that function are the extracted and parsed variables and flags.
Let's see how it works with a simple example:
func main() {
cmd := funcv.NewCommand("delete a file").
AddConstant("delete", false).
AddVariable("filename", "file to delete", new(funcv.StringConverter)).
MustCompile()
if _, err := cmd.Execute(os.Args[1:], func(name string) {
fmt.Println("deleting", name, "...")
// ...
}); err != nil {
fmt.Fprintln(os.Stderr, "invalid command:", strings.Join(os.Args[1:], " "))
}
}
$ example delete song.mp3
deleting song.mp3 ...
First, we called the funcv.NewCommand
function with a command description, then, we used the returned builder (funcv.Builder
) to add our command components, a constant text ("delete") and a string variable ("filename"). The call for MustCompile()
finished the building process and returned the new command (failure.Command
).
Next, we called the command's Execute
method with a slice of string arguments (os.Args[1:]
) and an action function (func(name string){...}
).
The Execute
method tests the given arguments slice ([]string{"delete", "song.mp3"}
) and finds that it contains two arguments, the first argument equals "delete", therefore, the action function is called with the second argument as a parameter (name string
).
Currently supported list of arguments:
Comment | |
---|---|
Constant | Static word (allowed characters: 0-9, A-Z, a-z, _, -). |
Variable | With default value or without. |
Flag | -x or -x..x, with parameter or without. |
Variadic | Closing the list of arguments. |
The list of supported arguments is extendable via the funcv.Argument
interface.
Arguments that translates to function parameters (ex: not constant) require a func.Converter
.
The package includes converters for Strings, Integers, Floats and Booleans.
It is possible to group different commands together using a funcv.Group
:
func main() {
var grp funcv.Group
err := funcv.NewCommand("delete a file").
AddConstant("example", false).
AddConstant("delete", false).
AddParameterlessFlag("r", "move to recycle bin", new(funcv.BooleanConverter), false, true).
AddVariable("filename", "file to delete", new(funcv.StringConverter)).
ToGroup(&grp, func(recycle bool, name string) {
// the count, order and type of params must match the count, order
// and type of flags and variables in the command (excluding constants)
if recycle {
fmt.Println("recycling", name, "...")
} else {
fmt.Println("deleting", name, "...")
}
// ...
})
if err != nil {
panic(err)
}
err = funcv.NewCommand("print this help").
AddConstant("example", false).
AddConstant("help", false).
ToGroup(&grp, func() {
// groups, commands and arguments implement io.WriterTo
// and will write their informative usage text into the
// writer given to WriteTo(io.Writer)
w := new(tabwriter.Writer)
w.Init(os.Stdout, 18, 8, 0, '\t', 0)
defer w.Flush()
if _, err := grp.WriteTo(w); err != nil {
panic(err)
}
})
if err != nil {
panic(err)
}
// test against all commands in grp
// returns the number of executed commands
if grp.ExecuteAll(append([]string{"example"}, os.Args[1:]...)) == 0 {
fmt.Fprintln(os.Stderr, "invalid command:", strings.Join(os.Args[1:], " "))
}
}
$ example delete song.mp3
deleting song.mp3 ...
$ example delete -r song.mp3
recycling song.mp3 ...
$ example delete -r false song.mp3
deleting song.mp3 ...
$ example delete -r true song.mp3
recycling song.mp3 ...
$ example help
delete a file: > example delete [-r] <filename>
-r move to recycle bin (default: false)
filename file to delete
print this help: > example help
$ example typo
invalid command: typo
Variadic action functions support is available. Example:
func main() {
var grp funcv.Group
converter := new(funcv.IntegerConverter)
if err := funcv.NewCommand("add two numbers").
AddConstant("calc", false).
AddConstant("add", false).
AddVariable("1st", "first operand", converter).
AddVariable("2nd", "second operand", converter).
ToGroup(&grp, func(x, y int) {
fmt.Println(x, "+", y, "=", x+y, "(I)")
}); err != nil {
panic(err)
}
if err := funcv.NewCommand("add two or more numbers").
AddConstant("calc", false).
AddConstant("add", false).
AddVariable("1st", "first operand", converter).
AddVariable("2nd", "second operand", converter).
AddVariadic("operands", "list of operands", converter).
ToGroup(&grp, func(operands ...int) {
var sb strings.Builder
var sum int
for i, op := range operands {
sum += op
sb.WriteString(fmt.Sprint(op))
if i+1 < len(operands) {
sb.WriteString(" + ")
}
}
sb.WriteString(fmt.Sprintf(" = %d (II)", sum))
fmt.Println(sb.String())
}); err != nil {
panic(err)
}
if grp.ExecuteAll(append([]string{"calc"}, os.Args[1:]...)) == 0 {
fmt.Fprintln(os.Stderr, "invalid command:", strings.Join(os.Args[1:], " "))
}
}
$ calc add 1
invalid command: add 1
$ calc add 1 2
1 + 2 = 3 (I)
1 + 2 = 3 (II)
$ calc add 1 2 3
1 + 2 + 3 = 6 (II)
Use ExecuteFirst
if you want to stop executing commands after the first successful executed command, the method returns the index of the executed command within the group, [0, len(group)
), or a negative value if no command was found:
func main() {
// ... same commands as before ...
if grp.ExecuteFirst(append([]string{"calc"}, os.Args[1:]...)) < 0 {
fmt.Fprintln(os.Stderr, "invalid command:", strings.Join(os.Args[1:], " "))
}
}
$ calc add 1 2
1 + 2 = 3 (I)
If an action function returns an error (non-nil), that error will propagate through the command's Execute
method:
func main() {
var grp funcv.Group
if err := funcv.NewCommand("...").
AddConstant("run", false).
AddConstant("me", false).
ToGroup(&grp, func() error {
fmt.Println("1nd command")
return errors.New("...")
}); err != nil {
panic(err)
}
if err := funcv.NewCommand("...").
AddConstant("run", false).
AddConstant("me", false).
ToGroup(&grp, func() {
fmt.Println("2nd command")
}); err != nil {
panic(err)
}
if grp.ExecuteFirst(append([]string{"run"}, os.Args[1:]...)) < 0 {
fmt.Fprintln(os.Stderr, "invalid command:", strings.Join(os.Args[1:], " "))
}
}
$ run me
1nd command
2nd command
ExecuteFirst
stops after the first successful command, if an action function returns an error, the execution is considered to be unsuccessful.
$ go get -u github.com/moshenahmias/funcv