Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merging issue 280 with master changes #296

Merged
merged 2 commits into from Sep 20, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
192 changes: 103 additions & 89 deletions README.md
Expand Up @@ -10,11 +10,11 @@

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. [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. [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)
Expand All @@ -31,13 +31,13 @@
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)
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)

<!-- /MarkdownTOC -->

Expand All @@ -60,26 +60,26 @@ package main
import "github.com/alecthomas/kong"

var CLI struct {
Rm struct {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you revert these whitespace changes please?

Copy link
Sponsor Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, looks like my IDE was being too helpful :/

Force bool `help:"Force removal."`
Recursive bool `help:"Recursively remove files."`
Rm struct {
Force bool `help:"Force removal."`
Recursive bool `help:"Recursively remove files."`

Paths []string `arg:"" name:"path" help:"Paths to remove." type:"path"`
} `cmd:"" help:"Remove files."`
Paths []string `arg:"" name:"path" help:"Paths to remove." type:"path"`
} `cmd:"" help:"Remove files."`

Ls struct {
Paths []string `arg:"" optional:"" name:"path" help:"Paths to list." type:"path"`
} `cmd:"" help:"List paths."`
Ls struct {
Paths []string `arg:"" optional:"" name:"path" help:"Paths to list." type:"path"`
} `cmd:"" help:"List paths."`
}

func main() {
ctx := kong.Parse(&CLI)
switch ctx.Command() {
case "rm <path>":
case "ls":
default:
panic(ctx.Command())
}
ctx := kong.Parse(&CLI)
switch ctx.Command() {
case "rm <path>":
case "ls":
default:
panic(ctx.Command())
}
}
```

Expand Down Expand Up @@ -154,26 +154,26 @@ package main
import "github.com/alecthomas/kong"

var CLI struct {
Rm struct {
Force bool `help:"Force removal."`
Recursive bool `help:"Recursively remove files."`
Rm struct {
Force bool `help:"Force removal."`
Recursive bool `help:"Recursively remove files."`

Paths []string `arg:"" name:"path" help:"Paths to remove." type:"path"`
} `cmd:"" help:"Remove files."`
Paths []string `arg:"" name:"path" help:"Paths to remove." type:"path"`
} `cmd:"" help:"Remove files."`

Ls struct {
Paths []string `arg:"" optional:"" name:"path" help:"Paths to list." type:"path"`
} `cmd:"" help:"List paths."`
Ls struct {
Paths []string `arg:"" optional:"" name:"path" help:"Paths to list." type:"path"`
} `cmd:"" help:"List paths."`
}

func main() {
ctx := kong.Parse(&CLI)
switch ctx.Command() {
case "rm <path>":
case "ls":
default:
panic(ctx.Command())
}
ctx := kong.Parse(&CLI)
switch ctx.Command() {
case "rm <path>":
case "ls":
default:
panic(ctx.Command())
}
}
```

Expand Down Expand Up @@ -204,42 +204,42 @@ eg.

```go
type Context struct {
Debug bool
Debug bool
}

type RmCmd struct {
Force bool `help:"Force removal."`
Recursive bool `help:"Recursively remove files."`
Force bool `help:"Force removal."`
Recursive bool `help:"Recursively remove files."`

Paths []string `arg:"" name:"path" help:"Paths to remove." type:"path"`
Paths []string `arg:"" name:"path" help:"Paths to remove." type:"path"`
}

func (r *RmCmd) Run(ctx *Context) error {
fmt.Println("rm", r.Paths)
return nil
fmt.Println("rm", r.Paths)
return nil
}

type LsCmd struct {
Paths []string `arg:"" optional:"" name:"path" help:"Paths to list." type:"path"`
Paths []string `arg:"" optional:"" name:"path" help:"Paths to list." type:"path"`
}

func (l *LsCmd) Run(ctx *Context) error {
fmt.Println("ls", l.Paths)
return nil
fmt.Println("ls", l.Paths)
return nil
}

var cli struct {
Debug bool `help:"Enable debug mode."`
Debug bool `help:"Enable debug mode."`

Rm RmCmd `cmd:"" help:"Remove files."`
Ls LsCmd `cmd:"" help:"List paths."`
Rm RmCmd `cmd:"" help:"Remove files."`
Ls LsCmd `cmd:"" help:"List paths."`
}

func main() {
ctx := kong.Parse(&cli)
// Call the Run() method of the selected parsed command.
err := ctx.Run(&Context{Debug: cli.Debug})
ctx.FatalIfErrorf(err)
ctx := kong.Parse(&cli)
// Call the Run() method of the selected parsed command.
err := ctx.Run(&Context{Debug: cli.Debug})
ctx.FatalIfErrorf(err)
}

```
Expand All @@ -259,21 +259,21 @@ eg.
type debugFlag bool

