Documentation
¶
Overview ¶
Example (Cargo) ¶
/*
A very partial unfaithful implementation of cargo's command line.
This showcases some harier patterns, like subcommands and custom value parsing.
*/
package main
import (
"errors"
"fmt"
"log"
"os"
"strconv"
"strings"
"github.com/jcbhmr/go-lexopt"
. "github.com/jcbhmr/go-lexopt/prelude"
)
const help = "cargo [+toolchain] [OPTIONS] [SUBCOMMAND]"
func main() {
os.Args = []string{"cargo", "+nightly", "--verbose", "--color=never", "install", "hello", "--root", "/home/octocat/project1", "--jobs", "8"}
log.SetFlags(0)
settings := globalSettings{
toolchain: "stable",
color: colorAuto,
offline: false,
quiet: false,
verbose: false,
}
parser := lexopt.ParserFromEnv()
for {
arg, ok, err := parser.Next()
fmt.Printf("parser=%#+v, arg=%#+v, ok=%#+v, err=%#+v\n", parser, arg, ok, err)
if err != nil {
log.Fatal(err)
}
if !ok {
break
}
if argV, ok := arg.(Long); ok && argV.A == "color" {
colorText, err := parser.Value()
if err != nil {
log.Fatal(err)
}
color, err2 := colorFromStr(colorText)
if err2 != "" {
err = &lexopt.ErrorCustom{errors.New(err2)}
log.Fatal(err)
}
settings.color = color
} else if argV, ok := arg.(Long); ok && argV.A == "offline" {
settings.offline = true
} else if argV, ok := arg.(Long); ok && argV.A == "quiet" {
settings.quiet = true
settings.verbose = false
} else if argV, ok := arg.(Long); ok && argV.A == "verbose" {
settings.quiet = false
settings.verbose = true
} else if argV, ok := arg.(Long); ok && argV.A == "help" {
fmt.Println(help)
os.Exit(0)
} else if value, ok := arg.(Value); ok {
if strings.HasPrefix(value.A, "+") {
settings.toolchain = value.A[1:]
} else if value.A == "install" {
err := install(settings, parser)
if err != nil {
log.Fatal(err)
}
return
} else {
log.Fatalf("Unknown subcommand '%v'", value.A)
}
} else {
log.Fatal(arg.Unexpected())
}
}
fmt.Println(help)
}
type globalSettings struct {
toolchain string
color color
offline bool
quiet bool
verbose bool
}
func install(settings globalSettings, parser *lexopt.Parser) lexopt.Error {
var package_ *string = nil
var root *string = nil
var jobs uint16 = getNoOfCPUs()
for {
arg, ok, err := parser.Next()
if err != nil {
return err
}
if !ok {
break
}
argShort, argShortOk := arg.(Short)
argLong, argLongOk := arg.(Long)
if value, ok := arg.(Value); package_ == nil && ok {
package_ = &value.A
} else if (arg == Long{"root"}) {
rootText, err := parser.Value()
if err != nil {
return err
}
root = &rootText
} else if (argShortOk && argShort.A == 'j') || (argLongOk && argLong.A == "jobs") {
jobsText, err := parser.Value()
if err != nil {
return err
}
jobs64, err2 := strconv.ParseUint(jobsText, 10, 16)
if err2 != nil {
return &lexopt.ErrorCustom{err2}
}
jobs = uint16(jobs64)
} else if argV, ok := arg.(Long); ok && argV.A == "help" {
fmt.Println("cargo install [OPTIONS] CRATE")
os.Exit(0)
} else {
return arg.Unexpected()
}
}
fmt.Printf("Settings: %#+v\n", settings)
if package_ == nil {
return &lexopt.ErrorCustom{errors.New("missing CRATE argument")}
}
fmt.Printf("Installing %v into %#v with %v jobs\n", *package_, root, jobs)
return nil
}
type color uint8
const (
colorAuto color = iota
colorAlways
colorNever
)
func colorFromStr(s string) (color, string) {
switch strings.ToLower(s) {
case "auto":
return colorAuto, ""
case "always":
return colorAlways, ""
case "never":
return colorNever, ""
default:
return 0, fmt.Sprintf("Invalid style '%v' [pick from: auto, always, never]", s)
}
}
func getNoOfCPUs() uint16 {
return 4
}
Output: Settings: lexopt_test.globalSettings{toolchain:"nightly", color:0, offline:false, quiet:false, verbose:true} Installing hello into /home/octocat/project1 with 8 jobs
Example (Hello) ¶
package main
import (
"errors"
"fmt"
"log"
"os"
"strconv"
"strings"
"github.com/jcbhmr/go-lexopt"
. "github.com/jcbhmr/go-lexopt/prelude"
)
type args struct {
thing string
number uint32
shout bool
}
func parseArgs() (args, error) {
var thing *string = nil
var number uint32 = 1
var shout bool = false
parser := lexopt.ParserFromEnv()
for {
arg, ok, err := parser.Next()
if err != nil {
return args{}, err
}
if !ok {
break
}
if (arg == Short{'n'}) || (arg == Long{"number"}) {
numberText, err := parser.Value()
if err != nil {
return args{}, err
}
number64, err2 := strconv.ParseUint(numberText, 10, 32)
if err2 != nil {
return args{}, err2
}
number = uint32(number64)
} else if (arg == Long{"shout"}) {
shout = true
} else if val, ok := arg.(Value); thing == nil && ok {
v := val.A
thing = &v
} else if (arg == Long{"help"}) {
fmt.Println("Usage: hello [-n|--number=NUM] [--shout] THING")
os.Exit(0)
} else {
return args{}, arg.Unexpected()
}
}
if thing == nil {
return args{}, errors.New("missing argument THING")
}
return args{
thing: *thing,
number: number,
shout: shout,
}, nil
}
func main() {
os.Args = []string{"hello", "--number=3", "--shout", "Alan Turing"}
log.SetFlags(0)
args, err := parseArgs()
if err != nil {
log.Fatal(err)
}
message := fmt.Sprintf("Hello %s!", args.thing)
if args.shout {
message = strings.ToUpper(message)
}
for i := uint32(0); i < args.number; i++ {
fmt.Println(message)
}
}
Output: HELLO ALAN TURING!
Example (Nonstandard) ¶
/*
Some programs accept options with an unusual syntax. For example, tail
accepts -13 as an alias for -n 13.
This program shows how to use parser.TryRawArgs() to handle them
manually.
(Note: actual tail implementations handle it slightly differently! This
is just an example.)
*/
package main
import (
"log"
"os"
"strconv"
"strings"
"github.com/jcbhmr/go-lexopt"
. "github.com/jcbhmr/go-lexopt/prelude"
)
func parseDashnum(parser *lexopt.Parser) (uint64, bool) {
raw, ok := parser.TryRawArgs()
if !ok {
return 0, false
}
arg, ok := raw.Peek()
if !ok {
return 0, false
}
num, err := strconv.ParseUint(strings.TrimPrefix(arg, "-"), 10, 64)
if err != nil {
return 0, false
}
raw.Next() // Consume the argument we just parsed
return num, true
}
func main() {
os.Args = []string{"nonstandard", "-13", "--number=42", "--follow", "file.txt"}
log.SetFlags(0)
parser := lexopt.ParserFromEnv()
for {
if num, ok := parseDashnum(parser); ok {
log.Printf("Got number %v", num)
} else {
arg, ok, err := parser.Next()
if err != nil {
log.Fatal(err)
}
if ok {
argShort, argShortOk := arg.(Short)
argLong, argLongOk := arg.(Long)
if (argShortOk && argShort.A == 'f') || (argLongOk && argLong.A == "follow") {
log.Println("Got --follow")
} else if (argShortOk && argShort.A == 'n') || (argLongOk && argLong.A == "number") {
numText, err := parser.Value()
if err != nil {
log.Fatal(err)
}
num, err2 := strconv.ParseUint(numText, 10, 64)
if err2 != nil {
log.Fatal(err2)
}
log.Printf("Got number %v", num)
} else if argV, ok := arg.(Value); ok {
log.Printf("Got file %v", argV.A)
}
} else {
break
}
}
}
}
Index ¶
- type Arg
- type ArgLong
- type ArgShort
- type ArgValue
- type Error
- type ErrorCustom
- type ErrorMissingValue
- type ErrorNonUnicodeValue
- type ErrorParsingFailed
- type ErrorUnexpectedArgument
- type ErrorUnexpectedOption
- type ErrorUnexpectedValue
- type Parser
- func (p *Parser) BinName() (string, bool)
- func (p *Parser) Next() (Arg, bool, Error)
- func (p *Parser) OptionalValue() (string, bool)
- func (p *Parser) RawArgs() (*RawArgs, Error)
- func (p *Parser) TryRawArgs() (*RawArgs, bool)
- func (p *Parser) Value() (string, Error)
- func (p *Parser) Values() (*ValuesIter, Error)
- type RawArgs
- type ValuesIter
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ErrorCustom ¶
type ErrorCustom struct {
A error
}
func (*ErrorCustom) Error ¶
func (e *ErrorCustom) Error() string
func (*ErrorCustom) GoString ¶
func (e *ErrorCustom) GoString() string
func (*ErrorCustom) String ¶
func (e *ErrorCustom) String() string
func (*ErrorCustom) Unwrap ¶
func (e *ErrorCustom) Unwrap() error
type ErrorMissingValue ¶
type ErrorMissingValue struct {
Option *string
}
func (*ErrorMissingValue) Error ¶
func (e *ErrorMissingValue) Error() string
func (*ErrorMissingValue) GoString ¶
func (e *ErrorMissingValue) GoString() string
func (*ErrorMissingValue) String ¶
func (e *ErrorMissingValue) String() string
func (*ErrorMissingValue) Unwrap ¶
func (e *ErrorMissingValue) Unwrap() error
type ErrorNonUnicodeValue ¶
type ErrorNonUnicodeValue struct {
A string
}
func (*ErrorNonUnicodeValue) Error ¶
func (e *ErrorNonUnicodeValue) Error() string
func (*ErrorNonUnicodeValue) GoString ¶
func (e *ErrorNonUnicodeValue) GoString() string
func (*ErrorNonUnicodeValue) String ¶
func (e *ErrorNonUnicodeValue) String() string
func (*ErrorNonUnicodeValue) Unwrap ¶
func (e *ErrorNonUnicodeValue) Unwrap() error
type ErrorParsingFailed ¶
func (*ErrorParsingFailed) Error ¶
func (e *ErrorParsingFailed) Error() string
func (*ErrorParsingFailed) GoString ¶
func (e *ErrorParsingFailed) GoString() string
func (*ErrorParsingFailed) String ¶
func (e *ErrorParsingFailed) String() string
func (*ErrorParsingFailed) Unwrap ¶
func (e *ErrorParsingFailed) Unwrap() error
type ErrorUnexpectedArgument ¶
type ErrorUnexpectedArgument struct {
A string
}
func (*ErrorUnexpectedArgument) Error ¶
func (e *ErrorUnexpectedArgument) Error() string
func (*ErrorUnexpectedArgument) GoString ¶
func (e *ErrorUnexpectedArgument) GoString() string
func (*ErrorUnexpectedArgument) String ¶
func (e *ErrorUnexpectedArgument) String() string
func (*ErrorUnexpectedArgument) Unwrap ¶
func (e *ErrorUnexpectedArgument) Unwrap() error
type ErrorUnexpectedOption ¶
type ErrorUnexpectedOption struct {
A string
}
func (*ErrorUnexpectedOption) Error ¶
func (e *ErrorUnexpectedOption) Error() string
func (*ErrorUnexpectedOption) GoString ¶
func (e *ErrorUnexpectedOption) GoString() string
func (*ErrorUnexpectedOption) String ¶
func (e *ErrorUnexpectedOption) String() string
func (*ErrorUnexpectedOption) Unwrap ¶
func (e *ErrorUnexpectedOption) Unwrap() error
type ErrorUnexpectedValue ¶
func (*ErrorUnexpectedValue) Error ¶
func (e *ErrorUnexpectedValue) Error() string
func (*ErrorUnexpectedValue) GoString ¶
func (e *ErrorUnexpectedValue) GoString() string
func (*ErrorUnexpectedValue) String ¶
func (e *ErrorUnexpectedValue) String() string
func (*ErrorUnexpectedValue) Unwrap ¶
func (e *ErrorUnexpectedValue) Unwrap() error
type Parser ¶
type Parser struct {
// contains filtered or unexported fields
}
A parser for command line arguments.
func ParserFromArgs ¶
Create a parser from an iterator that does **not** include a binary name.
The iterator is consumed immediately.
.BinName() will return (T, false). Consider using ParserFromIter() instead.
func ParserFromEnv ¶
func ParserFromEnv() *Parser
Create a parser from the environment using os.Args.
This is the usual way to create a Parser.
func ParserFromIter ¶
Create a parser from an iterator. This is useful for testing among other things.
The first item from the iterator **must** be the binary name, as from os.Args.
The iterator is consumed immediately.
Example ¶
args := []string{"myapp", "-n", "10", "./foo.bar"}
parser := lexopt.ParserFromIter(slices.Values(args))
func (*Parser) BinName ¶
The name of the command, as in the zeroth argument of the process.
This is intended for use in messages. If the name is not valid unicode it will be sanitized with replacement characters as by strings.ToValidUTF8.
To get the current executable, use os.Executable.
Example ¶
parser := lexopt.ParserFromEnv()
binName, ok := parser.BinName()
if !ok {
binName = "myapp"
}
fmt.Printf("%v: Some message", binName)
func (*Parser) Next ¶
Get the next option or positional argument.
A return value of (nil, false, nil) means there are no more arguments.
Errors ¶
ErrorUnexpectedValue is returned if the last option had a value that hasn't been consumed, as in --option=value or -o=value.
It's possible to continue parsing after this error (but this is rarely useful).
func (*Parser) OptionalValue ¶
Get a value only if it's concatenated to an option, as in -ovalue or --option=value or -o=value, but not -o value or --option value.
func (*Parser) RawArgs ¶
Take raw arguments from the original command line.
This returns an iterator of strings. Any arguments that are not consumed are kept, so you can continue parsing after you're done with the iterator.
TODO: To inspect an argument without consuming it, use rawArgs.Peek() or rawArgs.AsSlice().
Errors ¶
Returns an ErrorUnexpectedValue if the last option had a left-over argument, as in --option=value, -ovalue, or if it was midway through an option chan, as in -abc. The iterator only yields whole arguments. To avoid this, use TryRawArgs().
After this error the method is guarenteed to succeed, as it consumes the rest of the argument.
# Example As soon as a free-standing argument is found, consume the other arguments as-is, and build them into a command.
parser := lexopt.ParserFromArgs([]string{"-x", "echo", "-n", "'Hello world'"})
for {
arg, ok, err := parser.Next()
if err != nil {
return err
}
if !ok {
break
}
if prog, ok := arg.(Value); ok {
rawArgsIter, err := parser.RawArgs()
if err != nil {
return err
}
args := slices.Collect(rawArgsIter)
command := exec.Command(prog, args...)
}
}
func (*Parser) TryRawArgs ¶
Take raw arguments from the original command line, *if* the current argument has finished processing.
Unlike .RawArgs() this does not consume any value in case of a left-over argument. This makes it safe to call at any time.
It returns (nil, false) exactly when .OptionalValue() would return (T, true).
Note: If no arguments are left then it returns an empty iterator (not (nil, false)).
# Example Process arguments of the form -123 as numbers. For a complete runnable version of this example, see example_nonstandard_test.go.
parser := lexopt.ParserFromArgs([]string{"-13"})
parseDashnum := func (parser *lexopt.Parser) (uint64, bool) {
raw, ok := parser.TryRawArgs()
if !ok {
return 0, false
}
arg, ok := raw.Peek()
if !ok {
return 0, false
}
num, err := strconv.ParseUint(strings.TrimLeft(arg, "-"), 10, 64)
if err != nil {
return 0, false
}
raw.Next()
return num, true
}
for {
if num, ok := parseDashnum(parser); ok {
fmt.Printf("Got number %v\n", num)
} else {
arg, ok, err := parser.Next()
if err != nil {
log.Fatal(err)
}
if ok {
// ...
} else {
break
}
}
}
func (*Parser) Value ¶
Get a value for an option.
This function should normally be called right after seeing an option that expects a value, with positional arguments being collected using parser.Next().
A value is collected even if it looks like an option (i.e., starts with -).
Errors ¶
An ErrorMissingValue is returned if the end of the command line is reached.
func (*Parser) Values ¶
func (p *Parser) Values() (*ValuesIter, Error)
Gather multiple values for an option.
This is used for options that take multiple arguments, such as a --command flag that's invoked as app --command echo 'Hello world'.
It will gather arguments until another option is found, or -- is found, or the end of the command line is reached. This differs from .Value(), which takes a value even if it looks like an option.
An equals sign (=) will limit this to a single value. That means -a=b c and --opt=b c will only yield "b" while -a b c and --opt b c will yield "b" and "c".
# Errors If not at least one value is found then ErrorMissingValue is returned.
Example ¶
parser := lexopt.ParserFromArgs([]string{"a", "b", "-x", "one", "two", "three", "four"})
argumentsIter, err := parser.Values()
if err != nil {
return err
}
arguments := slices.Collect(argumentsIter)
require.Equal(t, []string{"a", "b"}, arguments)
parser.Next()
valuesIter, err := parser.Values()
if err != nil {
return err
}
next, _ := iter.Pull(valuesIter)
atMostThreeFiles := []string{}
for i := 0; i < 3; i++ {
value, ok := next()
if !ok {
break
}
atMostThreeFiles = append(atMostThreeFiles, value)
}
rawArgsIter, err := parser.RawArgs()
if err != nil {
return err
}
rawArgs := slices.Collect(rawArgsIter)
require.Equal(t, rawArgs, []string{"four"})
valuesIter, err = parser.Values()
if err != nil {
return err
}
for v := range valuesIter {
// ...
}
type ValuesIter ¶
type ValuesIter struct {
// contains filtered or unexported fields
}
func (*ValuesIter) All ¶
func (v *ValuesIter) All(yield func(string) bool)
func (*ValuesIter) Next ¶
func (v *ValuesIter) Next() (string, bool)