Clo is a Golang module to build declarations of a command-line app objectives - commands and
arguments with values. Clo processes user provided command-line app input string (args) and returns
a structured Request
object. Clo takes care of a help
command (perhaps more in the future).
While clo has been designed with Go 1.16 embedding in mind, we'll provide directions on how to use it today and will update once 1.16 is released.
- Run
go get github.com/boggydigital/clo
- Import
github.com/boggydgital/clo
- Create declarations in
clo.json
- Load definitions using
defs, _ := clo.LoadDefinitions("clo.json")
- Call
clo.Parse(os.Args[1:], defs)
to get aRequest
struct - Route to app code using
Request.Command
and passRequest.Arguments
Here is an example of a main.go
that implements this approach (NOTE: error handling is omitted for
brevity)
package main
import (
"github.com/boggydigital/clo"
"{your-app-module}/cmd"
"os"
)
func main() {
defs, _ := clo.LoadDefinitions("clo.json")
// Parse `os.Args` using definitions to get `Request` data
req, _ := clo.Parse(os.Args[1:], defs)
// Route request to app command handlers
cmd.Route(req, defs)
}
To route command-line objectives to app handlers you might add a cmd/route.go
with a
single Route
func that routes arguments data to command handlers.
NOTE: In order to allow clo to handle built-in commands, in your Route
handler you need to
send nil
and unknown commands to clo.Route
.
Example:
package cmd
import (
"github.com/boggydigital/clo"
)
func Route(req *clo.Request, defs *clo.Definitions) error {
// allow clo to handle nil requests (this will show help by default)
if req == nil {
return clo.Route(nil, defs)
}
switch req.Command {
case "yourCommand":
// route yourCommand here
// ...
default:
// allow clo to handle unknown commands
return clo.Route(req, defs)
}
return nil
}
Request
provides few shortcuts to get values:
ArgVal(arg string)
- gets a single (first) value for an argumentArgValues(arg string)
- gets all argument values specified in aRequest
Flag(arg string)
- returns true is argument has been provided (with or without values)
Example:
package cmd
import (
"github.com/boggydigital/clo"
)
func Route(req *clo.Request, defs *clo.Definitions) error {
if req == nil {
return clo.Route(nil, defs)
}
switch req.Command {
case "validate":
return Validate(req.ArgVal("path"), req.Flag("verbose"))
default:
return clo.Route(req, defs)
}
}
where Validate
is defined as:
package cmd
func Validate(path string, verbose bool) error {
// validate a file and display each test results if verbose was requested
// ...
return nil
}
App that uses clo would support the following calling convention:
app command [arguments [values]]
- Commands don't have any prefix. Commands can be specified by a prefix - the first command to match provided prefix would be used.
- Arguments are specified with
-
or--
prefixes.--debug
is the same as-debug
and, assuming there are no other arguments that start withd
, it'll be the same as--d
and-d
- Values are specified without any prefix.
Clo.json has the following top-level properties:
version
- version of the definitions file, currently the only supported version is1
cmd
- commands, arguments specified as a map:
{
"cmd": {
"validate": [
"path",
"verbose"
]
}
}
help
- help messages specified for topics (topic is a:
delimited list ofcommand:argument
):
{
"help": {
"clo": "command-line objectives",
"validate": "validates that provided file doesn't have errors",
"validate:path": "path to the file that should be validated",
"validate:verbose": "print result of every validation test"
}
}
Command arguments can also specify values supported by that argument. If values are specified for an
argument, only those values would be considered valid and processing user input would stop when a
different value is specified. Values are specified following a =
sign:
{
"cmd": {
"command1": [
"argument1=value1,value2",
"argument2"
]
}
}
In that declaration argument1
can only be specified with either value1
or value2
,
while argument2
can be specified with any arbitrary value.
To provide more control to authors clo supports several inline attributes that can be used to define constraints or set defaults:
-
_
- default. Applies to command, attributes, values:- When specified on a command, this command will be used if no other command has been a match.
Example:
clo clo.json
is the same asclo validate clo.json
ifvalidate_
command was specified as default. - When specified on an argument, this argument will be used if no other argument has been a
match. Example:
clo validate clo.json
is the same asclo validate --path clo.json
ifpath_
was specified as default. - When specified on a value, this argument value pair will be added to
Request
if the user didn't provide another value for that pair.
- When specified on a command, this command will be used if no other command has been a match.
Example:
-
!
- required. Applies to arguments. If set - argument value must be provided (can be in a form of default value). Example:
{
"cmd": {
"validate_": [
"path_!"
]
}
}
-
...
- multiple. Applies to arguments. If set - argument can take more than one value. If not set - parsing multiple values for such argument would result in error. -
$
- env. variable. Applies to arguments. If set - argument value can be read from env. variable, unless specified by the user.