diff --git a/README.md b/README.md index a4ce0c3..fa8624c 100644 --- a/README.md +++ b/README.md @@ -4,40 +4,42 @@ # Kong is a command-line parser for Go [![](https://godoc.org/github.com/alecthomas/kong?status.svg)](http://godoc.org/github.com/alecthomas/kong) [![CircleCI](https://img.shields.io/circleci/project/github/alecthomas/kong.svg)](https://circleci.com/gh/alecthomas/kong) [![Go Report Card](https://goreportcard.com/badge/github.com/alecthomas/kong)](https://goreportcard.com/report/github.com/alecthomas/kong) [![Slack chat](https://img.shields.io/static/v1?logo=slack&style=flat&label=slack&color=green&message=gophers)](https://gophers.slack.com/messages/CN9DS8YF3) - + -- [Introduction](#introduction) -- [Help](#help) - - [Help as a user of a Kong application](#help-as-a-user-of-a-kong-application) - - [Defining help in Kong](#defining-help-in-kong) -- [Command handling](#command-handling) - - [Switch on the command string](#switch-on-the-command-string) - - [Attach a Run... error method to each command](#attach-a-run-error-method-to-each-command) -- [Hooks: BeforeResolve, BeforeApply, AfterApply and the Bind option](#hooks-beforeresolve-beforeapply-afterapply-and-the-bind-option) -- [Flags](#flags) -- [Commands and sub-commands](#commands-and-sub-commands) -- [Branching positional arguments](#branching-positional-arguments) -- [Terminating positional arguments](#terminating-positional-arguments) -- [Slices](#slices) -- [Maps](#maps) -- [Custom named decoders](#custom-named-decoders) -- [Supported field types](#supported-field-types) -- [Custom decoders mappers](#custom-decoders-mappers) -- [Supported tags](#supported-tags) -- [Plugins](#plugins) -- [Dynamic Commands](#dynamic-commands) -- [Variable interpolation](#variable-interpolation) -- [Validation](#validation) -- [Modifying Kong's behaviour](#modifying-kongs-behaviour) - - [Namehelp and Descriptionhelp - set the application name description](#namehelp-and-descriptionhelp---set-the-application-name-description) - - [Configurationloader, paths... - load defaults from configuration files](#configurationloader-paths---load-defaults-from-configuration-files) - - [Resolver... - support for default values from external sources](#resolver---support-for-default-values-from-external-sources) - - [*Mapper... - customising how the command-line is mapped to Go values](#mapper---customising-how-the-command-line-is-mapped-to-go-values) - - [ConfigureHelpHelpOptions and HelpHelpFunc - customising help](#configurehelphelpoptions-and-helphelpfunc---customising-help) - - [Bind... - bind values for callback hooks and Run methods](#bind---bind-values-for-callback-hooks-and-run-methods) - - [Other options](#other-options) + - +1. [Introduction](#introduction) +1. [Help](#help) + 1. [Help as a user of a Kong application](#help-as-a-user-of-a-kong-application) + 1. [Defining help in Kong](#defining-help-in-kong) +1. [Command handling](#command-handling) + 1. [Switch on the command string](#switch-on-the-command-string) + 1. [Attach a `Run(...) error` method to each command](#attach-a-run-error-method-to-each-command) +1. [Hooks: BeforeResolve\(\), BeforeApply\(\), AfterApply\(\) and the Bind\(\) option](#hooks-beforeresolve-beforeapply-afterapply-and-the-bind-option) +1. [Flags](#flags) +1. [Commands and sub-commands](#commands-and-sub-commands) +1. [Branching positional arguments](#branching-positional-arguments) +1. [Positional arguments](#positional-arguments) +1. [Slices](#slices) +1. [Maps](#maps) +1. [Custom named decoders](#custom-named-decoders) +1. [Supported field types](#supported-field-types) +1. [Custom decoders \(mappers\)](#custom-decoders-mappers) +1. [Supported tags](#supported-tags) +1. [Plugins](#plugins) +1. [Dynamic Commands](#dynamic-commands) +1. [Variable interpolation](#variable-interpolation) +1. [Validation](#validation) +1. [Modifying Kong's behaviour](#modifying-kongs-behaviour) + 1. [`Name(help)` and `Description(help)` - set the application name description](#namehelp-and-descriptionhelp---set-the-application-name-description) + 1. [`Configuration(loader, paths...)` - load defaults from configuration files](#configurationloader-paths---load-defaults-from-configuration-files) + 1. [`Resolver(...)` - support for default values from external sources](#resolver---support-for-default-values-from-external-sources) + 1. [`*Mapper(...)` - customising how the command-line is mapped to Go values](#mapper---customising-how-the-command-line-is-mapped-to-go-values) + 1. [`ConfigureHelp(HelpOptions)` and `Help(HelpFunc)` - customising help](#configurehelphelpoptions-and-helphelpfunc---customising-help) + 1. [`Bind(...)` - bind values for callback hooks and Run\(\) methods](#bind---bind-values-for-callback-hooks-and-run-methods) + 1. [Other options](#other-options) + + ## Introduction @@ -333,11 +335,14 @@ var CLI struct { This looks a little verbose in this contrived example, but typically this will not be the case. -## Terminating positional arguments +## Positional arguments -If a [mapped type](#mapper---customising-how-the-command-line-is-mapped-to-go-values) is tagged with `arg` it will be treated as the final positional values to be parsed on the command line. +If a field is tagged with `arg:""` it will be treated as the final positional +value to be parsed on the command line. By default positional arguments are +required, but specifying `optional:""` will alter this. -If a positional argument is a slice, all remaining arguments will be appended to that slice. +If a positional argument is a slice, all remaining arguments will be appended +to that slice. ## Slices @@ -429,7 +434,7 @@ Both can coexist with standard Tag parsing. Tag | Description -----------------------| ------------------------------------------- `cmd:""` | If present, struct is a command. -`arg:""` | If present, field is an argument. +`arg:""` | If present, field is an argument. Required by default. `env:"X"` | Specify envar to use for default value. `name:"X"` | Long name, for overriding field name. `help:"X"` | Help text. diff --git a/go.mod b/go.mod index 9362c5c..bdd8bb2 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,7 @@ module github.com/alecthomas/kong require ( + github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142 github.com/davecgh/go-spew v1.1.1 // indirect github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.7.0 diff --git a/go.sum b/go.sum index d06ab36..12908f3 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142 h1:8Uy0oSf5co/NZXje7U1z8Mpep++QJOldL2hs/sBQf48= +github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/kong_test.go b/kong_test.go index f0844ba..a415946 100644 --- a/kong_test.go +++ b/kong_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/alecthomas/kong" + "github.com/alecthomas/repr" ) func mustNew(t *testing.T, cli interface{}, options ...kong.Option) *kong.Kong { @@ -1384,17 +1385,63 @@ func TestOptionReturnsErr(t *testing.T) { } func TestEnumValidation(t *testing.T) { - var cli struct { - Enum string `arg:"" enum:"one,two"` + tests := []struct { + name string + cli interface{} + fail bool + }{ + { + "Arg", + &struct { + Enum string `arg:"" enum:"one,two"` + }{}, + false, + }, + { + "RequiredArg", + &struct { + Enum string `required:"" arg:"" enum:"one,two"` + }{}, + false, + }, + { + "OptionalArg", + &struct { + Enum string `optional:"" arg:"" enum:"one,two"` + }{}, + true, + }, + { + "RepeatedArgs", + &struct { + Enum []string `arg:"" enum:"one,two"` + }{}, + false, + }, + { + "RequiredRepeatedArgs", + &struct { + Enum []string `required:"" arg:"" enum:"one,two"` + }{}, + false, + }, + { + "OptionalRepeatedArgs", + &struct { + Enum []string `optional:"" arg:"" enum:"one,two"` + }{}, + false, + }, } - _, err := kong.New(&cli) - require.Error(t, err) -} - -func TestEnumValidationSlice(t *testing.T) { - var cli struct { - Enum []string `arg:"" enum:"one,two"` + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + _, err := kong.New(test.cli) + if test.fail { + require.Error(t, err, repr.String(test.cli)) + } else { + require.NoError(t, err, repr.String(test.cli)) + } + }) } - _, err := kong.New(&cli) - require.NoError(t, err) } diff --git a/tag.go b/tag.go index a817c88..9f937d7 100644 --- a/tag.go +++ b/tag.go @@ -186,6 +186,8 @@ func hydrateTag(t *Tag, typ reflect.Type) error { // nolint: gocyclo // Arguments with defaults are always optional. if t.Arg && t.Default != "" { t.Optional = true + } else if t.Arg && !optional { // Arguments are required unless explicitly made optional. + t.Required = true } t.Name = t.Get("name") t.Help = t.Get("help")