func (d debugFlag) BeforeApply(logger *log.Logger) error {
logger.SetOutput(os.Stdout)
return nil
logger.SetOutput(os.Stdout)
return nil
}

var cli struct {
Debug debugFlag `help:"Enable debug logging."`
Debug debugFlag `help:"Enable debug logging."`
}

func main() {
// Debug logger going to discard.
logger := log.New(ioutil.Discard, "", log.LstdFlags)
// Debug logger going to discard.
logger := log.New(ioutil.Discard, "", log.LstdFlags)

ctx := kong.Parse(&cli, kong.Bind(logger))
ctx := kong.Parse(&cli, kong.Bind(logger))

// ...
// ...
}
```

Expand All @@ -285,7 +285,7 @@ eg. The command-line `app [--flag="foo"]` can be represented by the following.

```go
type CLI struct {
Flag string
Flag string
}
```

Expand All @@ -297,12 +297,12 @@ eg. The following struct represents the CLI structure `command [--flag="str"] su

```go
type CLI struct {
Command struct {
Flag string
Command struct {
Flag string

SubCommand struct {
} `cmd`
} `cmd`
SubCommand struct {
} `cmd`
} `cmd`
}
```

Expand All @@ -320,16 +320,16 @@ Can be represented with the following:

```go
var CLI struct {
Rename struct {
Name struct {
Name string `arg` // <-- NOTE: identical name to enclosing struct field.
To struct {
Name struct {
Name string `arg`
} `arg`
} `cmd`
} `arg`
} `cmd`
Rename struct {
Name struct {
Name string `arg` // <-- NOTE: identical name to enclosing struct field.
To struct {
Name struct {
Name string `arg`
} `arg`
} `cmd`
} `arg`
} `cmd`
}
```

Expand All @@ -356,9 +356,9 @@ You would use the following:

```go
var CLI struct {
Ls struct {
Files []string `arg:"" type:"existingfile"`
} `cmd`
Ls struct {
Files []string `arg:"" type:"existingfile"`
} `cmd`
}
```

Expand All @@ -374,16 +374,30 @@ You would use the following:

```go
var CLI struct {
Config struct {
Set struct {
Config map[string]float64 `arg:"" type:"file:"`
} `cmd`
} `cmd`
Config struct {
Set struct {
Config map[string]float64 `arg:"" type:"file:"`
} `cmd`
} `cmd`
}
```

For flags, multiple key+value pairs should be separated by `mapsep:"rune"` tag (defaults to `;`) eg. `--set="key1=value1;key2=value2"`.

## Pointers

Pointers work like the underlying type, except that you can differentiate between the presence of the zero value and no value being supplied.

For example:

```go
var CLI struct {
Foo *int
}
```

Would produce a nil value for `Foo` if no `--foo` argument is supplied, but would have a pointer to the value 0 if the argument `--foo=0` was supplied.

## Nested data structure

Kong support a nested data structure as well with `embed:""`. You can combine `embed:""` with `prefix:""`:
Expand Down Expand Up @@ -628,4 +642,4 @@ See the [section on hooks](#hooks-beforeresolve-beforeapply-afterapply-and-the-b

### Other options

The full set of options can be found [here](https://godoc.org/github.com/alecthomas/kong#Option).
The full set of options can be found [here](https://godoc.org/github.com/alecthomas/kong#Option).
26 changes: 25 additions & 1 deletion context.go
Expand Up @@ -661,6 +661,22 @@ func (c *Context) Apply() (string, error) {
return strings.Join(path, " "), nil
}

func flipBoolValue(value reflect.Value) error {
if value.Kind() == reflect.Bool {
value.SetBool(!value.Bool())
return nil
}

if value.Kind() == reflect.Ptr {
if !value.IsNil() {
return flipBoolValue(value.Elem())
}
return nil
}

return fmt.Errorf("cannot negate a value of %s", value.Type().String())
}

func (c *Context) parseFlag(flags []*Flag, match string) (err error) {
candidates := []string{}
for _, flag := range flags {
Expand Down Expand Up @@ -689,7 +705,10 @@ func (c *Context) parseFlag(flags []*Flag, match string) (err error) {
}
if flag.Negated {
value := c.getValue(flag.Value)
value.SetBool(!value.Bool())
err := flipBoolValue(value)
if err != nil {
return err
}
flag.Value.Apply(value)
}
c.Path = append(c.Path, &Path{Flag: flag})
Expand Down Expand Up @@ -889,6 +908,11 @@ func checkEnum(value *Value, target reflect.Value) error {
case reflect.Map, reflect.Struct:
return errors.New("enum can only be applied to a slice or value")

case reflect.Ptr:
if target.IsNil() {
return nil
}
return checkEnum(value, target.Elem())
default:
enumSlice := value.EnumSlice()
v := fmt.Sprintf("%v", target)
Expand Down