pax_global_header00006660000000000000000000000064145102026250014507gustar00rootroot0000000000000052 comment=93d31e17f429f1df8d21fb62056ac9f840fe4494 kong-0.8.1/000077500000000000000000000000001451020262500124535ustar00rootroot00000000000000kong-0.8.1/.github/000077500000000000000000000000001451020262500140135ustar00rootroot00000000000000kong-0.8.1/.github/FUNDING.yml000066400000000000000000000000251451020262500156250ustar00rootroot00000000000000github: [alecthomas] kong-0.8.1/.github/workflows/000077500000000000000000000000001451020262500160505ustar00rootroot00000000000000kong-0.8.1/.github/workflows/ci.yml000066400000000000000000000010731451020262500171670ustar00rootroot00000000000000on: push: branches: - master pull_request: name: CI jobs: test: name: Test runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v2 - name: Init Hermit run: ./bin/hermit env -r >> $GITHUB_ENV - name: Test run: go test ./... lint: name: Lint runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v2 - name: Init Hermit run: ./bin/hermit env -r >> $GITHUB_ENV - name: golangci-lint run: golangci-lint run kong-0.8.1/.gitignore000066400000000000000000000000041451020262500144350ustar00rootroot00000000000000bin kong-0.8.1/.golangci.yml000066400000000000000000000025541451020262500150450ustar00rootroot00000000000000run: tests: true output: print-issued-lines: false linters: enable-all: true disable: - maligned - lll - gochecknoglobals - wsl - funlen - gocognit - gomnd - goprintffuncname - paralleltest - nlreturn - goerr113 - ifshort - testpackage - wrapcheck - exhaustivestruct - forbidigo - gci - godot - gofumpt - cyclop - errorlint - nestif - golint - scopelint - interfacer - tagliatelle - thelper - godox - goconst - varnamelen - ireturn - exhaustruct - nonamedreturns - nilnil linters-settings: govet: check-shadowing: true dupl: threshold: 100 gocyclo: min-complexity: 20 exhaustive: default-signifies-exhaustive: true issues: max-per-linter: 0 max-same: 0 exclude-use-default: false exclude: - '^(G104|G204):' # Very commonly not checked. - 'Error return value of .(.*\.Help|.*\.MarkFlagRequired|(os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked' - 'exported method (.*\.MarshalJSON|.*\.UnmarshalJSON) should have comment or be unexported' - 'composite literal uses unkeyed fields' - 'bad syntax for struct tag key' - 'bad syntax for struct tag pair' - 'result .* \(error\) is always nil' - 'package io/ioutil is deprecated' kong-0.8.1/COPYING000066400000000000000000000020371451020262500135100ustar00rootroot00000000000000Copyright (C) 2018 Alec Thomas Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. kong-0.8.1/README.md000066400000000000000000001027001451020262500137320ustar00rootroot00000000000000

# 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: BeforeReset, BeforeResolve, BeforeApply, AfterApply and the Bind option](#hooks-beforereset-beforeresolve-beforeapply-afterapply-and-the-bind-option) - [Flags](#flags) - [Commands and sub-commands](#commands-and-sub-commands) - [Branching positional arguments](#branching-positional-arguments) - [Positional arguments](#positional-arguments) - [Slices](#slices) - [Maps](#maps) - [Pointers](#pointers) - [Nested data structure](#nested-data-structure) - [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) ## Introduction Kong aims to support arbitrarily complex command-line structures with as little developer effort as possible. To achieve that, command-lines are expressed as Go types, with the structure and tags directing how the command line is mapped onto the struct. For example, the following command-line: shell rm [-f] [-r] ... shell ls [ ...] Can be represented by the following command-line structure: ```go package main import "github.com/alecthomas/kong" var CLI struct { 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."` 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 ": case "ls": default: panic(ctx.Command()) } } ``` ## Help ### Help as a user of a Kong application Every Kong application includes a `--help` flag that will display auto-generated help. eg. $ shell --help usage: shell A shell-like example app. Flags: --help Show context-sensitive help. --debug Debug mode. Commands: rm ... Remove files. ls [ ...] List paths. If a command is provided, the help will show full detail on the command including all available flags. eg. $ shell --help rm usage: shell rm ... Remove files. Arguments: ... Paths to remove. Flags: --debug Debug mode. -f, --force Force removal. -r, --recursive Recursively remove files. ### Defining help in Kong Help is automatically generated from the command-line structure itself, including `help:""` and other tags. [Variables](#variable-interpolation) will also be interpolated into the help string. Finally, any command, or argument type implementing the interface `Help() string` will have this function called to retrieve more detail to augment the help tag. This allows for much more descriptive text than can fit in Go tags. [See \_examples/shell/help](./_examples/shell/help) #### Showing the _command_'s detailed help A command's additional help text is _not_ shown from top-level help, but can be displayed within contextual help: **Top level help** ```bash $ go run ./_examples/shell/help --help Usage: help An app demonstrating HelpProviders Flags: -h, --help Show context-sensitive help. --flag Regular flag help Commands: echo Regular command help ``` **Contextual** ```bash $ go run ./_examples/shell/help echo --help Usage: help echo Regular command help 🚀 additional command help Arguments: Regular argument help Flags: -h, --help Show context-sensitive help. --flag Regular flag help ``` #### Showing an _argument_'s detailed help Custom help will only be shown for _positional arguments with named fields_ ([see the README section on positional arguments for more details on what that means](../../../README.md#branching-positional-arguments)) **Contextual argument help** ```bash $ go run ./_examples/shell/help msg --help Usage: help echo Regular argument help 📣 additional argument help Flags: -h, --help Show context-sensitive help. --flag Regular flag help ``` ## Command handling There are two ways to handle commands in Kong. ### Switch on the command string When you call `kong.Parse()` it will return a unique string representation of the command. Each command branch in the hierarchy will be a bare word and each branching argument or required positional argument will be the name surrounded by angle brackets. Here's an example: There's an example of this pattern [here](https://github.com/alecthomas/kong/blob/master/_examples/shell/commandstring/main.go). eg. ```go package main import "github.com/alecthomas/kong" var CLI struct { 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."` 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 ": case "ls": default: panic(ctx.Command()) } } ``` This has the advantage that it is convenient, but the downside that if you modify your CLI structure, the strings may change. This can be fragile. ### Attach a `Run(...) error` method to each command A more robust approach is to break each command out into their own structs: 1. Break leaf commands out into separate structs. 2. Attach a `Run(...) error` method to all leaf commands. 3. Call `kong.Kong.Parse()` to obtain a `kong.Context`. 4. Call `kong.Context.Run(bindings...)` to call the selected parsed command. Once a command node is selected by Kong it will search from that node back to the root. Each encountered command node with a `Run(...) error` will be called in reverse order. This allows sub-trees to be re-used fairly conveniently. In addition to values bound with the `kong.Bind(...)` option, any values passed through to `kong.Context.Run(...)` are also bindable to the target's `Run()` arguments. Finally, hooks can also contribute bindings via `kong.Context.Bind()` and `kong.Context.BindTo()`. There's a full example emulating part of the Docker CLI [here](https://github.com/alecthomas/kong/tree/master/_examples/docker). eg. ```go type Context struct { Debug bool } type RmCmd struct { Force bool `help:"Force removal."` Recursive bool `help:"Recursively remove files."` 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 } type LsCmd struct { 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 } var cli struct { Debug bool `help:"Enable debug mode."` 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) } ``` ## Hooks: BeforeReset(), BeforeResolve(), BeforeApply(), AfterApply() and the Bind() option If a node in the grammar has a `BeforeReset(...)`, `BeforeResolve (...)`, `BeforeApply(...) error` and/or `AfterApply(...) error` method, those methods will be called before values are reset, before validation/assignment, and after validation/assignment, respectively. The `--help` flag is implemented with a `BeforeReset` hook. Arguments to hooks are provided via the `Run(...)` method or `Bind(...)` option. `*Kong`, `*Context` and `*Path` are also bound and finally, hooks can also contribute bindings via `kong.Context.Bind()` and `kong.Context.BindTo()`. eg. ```go // A flag with a hook that, if triggered, will set the debug loggers output to stdout. type debugFlag bool func (d debugFlag) BeforeApply(logger *log.Logger) error { logger.SetOutput(os.Stdout) return nil } var cli struct { Debug debugFlag `help:"Enable debug logging."` } func main() { // Debug logger going to discard. logger := log.New(ioutil.Discard, "", log.LstdFlags) ctx := kong.Parse(&cli, kong.Bind(logger)) // ... } ``` Another example of using hooks is load the env-file: ```go package main import ( "fmt" "github.com/alecthomas/kong" "github.com/joho/godotenv" ) type EnvFlag string // BeforeResolve loads env file. func (c EnvFlag) BeforeReset(ctx *kong.Context, trace *kong.Path) error { path := string(ctx.FlagValue(trace.Flag).(EnvFlag)) // nolint path = kong.ExpandPath(path) if err := godotenv.Load(path); err != nil { return err } return nil } var CLI struct { EnvFile EnvFlag Flag `env:"FLAG"` } func main() { _ = kong.Parse(&CLI) fmt.Println(CLI.Flag) } ``` ## Flags Any [mapped](#mapper---customising-how-the-command-line-is-mapped-to-go-values) field in the command structure _not_ tagged with `cmd` or `arg` will be a flag. Flags are optional by default. eg. The command-line `app [--flag="foo"]` can be represented by the following. ```go type CLI struct { Flag string } ``` ## Commands and sub-commands Sub-commands are specified by tagging a struct field with `cmd`. Kong supports arbitrarily nested commands. eg. The following struct represents the CLI structure `command [--flag="str"] sub-command`. ```go type CLI struct { Command struct { Flag string SubCommand struct { } `cmd` } `cmd` } ``` If a sub-command is tagged with `default:"1"` it will be selected if there are no further arguments. If a sub-command is tagged with `default:"withargs"` it will be selected even if there are further arguments or flags and those arguments or flags are valid for the sub-command. This allows the user to omit the sub-command name on the CLI if its arguments/flags are not ambiguous with the sibling commands or flags. ## Branching positional arguments In addition to sub-commands, structs can also be configured as branching positional arguments. This is achieved by tagging an [unmapped](#mapper---customising-how-the-command-line-is-mapped-to-go-values) nested struct field with `arg`, then including a positional argument field inside that struct _with the same name_. For example, the following command structure: app rename to 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` } ``` This looks a little verbose in this contrived example, but typically this will not be the case. ## Positional arguments 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. ## Slices Slice values are treated specially. First the input is split on the `sep:""` tag (defaults to `,`), then each element is parsed by the slice element type and appended to the slice. If the same value is encountered multiple times, elements continue to be appended. To represent the following command-line: cmd ls ... You would use the following: ```go var CLI struct { Ls struct { Files []string `arg:"" type:"existingfile"` } `cmd` } ``` ## Maps Maps are similar to slices except that only one key/value pair can be assigned per value, and the `sep` tag denotes the assignment character and defaults to `=`. To represent the following command-line: cmd config set = = ... You would use the following: ```go var CLI struct { 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:""`: ```go var CLI struct { Logging struct { Level string `enum:"debug,info,warn,error" default:"info"` Type string `enum:"json,console" default:"console"` } `embed:"" prefix:"logging."` } ``` This configures Kong to accept flags `--logging.level` and `--logging.type`. ## Custom named decoders Kong includes a number of builtin custom type mappers. These can be used by specifying the tag `type:""`. They are registered with the option function `NamedMapper(name, mapper)`. | Name | Description | | -------------- | ---------------------------------------------------------------------------------------------------------------------- | | `path` | A path. ~ expansion is applied. `-` is accepted for stdout, and will be passed unaltered. | | `existingfile` | An existing file. ~ expansion is applied. `-` is accepted for stdin, and will be passed unaltered. | | `existingdir` | An existing directory. ~ expansion is applied. | | `counter` | Increment a numeric field. Useful for `-vvv`. Can accept `-s`, `--long` or `--long=N`. | | `filecontent` | Read the file at path into the field. ~ expansion is applied. `-` is accepted for stdin, and will be passed unaltered. | Slices and maps treat type tags specially. For slices, the `type:""` tag specifies the element type. For maps, the tag has the format `tag:"[]:[]"` where either may be omitted. ## Supported field types ## Custom decoders (mappers) Any field implementing `encoding.TextUnmarshaler` or `json.Unmarshaler` will use those interfaces for decoding values. Kong also includes builtin support for many common Go types: | Type | Description | | --------------- | ----------------------------------------------------------------------------------------------------------- | | `time.Duration` | Populated using `time.ParseDuration()`. | | `time.Time` | Populated using `time.Parse()`. Format defaults to RFC3339 but can be overridden with the `format:"X"` tag. | | `*os.File` | Path to a file that will be opened, or `-` for `os.Stdin`. File must be closed by the user. | | `*url.URL` | Populated with `url.Parse()`. | For more fine-grained control, if a field implements the [MapperValue](https://godoc.org/github.com/alecthomas/kong#MapperValue) interface it will be used to decode arguments into the field. ## Supported tags Tags can be in two forms: 1. Standard Go syntax, eg. `kong:"required,name='foo'"`. 2. Bare tags, eg. `required:"" name:"foo"` Both can coexist with standard Tag parsing. | Tag | Description | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `cmd:""` | If present, struct is a command. | | `arg:""` | If present, field is an argument. Required by default. | | `env:"X,Y,..."` | Specify envars to use for default value. The envs are resolved in the declared order. The first value found is used. | | `name:"X"` | Long name, for overriding field name. | | `help:"X"` | Help text. | | `type:"X"` | Specify [named types](#custom-named-decoders) to use. | | `placeholder:"X"` | Placeholder text. | | `default:"X"` | Default value. | | `default:"1"` | On a command, make it the default. | | `default:"withargs"` | On a command, make it the default and allow args/flags from that command | | `short:"X"` | Short name, if flag. | | `aliases:"X,Y"` | One or more aliases (for cmd). | | `required:""` | If present, flag/arg is required. | | `optional:""` | If present, flag/arg is optional. | | `hidden:""` | If present, command or flag is hidden. | | `negatable:""` | If present on a `bool` field, supports prefixing a flag with `--no-` to invert the default value | | `format:"X"` | Format for parsing input, if supported. | | `sep:"X"` | Separator for sequences (defaults to ","). May be `none` to disable splitting. | | `mapsep:"X"` | Separator for maps (defaults to ";"). May be `none` to disable splitting. | | `enum:"X,Y,..."` | Set of valid values allowed for this flag. An enum field must be `required` or have a valid `default`. | | `group:"X"` | Logical group for a flag or command. | | `xor:"X,Y,..."` | Exclusive OR groups for flags. Only one flag in the group can be used which is restricted within the same command. When combined with `required`, at least one of the `xor` group will be required. | | `prefix:"X"` | Prefix for all sub-flags. | | `envprefix:"X"` | Envar prefix for all sub-flags. | | `set:"K=V"` | Set a variable for expansion by child elements. Multiples can occur. | | `embed:""` | If present, this field's children will be embedded in the parent. Useful for composition. | | `passthrough:""` | If present on a positional argument, it stops flag parsing when encountered, as if `--` was processed before. Useful for external command wrappers, like `exec`. On a command it requires that the command contains only one argument of type `[]string` which is then filled with everything following the command, unparsed. | | `-` | Ignore the field. Useful for adding non-CLI fields to a configuration struct. e.g `` `kong:"-"` `` | ## Plugins Kong CLI's can be extended by embedding the `kong.Plugin` type and populating it with pointers to Kong annotated structs. For example: ```go var pluginOne struct { PluginOneFlag string } var pluginTwo struct { PluginTwoFlag string } var cli struct { BaseFlag string kong.Plugins } cli.Plugins = kong.Plugins{&pluginOne, &pluginTwo} ``` Additionally if an interface type is embedded, it can also be populated with a Kong annotated struct. ## Dynamic Commands While plugins give complete control over extending command-line interfaces, Kong also supports dynamically adding commands via `kong.DynamicCommand()`. ## Variable interpolation Kong supports limited variable interpolation into help strings, enum lists and default values. Variables are in the form: ${} ${=} Variables are set with the `Vars{"key": "value", ...}` option. Undefined variable references in the grammar without a default will result in an error at construction time. Variables can also be set via the `set:"K=V"` tag. In this case, those variables will be available for that node and all children. This is useful for composition by allowing the same struct to be reused. When interpolating into flag or argument help strings, some extra variables are defined from the value itself: ${default} ${enum} For flags with associated environment variables, the variable `${env}` can be interpolated into the help string. In the absence of this variable in the help string, Kong will append `($$${env})` to the help string. eg. ```go type cli struct { Config string `type:"path" default:"${config_file}"` } func main() { kong.Parse(&cli, kong.Vars{ "config_file": "~/.app.conf", }) } ``` ## Validation Kong does validation on the structure of a command-line, but also supports extensible validation. Any node in the tree may implement the following interface: ```go type Validatable interface { Validate() error } ``` If one of these nodes is in the active command-line it will be called during normal validation. ## Modifying Kong's behaviour Each Kong parser can be configured via functional options passed to `New(cli interface{}, options...Option)`. The full set of options can be found [here](https://godoc.org/github.com/alecthomas/kong#Option). ### `Name(help)` and `Description(help)` - set the application name description Set the application name and/or description. The name of the application will default to the binary name, but can be overridden with `Name(name)`. As with all help in Kong, text will be wrapped to the terminal. ### `Configuration(loader, paths...)` - load defaults from configuration files This option provides Kong with support for loading defaults from a set of configuration files. Each file is opened, if possible, and the loader called to create a resolver for that file. eg. ```go kong.Parse(&cli, kong.Configuration(kong.JSON, "/etc/myapp.json", "~/.myapp.json")) ``` [See the tests](https://github.com/alecthomas/kong/blob/master/resolver_test.go#L206) for an example of how the JSON file is structured. #### List of Configuration Loaders - [YAML](https://github.com/alecthomas/kong-yaml) - [HCL](https://github.com/alecthomas/kong-hcl) - [TOML](https://github.com/alecthomas/kong-toml) - [JSON](https://github.com/alecthomas/kong) ### `Resolver(...)` - support for default values from external sources Resolvers are Kong's extension point for providing default values from external sources. As an example, support for environment variables via the `env` tag is provided by a resolver. There's also a builtin resolver for JSON configuration files. Example resolvers can be found in [resolver.go](https://github.com/alecthomas/kong/blob/master/resolver.go). ### `*Mapper(...)` - customising how the command-line is mapped to Go values Command-line arguments are mapped to Go values via the Mapper interface: ```go // A Mapper represents how a field is mapped from command-line values to Go. // // Mappers can be associated with concrete fields via pointer, reflect.Type, reflect.Kind, or via a "type" tag. // // Additionally, if a type implements the MapperValue interface, it will be used. type Mapper interface { // Decode ctx.Value with ctx.Scanner into target. Decode(ctx *DecodeContext, target reflect.Value) error } ``` All builtin Go types (as well as a bunch of useful stdlib types like `time.Time`) have mappers registered by default. Mappers for custom types can be added using `kong.??Mapper(...)` options. Mappers are applied to fields in four ways: 1. `NamedMapper(string, Mapper)` and using the tag key `type:""`. 2. `KindMapper(reflect.Kind, Mapper)`. 3. `TypeMapper(reflect.Type, Mapper)`. 4. `ValueMapper(interface{}, Mapper)`, passing in a pointer to a field of the grammar. ### `ConfigureHelp(HelpOptions)` and `Help(HelpFunc)` - customising help The default help output is usually sufficient, but if not there are two solutions. 1. Use `ConfigureHelp(HelpOptions)` to configure how help is formatted (see [HelpOptions](https://godoc.org/github.com/alecthomas/kong#HelpOptions) for details). 2. Custom help can be wired into Kong via the `Help(HelpFunc)` option. The `HelpFunc` is passed a `Context`, which contains the parsed context for the current command-line. See the implementation of `PrintHelp` for an example. 3. Use `ValueFormatter(HelpValueFormatter)` if you want to just customize the help text that is accompanied by flags and arguments. 4. Use `Groups([]Group)` if you want to customize group titles or add a header. ### `Bind(...)` - bind values for callback hooks and Run() methods See the [section on hooks](#hooks-beforeresolve-beforeapply-afterapply-and-the-bind-option) for details. ### Other options The full set of options can be found [here](https://godoc.org/github.com/alecthomas/kong#Option). kong-0.8.1/_examples/000077500000000000000000000000001451020262500144305ustar00rootroot00000000000000kong-0.8.1/_examples/docker/000077500000000000000000000000001451020262500156775ustar00rootroot00000000000000kong-0.8.1/_examples/docker/README.md000066400000000000000000000001461451020262500171570ustar00rootroot00000000000000# Large-scale composed CLI This directory illustrates how a large-scale CLI app could be structured. kong-0.8.1/_examples/docker/commands.go000066400000000000000000000122471451020262500200350ustar00rootroot00000000000000// nolint package main import "fmt" type AttachCmd struct { DetachKeys string `help:"Override the key sequence for detaching a container"` NoStdin bool `help:"Do not attach STDIN"` SigProxy bool `help:"Proxy all received signals to the process" default:"true"` Container string `arg required help:"Container ID to attach to."` } func (a *AttachCmd) Run(globals *Globals) error { fmt.Printf("Config: %s\n", globals.Config) fmt.Printf("Attaching to: %v\n", a.Container) fmt.Printf("SigProxy: %v\n", a.SigProxy) return nil } type BuildCmd struct { Arg string `arg required` } func (cmd *BuildCmd) Run(globals *Globals) error { return nil } type CommitCmd struct { Arg string `arg required` } func (cmd *CommitCmd) Run(globals *Globals) error { return nil } type CpCmd struct { Arg string `arg required` } func (cmd *CpCmd) Run(globals *Globals) error { return nil } type CreateCmd struct { Arg string `arg required` } func (cmd *CreateCmd) Run(globals *Globals) error { return nil } type DeployCmd struct { Arg string `arg required` } func (cmd *DeployCmd) Run(globals *Globals) error { return nil } type DiffCmd struct { Arg string `arg required` } func (cmd *DiffCmd) Run(globals *Globals) error { return nil } type EventsCmd struct { Arg string `arg required` } func (cmd *EventsCmd) Run(globals *Globals) error { return nil } type ExecCmd struct { Arg string `arg required` } func (cmd *ExecCmd) Run(globals *Globals) error { return nil } type ExportCmd struct { Arg string `arg required` } func (cmd *ExportCmd) Run(globals *Globals) error { return nil } type HistoryCmd struct { Arg string `arg required` } func (cmd *HistoryCmd) Run(globals *Globals) error { return nil } type ImagesCmd struct { Arg string `arg required` } func (cmd *ImagesCmd) Run(globals *Globals) error { return nil } type ImportCmd struct { Arg string `arg required` } func (cmd *ImportCmd) Run(globals *Globals) error { return nil } type InfoCmd struct { Arg string `arg required` } func (cmd *InfoCmd) Run(globals *Globals) error { return nil } type InspectCmd struct { Arg string `arg required` } func (cmd *InspectCmd) Run(globals *Globals) error { return nil } type KillCmd struct { Arg string `arg required` } func (cmd *KillCmd) Run(globals *Globals) error { return nil } type LoadCmd struct { Arg string `arg required` } func (cmd *LoadCmd) Run(globals *Globals) error { return nil } type LoginCmd struct { Arg string `arg required` } func (cmd *LoginCmd) Run(globals *Globals) error { return nil } type LogoutCmd struct { Arg string `arg required` } func (cmd *LogoutCmd) Run(globals *Globals) error { return nil } type LogsCmd struct { Arg string `arg required` } func (cmd *LogsCmd) Run(globals *Globals) error { return nil } type PauseCmd struct { Arg string `arg required` } func (cmd *PauseCmd) Run(globals *Globals) error { return nil } type PortCmd struct { Arg string `arg required` } func (cmd *PortCmd) Run(globals *Globals) error { return nil } type PsCmd struct { Arg string `arg required` } func (cmd *PsCmd) Run(globals *Globals) error { return nil } type PullCmd struct { Arg string `arg required` } func (cmd *PullCmd) Run(globals *Globals) error { return nil } type PushCmd struct { Arg string `arg required` } func (cmd *PushCmd) Run(globals *Globals) error { return nil } type RenameCmd struct { Arg string `arg required` } func (cmd *RenameCmd) Run(globals *Globals) error { return nil } type RestartCmd struct { Arg string `arg required` } func (cmd *RestartCmd) Run(globals *Globals) error { return nil } type RmCmd struct { Arg string `arg required` } func (cmd *RmCmd) Run(globals *Globals) error { return nil } type RmiCmd struct { Arg string `arg required` } func (cmd *RmiCmd) Run(globals *Globals) error { return nil } type RunCmd struct { Arg string `arg required` } func (cmd *RunCmd) Run(globals *Globals) error { return nil } type SaveCmd struct { Arg string `arg required` } func (cmd *SaveCmd) Run(globals *Globals) error { return nil } type SearchCmd struct { Arg string `arg required` } func (cmd *SearchCmd) Run(globals *Globals) error { return nil } type StartCmd struct { Arg string `arg required` } func (cmd *StartCmd) Run(globals *Globals) error { return nil } type StatsCmd struct { Arg string `arg required` } func (cmd *StatsCmd) Run(globals *Globals) error { return nil } type StopCmd struct { Arg string `arg required` } func (cmd *StopCmd) Run(globals *Globals) error { return nil } type TagCmd struct { Arg string `arg required` } func (cmd *TagCmd) Run(globals *Globals) error { return nil } type TopCmd struct { Arg string `arg required` } func (cmd *TopCmd) Run(globals *Globals) error { return nil } type UnpauseCmd struct { Arg string `arg required` } func (cmd *UnpauseCmd) Run(globals *Globals) error { return nil } type UpdateCmd struct { Arg string `arg required` } func (cmd *UpdateCmd) Run(globals *Globals) error { return nil } type VersionCmd struct { Arg string `arg required` } func (cmd *VersionCmd) Run(globals *Globals) error { return nil } type WaitCmd struct { Arg string `arg required` } func (cmd *WaitCmd) Run(globals *Globals) error { return nil } kong-0.8.1/_examples/docker/main.go000066400000000000000000000114401451020262500171520ustar00rootroot00000000000000// nolint package main import ( "fmt" "github.com/alecthomas/kong" ) type Globals struct { Config string `help:"Location of client config files" default:"~/.docker" type:"path"` Debug bool `short:"D" help:"Enable debug mode"` Host []string `short:"H" help:"Daemon socket(s) to connect to"` LogLevel string `short:"l" help:"Set the logging level (debug|info|warn|error|fatal)" default:"info"` TLS bool `help:"Use TLS; implied by --tls-verify"` TLSCACert string `name:"tls-ca-cert" help:"Trust certs signed only by this CA" default:"~/.docker/ca.pem" type:"path"` TLSCert string `help:"Path to TLS certificate file" default:"~/.docker/cert.pem" type:"path"` TLSKey string `help:"Path to TLS key file" default:"~/.docker/key.pem" type:"path"` TLSVerify bool `help:"Use TLS and verify the remote"` Version VersionFlag `name:"version" help:"Print version information and quit"` } type VersionFlag string func (v VersionFlag) Decode(ctx *kong.DecodeContext) error { return nil } func (v VersionFlag) IsBool() bool { return true } func (v VersionFlag) BeforeApply(app *kong.Kong, vars kong.Vars) error { fmt.Println(vars["version"]) app.Exit(0) return nil } type CLI struct { Globals Attach AttachCmd `cmd:"" help:"Attach local standard input, output, and error streams to a running container"` Build BuildCmd `cmd:"" help:"Build an image from a Dockerfile"` Commit CommitCmd `cmd:"" help:"Create a new image from a container's changes"` Cp CpCmd `cmd:"" help:"Copy files/folders between a container and the local filesystem"` Create CreateCmd `cmd:"" help:"Create a new container"` Deploy DeployCmd `cmd:"" help:"Deploy a new stack or update an existing stack"` Diff DiffCmd `cmd:"" help:"Inspect changes to files or directories on a container's filesystem"` Events EventsCmd `cmd:"" help:"Get real time events from the server"` Exec ExecCmd `cmd:"" help:"Run a command in a running container"` Export ExportCmd `cmd:"" help:"Export a container's filesystem as a tar archive"` History HistoryCmd `cmd:"" help:"Show the history of an image"` Images ImagesCmd `cmd:"" help:"List images"` Import ImportCmd `cmd:"" help:"Import the contents from a tarball to create a filesystem image"` Info InfoCmd `cmd:"" help:"Display system-wide information"` Inspect InspectCmd `cmd:"" help:"Return low-level information on Docker objects"` Kill KillCmd `cmd:"" help:"Kill one or more running containers"` Load LoadCmd `cmd:"" help:"Load an image from a tar archive or STDIN"` Login LoginCmd `cmd:"" help:"Log in to a Docker registry"` Logout LogoutCmd `cmd:"" help:"Log out from a Docker registry"` Logs LogsCmd `cmd:"" help:"Fetch the logs of a container"` Pause PauseCmd `cmd:"" help:"Pause all processes within one or more containers"` Port PortCmd `cmd:"" help:"List port mappings or a specific mapping for the container"` Ps PsCmd `cmd:"" help:"List containers"` Pull PullCmd `cmd:"" help:"Pull an image or a repository from a registry"` Push PushCmd `cmd:"" help:"Push an image or a repository to a registry"` Rename RenameCmd `cmd:"" help:"Rename a container"` Restart RestartCmd `cmd:"" help:"Restart one or more containers"` Rm RmCmd `cmd:"" help:"Remove one or more containers"` Rmi RmiCmd `cmd:"" help:"Remove one or more images"` Run RunCmd `cmd:"" help:"Run a command in a new container"` Save SaveCmd `cmd:"" help:"Save one or more images to a tar archive (streamed to STDOUT by default)"` Search SearchCmd `cmd:"" help:"Search the Docker Hub for images"` Start StartCmd `cmd:"" help:"Start one or more stopped containers"` Stats StatsCmd `cmd:"" help:"Display a live stream of container(s) resource usage statistics"` Stop StopCmd `cmd:"" help:"Stop one or more running containers"` Tag TagCmd `cmd:"" help:"Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE"` Top TopCmd `cmd:"" help:"Display the running processes of a container"` Unpause UnpauseCmd `cmd:"" help:"Unpause all processes within one or more containers"` Update UpdateCmd `cmd:"" help:"Update configuration of one or more containers"` Version VersionCmd `cmd:"" help:"Show the Docker version information"` Wait WaitCmd `cmd:"" help:"Block until one or more containers stop, then print their exit codes"` } func main() { cli := CLI{ Globals: Globals{ Version: VersionFlag("0.1.1"), }, } ctx := kong.Parse(&cli, kong.Name("docker"), kong.Description("A self-sufficient runtime for containers"), kong.UsageOnError(), kong.ConfigureHelp(kong.HelpOptions{ Compact: true, }), kong.Vars{ "version": "0.0.1", }) err := ctx.Run(&cli.Globals) ctx.FatalIfErrorf(err) } kong-0.8.1/_examples/server/000077500000000000000000000000001451020262500157365ustar00rootroot00000000000000kong-0.8.1/_examples/server/README.md000066400000000000000000000011101451020262500172060ustar00rootroot00000000000000# An interactive SSH server In addition to command-lines, Kong can be used interactively. This example serves a Kong command-line over SSH. Run with `go run .` then ssh to it like so: ``` $ ssh -p 6740 127.0.0.1 Welcome! > ? Example using Kong for interactive command parsing. Commands: help [ ...] Show help. status Show server status. > status OK > help status Show server status. Flags: -v, --verbose Show verbose status information. > status OK > status -v OK > status foo error: unexpected argument foo Show server status. Flags: > ^D ``` kong-0.8.1/_examples/server/console.go000066400000000000000000000016411451020262500177310ustar00rootroot00000000000000// nolint: govet package main import ( "fmt" "github.com/alecthomas/kong" ) // Ensure the grammar compiles. var _ = kong.Must(&grammar{}) // Server interface. type grammar struct { Help helpCmd `cmd:"" help:"Show help."` Question helpCmd `cmd:"" hidden:"" name:"?" help:"Show help."` Status statusCmd `cmd:"" help:"Show server status."` } type statusCmd struct { Verbose bool `short:"v" help:"Show verbose status information."` } func (s *statusCmd) Run(ctx *kong.Context) error { ctx.Printf("OK") return nil } type helpCmd struct { Command []string `arg:"" optional:"" help:"Show help on command."` } // Run shows help. func (h *helpCmd) Run(realCtx *kong.Context) error { ctx, err := kong.Trace(realCtx.Kong, h.Command) if err != nil { return err } if ctx.Error != nil { return ctx.Error } err = ctx.PrintUsage(false) if err != nil { return err } fmt.Fprintln(realCtx.Stdout) return nil } kong-0.8.1/_examples/server/go.mod000066400000000000000000000010651451020262500170460ustar00rootroot00000000000000module kong_server go 1.13 require ( github.com/alecthomas/colour v0.1.0 github.com/alecthomas/kong v0.2.1 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e github.com/chzyer/test v1.0.0 // indirect github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect github.com/gliderlabs/ssh v0.2.2 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/kr/pty v1.1.8 github.com/mattn/go-isatty v0.0.12 // indirect golang.org/x/crypto v0.1.0 ) kong-0.8.1/_examples/server/go.sum000066400000000000000000000137211451020262500170750ustar00rootroot00000000000000github.com/alecthomas/colour v0.1.0 h1:nOE9rJm6dsZ66RGWYSFrXw461ZIt9A6+nHgL7FRrDUk= github.com/alecthomas/colour v0.1.0/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= github.com/alecthomas/kong v0.2.1 h1:V1tLBhyQBC4rsbXbcOvm3GBaytJSwRNX69fp1WJxbqQ= github.com/alecthomas/kong v0.2.1/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/creack/pty v1.1.7 h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 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= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= kong-0.8.1/_examples/server/main.go000066400000000000000000000064561451020262500172240ustar00rootroot00000000000000package main import ( "errors" "fmt" "io" "log" "os" "strings" "github.com/chzyer/readline" "github.com/gliderlabs/ssh" "github.com/google/shlex" "github.com/kr/pty" "golang.org/x/crypto/ssh/terminal" "github.com/alecthomas/colour" "github.com/alecthomas/kong" ) // Handle a single SSH interactive connection. func handle(log *log.Logger, s ssh.Session) error { log.Printf("New SSH") sshPty, _, isPty := s.Pty() if !isPty { return errors.New("no PTY requested") } log.Printf("Using TERM=%s width=%d height=%d", sshPty.Term, sshPty.Window.Width, sshPty.Window.Height) cpty, tty, err := pty.Open() if err != nil { return err } defer tty.Close() state, err := terminal.GetState(int(cpty.Fd())) if err != nil { return err } defer terminal.Restore(int(cpty.Fd()), state) colour.Fprintln(tty, "^BWelcome!^R") go io.Copy(cpty, s) go io.Copy(s, cpty) parser, err := buildShellParser(tty) if err != nil { return err } rl, err := readline.NewEx(&readline.Config{ Prompt: "> ", Stderr: tty, Stdout: tty, Stdin: tty, FuncOnWidthChanged: func(f func()) {}, FuncMakeRaw: func() error { _, err := terminal.MakeRaw(int(cpty.Fd())) // nolint: govet return err }, FuncExitRaw: func() error { return nil }, }) if err != nil { return err } log.Printf("Loop") for { tty.Sync() var line string line, err = rl.Readline() if err != nil { if err == io.EOF { return nil } return err } var args []string args, err := shlex.Split(string(line)) if err != nil { parser.Errorf("%s", err) continue } var ctx *kong.Context ctx, err = parser.Parse(args) if err != nil { parser.Errorf("%s", err) if err, ok := err.(*kong.ParseError); ok { log.Println(err.Error()) err.Context.PrintUsage(false) } continue } err = ctx.Run(ctx) if err != nil { parser.Errorf("%s", err) continue } } } func buildShellParser(tty *os.File) (*kong.Kong, error) { parser, err := kong.New(&grammar{}, kong.Name(""), kong.Description("Example using Kong for interactive command parsing."), kong.Writers(tty, tty), kong.Exit(func(int) {}), kong.ConfigureHelp(kong.HelpOptions{ NoAppSummary: true, }), kong.NoDefaultHelp(), ) return parser, err } func handlerWithError(handle func(log *log.Logger, s ssh.Session) error) ssh.Handler { return func(s ssh.Session) { prefix := fmt.Sprintf("%s->%s ", s.LocalAddr(), s.RemoteAddr()) l := log.New(os.Stdout, prefix, log.LstdFlags) err := handle(l, s) if err != nil { log.Printf("error: %s", err) s.Exit(1) } else { log.Printf("Bye") s.Exit(0) } } } var cli struct { HostKey string `type:"existingfile" help:"SSH host key to use." default:"server_rsa_key"` Bind string `help:"Bind address for server." default:"127.0.0.1:6740"` } func main() { ctx := kong.Parse(&cli, kong.Name("server"), kong.Description("A network server using Kong for interacting with clients.")) ssh.Handle(handlerWithError(handle)) log.Printf("SSH listening on: %s", cli.Bind) log.Printf("Using host key: %s", cli.HostKey) log.Println() parts := strings.Split(cli.Bind, ":") log.Printf("Connect with: ssh -p %s %s", parts[1], parts[0]) log.Println() err := ssh.ListenAndServe(cli.Bind, nil, ssh.HostKeyFile(cli.HostKey)) ctx.FatalIfErrorf(err) } kong-0.8.1/_examples/server/server_rsa_key000066400000000000000000000032131451020262500207030ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAxK1QbPQYibc0VFZdc6GHPka5oTeGnXsMZiyX4JbHUJl1oNDB Xg9NATbft+q/6ZDjyVEQhq8xgLvYFkL8qBLt/6UAaOub0RtmPqQwmxoNLWuXFFwn YlBKApQ4gf58/jOcGPpdEwfjkjwLb536Bni25XMU4cYrdIvQIhtMaK+Zqja/3MAC V6ZRZZCd8hABJqaZu+3mRElnF1d7gfMvA/hhaq7Y5VYr8rUrBHHimrT/GEP6aCbf Npo43SfRnUDu2+EAK7vA9cM8fg/O/mvNR1/9jzOWyr8ZDLD6R6iaZCQ2anEPcqim MCOtibSeVOms07Zcn/TSsgmfGwa8rQkpXVJA5wIDAQABAoIBAQCTUccGdajTrzkx WyfQ71NgoJV3XyIkYAEfn5N8FTTi+LAVb4kILanemP3mw55RE8isCV65pA0OgqYP tsmOE+/WKAAwlxs1/LIPhekqpM7uEMMv6v9NMxrc562UIc36kyn/w7loAebCqNtg FhMsOcu1/wfLPidau0eB5LTNTYtq5RuSKxoindvatk+Zmk0KjoA+f25MlwCEHQNr ygpopclyTHVln2t3t0o97/a7dHa9+HlmVO4GxWvTTiqtcFErTGWtTUW8aeZFS83r p+JZNxReSJ2MlM9bm15wJ0L86GTeYZQiaNuC1XETbFvX+9Ffkl+7EtsdYDLV1N6r /eOP2f0hAoGBAOKVDHmnru7SVmbH5BI8HW5sd6IVztZM3+BKzy6AaPc4/FgG6MOr bJyFbmN8S/gVi4OYOJXgfaeKcycYJFTjXUSnNRQ9eT0MseD9SxzEXV7RGtnvudiu pbRmtBRtf3e4beaN9X4SfWk4+Frw7B8UsPXwV/09s7AW279cES565IkfAoGBAN42 TQSC/jQmJBpGSnqWfqQtKPTSKFoZ/JQbxoy9QckAMqVSFwBBgwQYr4MbI7WyjPRE s43kpf+Sq/++fc3hyk5XAWBKscK0KLs0HBRZyLybQYI+f4/x2giVzKeRRNVa9nQa VdIU8i+eO2AUzG690q89HGkRBsfXekjq5kXC9Cc5AoGAUY0b5F16FPMXrf6cFAQX A7t+g5Qd0fvxSCUk1LPbE8Aq8vPpqyN0ABH2XVBLd4spn7+V/jvCfh7Su2txCCyd USxtak+F53c+PqBr/HqgsJPKek5SMa8KbRfaENAoZMq4o5bMmQfGo6yhlvnHwpgL 6TkMMlWW6vYPOZzFglkxEDkCgYApT78Rz6ii2VRs7hR6pe/1Zc/vdAK8fYhPoLpQ //5y9+5yfch467UH1e8LWMhSx1cdMoiPIKsb0JDZgviwhgGufs5qsHhL0mKgKxft UKPZLKQJKsVcZYI7hl396Sv63mZjP2IlJG/CGpC/VB6NmAzLN3lIrzmrfYvmcoVN AumRQQKBgB4Uznek3nq5ZQf93+ucvXpf0bLRqq1va7jYmRosyiCN52grbclj5uMq vxr1uoqmgtCfqdgUbm0s+kVK6D4bPkz4HQOSXImXhLs8/KdixYfPLSarxYvTxZKg mMF1XqcdRwSv3RZYtUbbF7dYQYsC1/ZKXvtPldeoDmTZ+U7b2qbE -----END RSA PRIVATE KEY----- kong-0.8.1/_examples/server/server_rsa_key.pub000066400000000000000000000005761451020262500215010ustar00rootroot00000000000000ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDErVBs9BiJtzRUVl1zoYc+RrmhN4adewxmLJfglsdQmXWg0MFeD00BNt+36r/pkOPJURCGrzGAu9gWQvyoEu3/pQBo65vRG2Y+pDCbGg0ta5cUXCdiUEoClDiB/nz+M5wY+l0TB+OSPAtvnfoGeLblcxThxit0i9AiG0xor5mqNr/cwAJXplFlkJ3yEAEmppm77eZESWcXV3uB8y8D+GFqrtjlVivytSsEceKatP8YQ/poJt82mjjdJ9GdQO7b4QAru8D1wzx+D87+a81HX/2PM5bKvxkMsPpHqJpkJDZqcQ9yqKYwI62JtJ5U6azTtlyf9NKyCZ8bBrytCSldUkDn kong-0.8.1/_examples/shell/000077500000000000000000000000001451020262500155375ustar00rootroot00000000000000kong-0.8.1/_examples/shell/commandstring/000077500000000000000000000000001451020262500204045ustar00rootroot00000000000000kong-0.8.1/_examples/shell/commandstring/main.go000066400000000000000000000015541451020262500216640ustar00rootroot00000000000000package main import ( "fmt" "github.com/alecthomas/kong" ) var cli struct { Debug bool `help:"Debug mode."` Rm struct { User string `help:"Run as user." short:"u" default:"default"` Force bool `help:"Force removal." short:"f"` Recursive bool `help:"Recursively remove files." short:"r"` Paths []string `arg:"" help:"Paths to remove." type:"path" name:"path"` } `cmd:"" help:"Remove files."` Ls struct { Paths []string `arg:"" optional:"" help:"Paths to list." type:"path"` } `cmd:"" help:"List paths."` } func main() { ctx := kong.Parse(&cli, kong.Name("shell"), kong.Description("A shell-like example app."), kong.UsageOnError(), kong.ConfigureHelp(kong.HelpOptions{ Compact: true, Summary: true, })) switch ctx.Command() { case "rm ": fmt.Println(cli.Rm.Paths, cli.Rm.Force, cli.Rm.Recursive) case "ls": } } kong-0.8.1/_examples/shell/help/000077500000000000000000000000001451020262500164675ustar00rootroot00000000000000kong-0.8.1/_examples/shell/help/README.md000066400000000000000000000003711451020262500177470ustar00rootroot00000000000000# Example of custom help providers This example demonstrates how to add `Help() string` functions (ie. the `HelpProvider` interface) to your commands, arguments, and flags to augment the help text provided using `help:""` style tagged annotations. kong-0.8.1/_examples/shell/help/main.go000066400000000000000000000016201451020262500177410ustar00rootroot00000000000000package main import ( "fmt" "github.com/alecthomas/kong" ) var cli struct { Flag flagWithHelp `help:"Regular flag help"` Echo commandWithHelp `cmd:"" help:"Regular command help"` } type flagWithHelp bool func (f *flagWithHelp) Help() string { return "🏁 additional flag help" } type commandWithHelp struct { Msg argumentWithHelp `arg:"" help:"Regular argument help"` } func (c *commandWithHelp) Help() string { return "🚀 additional command help" } type argumentWithHelp struct { Msg string `arg:""` } func (f *argumentWithHelp) Help() string { return "📣 additional argument help" } func main() { ctx := kong.Parse(&cli, kong.Name("help"), kong.Description("An app demonstrating HelpProviders"), kong.UsageOnError(), kong.ConfigureHelp(kong.HelpOptions{ Compact: true, Summary: false, })) switch ctx.Command() { case "echo ": fmt.Println(cli.Echo.Msg) } } kong-0.8.1/benchmark_test.go000066400000000000000000000032001451020262500157660ustar00rootroot00000000000000package kong import ( "fmt" "strconv" "testing" "github.com/alecthomas/assert/v2" ) func BenchmarkKong_interpolate(b *testing.B) { prepareKong := func(t testing.TB, count int) *Kong { t.Helper() k := &Kong{ vars: make(Vars, count), registry: NewRegistry().RegisterDefaults(), } for i := 0; i < count; i++ { helpVar := fmt.Sprintf("help_param%d", i) k.vars[helpVar] = strconv.Itoa(i) } grammar := &struct { Param0 string `help:"${help_param0}"` }{} model, err := build(k, grammar) assert.NoError(t, err) for i := 0; i < count; i++ { model.Node.Flags = append(model.Node.Flags, &Flag{ Value: &Value{ Help: fmt.Sprintf("${help_param%d}", i), Tag: newEmptyTag(), }, }) } k.Model = model return k } for _, count := range []int{5, 500, 5000} { count := count b.Run(strconv.Itoa(count), func(b *testing.B) { var err error k := prepareKong(b, count) for i := 0; i < b.N; i++ { err = k.interpolate(k.Model.Node) } assert.NoError(b, err) b.ReportAllocs() }) } } func Benchmark_interpolateValue(b *testing.B) { varsLen := 10000 k := &Kong{ vars: make(Vars, 10000), registry: NewRegistry().RegisterDefaults(), } for i := 0; i < varsLen; i++ { helpVar := fmt.Sprintf("help_param%d", i) k.vars[helpVar] = strconv.Itoa(i) } grammar := struct { Param9999 string `kong:"cmd,help=${help_param9999}"` }{} model, err := build(k, &grammar) if err != nil { b.FailNow() } k.Model = model flag := k.Model.Flags[0] for i := 0; i < b.N; i++ { err = k.interpolateValue(flag.Value, k.vars) if err != nil { b.FailNow() } } b.ReportAllocs() } kong-0.8.1/bin/000077500000000000000000000000001451020262500132235ustar00rootroot00000000000000kong-0.8.1/bin/.go-1.19.1.pkg000077700000000000000000000000001451020262500163442hermitustar00rootroot00000000000000kong-0.8.1/bin/.golangci-lint-1.46.2.pkg000077700000000000000000000000001451020262500204672hermitustar00rootroot00000000000000kong-0.8.1/bin/README.hermit.md000066400000000000000000000004161451020262500157720ustar00rootroot00000000000000# Hermit environment This is a [Hermit](https://github.com/cashapp/hermit) bin directory. The symlinks in this directory are managed by Hermit and will automatically download and install Hermit itself as well as packages. These packages are local to this environment. kong-0.8.1/bin/activate-hermit000077500000000000000000000010621451020262500162360ustar00rootroot00000000000000#!/bin/bash # This file must be used with "source bin/activate-hermit" from bash or zsh. # You cannot run it directly if [ "${BASH_SOURCE-}" = "$0" ]; then echo "You must source this script: \$ source $0" >&2 exit 33 fi BIN_DIR="$(dirname "${BASH_SOURCE[0]:-${(%):-%x}}")" if "${BIN_DIR}/hermit" noop > /dev/null; then eval "$("${BIN_DIR}/hermit" activate "${BIN_DIR}/..")" if [ -n "${BASH-}" ] || [ -n "${ZSH_VERSION-}" ]; then hash -r 2>/dev/null fi echo "Hermit environment $("${HERMIT_ENV}"/bin/hermit env HERMIT_ENV) activated" fi kong-0.8.1/bin/go000077700000000000000000000000001451020262500154612.go-1.19.1.pkgustar00rootroot00000000000000kong-0.8.1/bin/gofmt000077700000000000000000000000001451020262500161702.go-1.19.1.pkgustar00rootroot00000000000000kong-0.8.1/bin/golangci-lint000077700000000000000000000000001451020262500217262.golangci-lint-1.46.2.pkgustar00rootroot00000000000000kong-0.8.1/bin/hermit000077500000000000000000000013611451020262500144420ustar00rootroot00000000000000#!/bin/bash set -eo pipefail if [ -z "${HERMIT_STATE_DIR}" ]; then case "$(uname -s)" in Darwin) export HERMIT_STATE_DIR="${HOME}/Library/Caches/hermit" ;; Linux) export HERMIT_STATE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/hermit" ;; esac fi export HERMIT_DIST_URL="${HERMIT_DIST_URL:-https://github.com/cashapp/hermit/releases/download/stable}" HERMIT_CHANNEL="$(basename "${HERMIT_DIST_URL}")" export HERMIT_CHANNEL export HERMIT_EXE=${HERMIT_EXE:-${HERMIT_STATE_DIR}/pkg/hermit@${HERMIT_CHANNEL}/hermit} if [ ! -x "${HERMIT_EXE}" ]; then echo "Bootstrapping ${HERMIT_EXE} from ${HERMIT_DIST_URL}" 1>&2 curl -fsSL "${HERMIT_DIST_URL}/install.sh" | /bin/bash 1>&2 fi exec "${HERMIT_EXE}" --level=fatal exec "$0" -- "$@" kong-0.8.1/bin/hermit.hcl000066400000000000000000000000001451020262500151710ustar00rootroot00000000000000kong-0.8.1/build.go000066400000000000000000000225371451020262500141120ustar00rootroot00000000000000package kong import ( "fmt" "reflect" "strings" ) // Plugins are dynamically embedded command-line structures. // // Each element in the Plugins list *must* be a pointer to a structure. type Plugins []interface{} func build(k *Kong, ast interface{}) (app *Application, err error) { v := reflect.ValueOf(ast) iv := reflect.Indirect(v) if v.Kind() != reflect.Ptr || iv.Kind() != reflect.Struct { return nil, fmt.Errorf("expected a pointer to a struct but got %T", ast) } app = &Application{} extraFlags := k.extraFlags() seenFlags := map[string]bool{} for _, flag := range extraFlags { seenFlags[flag.Name] = true } node, err := buildNode(k, iv, ApplicationNode, newEmptyTag(), seenFlags) if err != nil { return nil, err } if len(node.Positional) > 0 && len(node.Children) > 0 { return nil, fmt.Errorf("can't mix positional arguments and branching arguments on %T", ast) } app.Node = node app.Node.Flags = append(extraFlags, app.Node.Flags...) app.Tag = newEmptyTag() app.Tag.Vars = k.vars return app, nil } func dashedString(s string) string { return strings.Join(camelCase(s), "-") } type flattenedField struct { field reflect.StructField value reflect.Value tag *Tag } func flattenedFields(v reflect.Value, ptag *Tag) (out []flattenedField, err error) { v = reflect.Indirect(v) for i := 0; i < v.NumField(); i++ { ft := v.Type().Field(i) fv := v.Field(i) tag, err := parseTag(v, ft) if err != nil { return nil, err } if tag.Ignored { continue } // Assign group if it's not already set. if tag.Group == "" { tag.Group = ptag.Group } // Accumulate prefixes. tag.Prefix = ptag.Prefix + tag.Prefix tag.EnvPrefix = ptag.EnvPrefix + tag.EnvPrefix // Combine parent vars. tag.Vars = ptag.Vars.CloneWith(tag.Vars) // Command and embedded structs can be pointers, so we hydrate them now. if (tag.Cmd || tag.Embed) && ft.Type.Kind() == reflect.Ptr { fv = reflect.New(ft.Type.Elem()).Elem() v.FieldByIndex(ft.Index).Set(fv.Addr()) } if !ft.Anonymous && !tag.Embed { if fv.CanSet() { field := flattenedField{field: ft, value: fv, tag: tag} out = append(out, field) } continue } // Embedded type. if fv.Kind() == reflect.Interface { fv = fv.Elem() } else if fv.Type() == reflect.TypeOf(Plugins{}) { for i := 0; i < fv.Len(); i++ { fields, ferr := flattenedFields(fv.Index(i).Elem(), tag) if ferr != nil { return nil, ferr } out = append(out, fields...) } continue } sub, err := flattenedFields(fv, tag) if err != nil { return nil, err } out = append(out, sub...) } return out, nil } // Build a Node in the Kong data model. // // "v" is the value to create the node from, "typ" is the output Node type. func buildNode(k *Kong, v reflect.Value, typ NodeType, tag *Tag, seenFlags map[string]bool) (*Node, error) { node := &Node{ Type: typ, Target: v, Tag: tag, } fields, err := flattenedFields(v, tag) if err != nil { return nil, err } MAIN: for _, field := range fields { for _, r := range k.ignoreFields { if r.MatchString(v.Type().Name() + "." + field.field.Name) { continue MAIN } } ft := field.field fv := field.value tag := field.tag name := tag.Name if name == "" { name = tag.Prefix + k.flagNamer(ft.Name) } else { name = tag.Prefix + name } if len(tag.Envs) != 0 { for i := range tag.Envs { tag.Envs[i] = tag.EnvPrefix + tag.Envs[i] } } // Nested structs are either commands or args, unless they implement the Mapper interface. if field.value.Kind() == reflect.Struct && (tag.Cmd || tag.Arg) && k.registry.ForValue(fv) == nil { typ := CommandNode if tag.Arg { typ = ArgumentNode } err = buildChild(k, node, typ, v, ft, fv, tag, name, seenFlags) } else { err = buildField(k, node, v, ft, fv, tag, name, seenFlags) } if err != nil { return nil, err } } // Validate if there are no duplicate names if err := checkDuplicateNames(node, v); err != nil { return nil, err } // "Unsee" flags. for _, flag := range node.Flags { delete(seenFlags, "--"+flag.Name) if flag.Short != 0 { delete(seenFlags, "-"+string(flag.Short)) } } if err := validatePositionalArguments(node); err != nil { return nil, err } return node, nil } func validatePositionalArguments(node *Node) error { var last *Value for i, curr := range node.Positional { if last != nil { // Scan through argument positionals to ensure optional is never before a required. if !last.Required && curr.Required { return fmt.Errorf("%s: required %q cannot come after optional %q", node.FullPath(), curr.Name, last.Name) } // Cumulative argument needs to be last. if last.IsCumulative() { return fmt.Errorf("%s: argument %q cannot come after cumulative %q", node.FullPath(), curr.Name, last.Name) } } last = curr curr.Position = i } return nil } func buildChild(k *Kong, node *Node, typ NodeType, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) error { child, err := buildNode(k, fv, typ, newEmptyTag(), seenFlags) if err != nil { return err } child.Name = name child.Tag = tag child.Parent = node child.Help = tag.Help child.Hidden = tag.Hidden child.Group = buildGroupForKey(k, tag.Group) child.Aliases = tag.Aliases if provider, ok := fv.Addr().Interface().(HelpProvider); ok { child.Detail = provider.Help() } // A branching argument. This is a bit hairy, as we let buildNode() do the parsing, then check that // a positional argument is provided to the child, and move it to the branching argument field. if tag.Arg { if len(child.Positional) == 0 { return failField(v, ft, "positional branch must have at least one child positional argument named %q", name) } if child.Positional[0].Name != name { return failField(v, ft, "first field in positional branch must have the same name as the parent field (%s).", child.Name) } child.Argument = child.Positional[0] child.Positional = child.Positional[1:] if child.Help == "" { child.Help = child.Argument.Help } } else { if tag.HasDefault { if node.DefaultCmd != nil { return failField(v, ft, "can't have more than one default command under %s", node.Summary()) } if tag.Default != "withargs" && (len(child.Children) > 0 || len(child.Positional) > 0) { return failField(v, ft, "default command %s must not have subcommands or arguments", child.Summary()) } node.DefaultCmd = child } if tag.Passthrough { if len(child.Children) > 0 || len(child.Flags) > 0 { return failField(v, ft, "passthrough command %s must not have subcommands or flags", child.Summary()) } if len(child.Positional) != 1 { return failField(v, ft, "passthrough command %s must contain exactly one positional argument", child.Summary()) } if !checkPassthroughArg(child.Positional[0].Target) { return failField(v, ft, "passthrough command %s must contain exactly one positional argument of []string type", child.Summary()) } child.Passthrough = true } } node.Children = append(node.Children, child) if len(child.Positional) > 0 && len(child.Children) > 0 { return failField(v, ft, "can't mix positional arguments and branching arguments") } return nil } func buildField(k *Kong, node *Node, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) error { mapper := k.registry.ForNamedValue(tag.Type, fv) if mapper == nil { return failField(v, ft, "unsupported field type %s, perhaps missing a cmd:\"\" tag?", ft.Type) } value := &Value{ Name: name, Help: tag.Help, OrigHelp: tag.Help, HasDefault: tag.HasDefault, Default: tag.Default, DefaultValue: reflect.New(fv.Type()).Elem(), Mapper: mapper, Tag: tag, Target: fv, Enum: tag.Enum, Passthrough: tag.Passthrough, // Flags are optional by default, and args are required by default. Required: (!tag.Arg && tag.Required) || (tag.Arg && !tag.Optional), Format: tag.Format, } if tag.Arg { node.Positional = append(node.Positional, value) } else { if seenFlags["--"+value.Name] { return failField(v, ft, "duplicate flag --%s", value.Name) } seenFlags["--"+value.Name] = true if tag.Short != 0 { if seenFlags["-"+string(tag.Short)] { return failField(v, ft, "duplicate short flag -%c", tag.Short) } seenFlags["-"+string(tag.Short)] = true } flag := &Flag{ Value: value, Short: tag.Short, PlaceHolder: tag.PlaceHolder, Envs: tag.Envs, Group: buildGroupForKey(k, tag.Group), Xor: tag.Xor, Hidden: tag.Hidden, } value.Flag = flag node.Flags = append(node.Flags, flag) } return nil } func buildGroupForKey(k *Kong, key string) *Group { if key == "" { return nil } for _, group := range k.groups { if group.Key == key { return &group } } // No group provided with kong.ExplicitGroups. We create one ad-hoc for this key. return &Group{ Key: key, Title: key, } } func checkDuplicateNames(node *Node, v reflect.Value) error { seenNames := make(map[string]struct{}) for _, node := range node.Children { if _, ok := seenNames[node.Name]; ok { name := v.Type().Name() if name == "" { name = "" } return fmt.Errorf("duplicate command name %q in command %q", node.Name, name) } seenNames[node.Name] = struct{}{} } return nil } kong-0.8.1/callbacks.go000066400000000000000000000060161451020262500147240ustar00rootroot00000000000000package kong import ( "fmt" "reflect" "strings" ) type bindings map[reflect.Type]func() (reflect.Value, error) func (b bindings) String() string { out := []string{} for k := range b { out = append(out, k.String()) } return "bindings{" + strings.Join(out, ", ") + "}" } func (b bindings) add(values ...interface{}) bindings { for _, v := range values { v := v b[reflect.TypeOf(v)] = func() (reflect.Value, error) { return reflect.ValueOf(v), nil } } return b } func (b bindings) addTo(impl, iface interface{}) { valueOf := reflect.ValueOf(impl) b[reflect.TypeOf(iface).Elem()] = func() (reflect.Value, error) { return valueOf, nil } } func (b bindings) addProvider(provider interface{}) error { pv := reflect.ValueOf(provider) t := pv.Type() if t.Kind() != reflect.Func || t.NumIn() != 0 || t.NumOut() != 2 || t.Out(1) != reflect.TypeOf((*error)(nil)).Elem() { return fmt.Errorf("%T must be a function with the signature func()(T, error)", provider) } rt := pv.Type().Out(0) b[rt] = func() (reflect.Value, error) { out := pv.Call(nil) errv := out[1] var err error if !errv.IsNil() { err = errv.Interface().(error) // nolint } return out[0], err } return nil } // Clone and add values. func (b bindings) clone() bindings { out := make(bindings, len(b)) for k, v := range b { out[k] = v } return out } func (b bindings) merge(other bindings) bindings { for k, v := range other { b[k] = v } return b } func getMethod(value reflect.Value, name string) reflect.Value { method := value.MethodByName(name) if !method.IsValid() { if value.CanAddr() { method = value.Addr().MethodByName(name) } } return method } func callFunction(f reflect.Value, bindings bindings) error { if f.Kind() != reflect.Func { return fmt.Errorf("expected function, got %s", f.Type()) } in := []reflect.Value{} t := f.Type() if t.NumOut() != 1 || !t.Out(0).Implements(callbackReturnSignature) { return fmt.Errorf("return value of %s must implement \"error\"", t) } for i := 0; i < t.NumIn(); i++ { pt := t.In(i) if argf, ok := bindings[pt]; ok { argv, err := argf() if err != nil { return err } in = append(in, argv) } else { return fmt.Errorf("couldn't find binding of type %s for parameter %d of %s(), use kong.Bind(%s)", pt, i, t, pt) } } out := f.Call(in) if out[0].IsNil() { return nil } return out[0].Interface().(error) // nolint } func callAnyFunction(f reflect.Value, bindings bindings) (out []any, err error) { if f.Kind() != reflect.Func { return nil, fmt.Errorf("expected function, got %s", f.Type()) } in := []reflect.Value{} t := f.Type() for i := 0; i < t.NumIn(); i++ { pt := t.In(i) if argf, ok := bindings[pt]; ok { argv, err := argf() if err != nil { return nil, err } in = append(in, argv) } else { return nil, fmt.Errorf("couldn't find binding of type %s for parameter %d of %s(), use kong.Bind(%s)", pt, i, t, pt) } } outv := f.Call(in) out = make([]any, len(outv)) for i, v := range outv { out[i] = v.Interface() } return out, nil } kong-0.8.1/camelcase.go000066400000000000000000000055061451020262500147250ustar00rootroot00000000000000package kong // NOTE: This code is from https://github.com/fatih/camelcase. MIT license. import ( "unicode" "unicode/utf8" ) // Split splits the camelcase word and returns a list of words. It also // supports digits. Both lower camel case and upper camel case are supported. // For more info please check: http://en.wikipedia.org/wiki/CamelCase // // Examples // // "" => [""] // "lowercase" => ["lowercase"] // "Class" => ["Class"] // "MyClass" => ["My", "Class"] // "MyC" => ["My", "C"] // "HTML" => ["HTML"] // "PDFLoader" => ["PDF", "Loader"] // "AString" => ["A", "String"] // "SimpleXMLParser" => ["Simple", "XML", "Parser"] // "vimRPCPlugin" => ["vim", "RPC", "Plugin"] // "GL11Version" => ["GL", "11", "Version"] // "99Bottles" => ["99", "Bottles"] // "May5" => ["May", "5"] // "BFG9000" => ["BFG", "9000"] // "BöseÜberraschung" => ["Böse", "Überraschung"] // "Two spaces" => ["Two", " ", "spaces"] // "BadUTF8\xe2\xe2\xa1" => ["BadUTF8\xe2\xe2\xa1"] // // Splitting rules // // 1) If string is not valid UTF-8, return it without splitting as // single item array. // 2) Assign all unicode characters into one of 4 sets: lower case // letters, upper case letters, numbers, and all other characters. // 3) Iterate through characters of string, introducing splits // between adjacent characters that belong to different sets. // 4) Iterate through array of split strings, and if a given string // is upper case: // if subsequent string is lower case: // move last character of upper case string to beginning of // lower case string func camelCase(src string) (entries []string) { // don't split invalid utf8 if !utf8.ValidString(src) { return []string{src} } entries = []string{} var runes [][]rune lastClass := 0 // split into fields based on class of unicode character for _, r := range src { var class int switch { case unicode.IsLower(r): class = 1 case unicode.IsUpper(r): class = 2 case unicode.IsDigit(r): class = 3 default: class = 4 } if class == lastClass { runes[len(runes)-1] = append(runes[len(runes)-1], r) } else { runes = append(runes, []rune{r}) } lastClass = class } // handle upper case -> lower case sequences, e.g. // "PDFL", "oader" -> "PDF", "Loader" for i := 0; i < len(runes)-1; i++ { if unicode.IsUpper(runes[i][0]) && unicode.IsLower(runes[i+1][0]) { runes[i+1] = append([]rune{runes[i][len(runes[i])-1]}, runes[i+1]...) runes[i] = runes[i][:len(runes[i])-1] } } // construct []string from results for _, s := range runes { if len(s) > 0 { entries = append(entries, string(s)) } } return entries } kong-0.8.1/config_test.go000066400000000000000000000022501451020262500153050ustar00rootroot00000000000000package kong_test import ( "encoding/json" "io/ioutil" "os" "testing" "github.com/alecthomas/assert/v2" "github.com/alecthomas/kong" ) func TestMultipleConfigLoading(t *testing.T) { var cli struct { Flag string `json:"flag,omitempty"` } cli.Flag = "first" first, cleanFirst := makeConfig(t, &cli) defer cleanFirst() cli.Flag = "" second, cleanSecond := makeConfig(t, &cli) defer cleanSecond() p := mustNew(t, &cli, kong.Configuration(kong.JSON, first, second)) _, err := p.Parse(nil) assert.NoError(t, err) assert.Equal(t, "first", cli.Flag) } func TestConfigValidation(t *testing.T) { var cli struct { Flag string `json:"flag,omitempty" enum:"valid" required:""` } cli.Flag = "invalid" conf, cleanConf := makeConfig(t, &cli) defer cleanConf() p := mustNew(t, &cli, kong.Configuration(kong.JSON, conf)) _, err := p.Parse(nil) assert.Error(t, err) } func makeConfig(t *testing.T, config interface{}) (path string, cleanup func()) { t.Helper() w, err := ioutil.TempFile("", "") assert.NoError(t, err) defer w.Close() // nolint: gosec err = json.NewEncoder(w).Encode(config) assert.NoError(t, err) return w.Name(), func() { os.Remove(w.Name()) } } kong-0.8.1/context.go000066400000000000000000000605631451020262500145000ustar00rootroot00000000000000package kong import ( "errors" "fmt" "os" "reflect" "sort" "strconv" "strings" ) // Path records the nodes and parsed values from the current command-line. type Path struct { Parent *Node // One of these will be non-nil. App *Application Positional *Positional Flag *Flag Argument *Argument Command *Command // Flags added by this node. Flags []*Flag // True if this Path element was created as the result of a resolver. Resolved bool } // Node returns the Node associated with this Path, or nil if Path is a non-Node. func (p *Path) Node() *Node { switch { case p.App != nil: return p.App.Node case p.Argument != nil: return p.Argument case p.Command != nil: return p.Command } return nil } // Visitable returns the Visitable for this path element. func (p *Path) Visitable() Visitable { switch { case p.App != nil: return p.App case p.Argument != nil: return p.Argument case p.Command != nil: return p.Command case p.Flag != nil: return p.Flag case p.Positional != nil: return p.Positional } return nil } // Context contains the current parse context. type Context struct { *Kong // A trace through parsed nodes. Path []*Path // Original command-line arguments. Args []string // Error that occurred during trace, if any. Error error values map[*Value]reflect.Value // Temporary values during tracing. bindings bindings resolvers []Resolver // Extra context-specific resolvers. scan *Scanner } // Trace path of "args" through the grammar tree. // // The returned Context will include a Path of all commands, arguments, positionals and flags. // // This just constructs a new trace. To fully apply the trace you must call Reset(), Resolve(), // Validate() and Apply(). func Trace(k *Kong, args []string) (*Context, error) { c := &Context{ Kong: k, Args: args, Path: []*Path{ {App: k.Model, Flags: k.Model.Flags}, }, values: map[*Value]reflect.Value{}, scan: Scan(args...), bindings: bindings{}, } c.Error = c.trace(c.Model.Node) return c, nil } // Bind adds bindings to the Context. func (c *Context) Bind(args ...interface{}) { c.bindings.add(args...) } // BindTo adds a binding to the Context. // // This will typically have to be called like so: // // BindTo(impl, (*MyInterface)(nil)) func (c *Context) BindTo(impl, iface interface{}) { c.bindings.addTo(impl, iface) } // BindToProvider allows binding of provider functions. // // This is useful when the Run() function of different commands require different values that may // not all be initialisable from the main() function. func (c *Context) BindToProvider(provider interface{}) error { return c.bindings.addProvider(provider) } // Value returns the value for a particular path element. func (c *Context) Value(path *Path) reflect.Value { switch { case path.Positional != nil: return c.values[path.Positional] case path.Flag != nil: return c.values[path.Flag.Value] case path.Argument != nil: return c.values[path.Argument.Argument] } panic("can only retrieve value for flag, argument or positional") } // Selected command or argument. func (c *Context) Selected() *Node { var selected *Node for _, path := range c.Path { switch { case path.Command != nil: selected = path.Command case path.Argument != nil: selected = path.Argument } } return selected } // Empty returns true if there were no arguments provided. func (c *Context) Empty() bool { for _, path := range c.Path { if !path.Resolved && path.App == nil { return false } } return true } // Validate the current context. func (c *Context) Validate() error { // nolint: gocyclo err := Visit(c.Model, func(node Visitable, next Next) error { switch node := node.(type) { case *Value: ok := atLeastOneEnvSet(node.Tag.Envs) if node.Enum != "" && (!node.Required || node.HasDefault || (len(node.Tag.Envs) != 0 && ok)) { if err := checkEnum(node, node.Target); err != nil { return err } } case *Flag: ok := atLeastOneEnvSet(node.Tag.Envs) if node.Enum != "" && (!node.Required || node.HasDefault || (len(node.Tag.Envs) != 0 && ok)) { if err := checkEnum(node.Value, node.Target); err != nil { return err } } } return next(nil) }) if err != nil { return err } for _, el := range c.Path { var ( value reflect.Value desc string ) switch node := el.Visitable().(type) { case *Value: value = node.Target desc = node.ShortSummary() case *Flag: value = node.Target desc = node.ShortSummary() case *Application: value = node.Target desc = "" case *Node: value = node.Target desc = node.Path() } if validate := isValidatable(value); validate != nil { if err := validate.Validate(); err != nil { if desc != "" { return fmt.Errorf("%s: %w", desc, err) } return err } } } for _, resolver := range c.combineResolvers() { if err := resolver.Validate(c.Model); err != nil { return err } } for _, path := range c.Path { var value *Value switch { case path.Flag != nil: value = path.Flag.Value case path.Positional != nil: value = path.Positional } if value != nil && value.Tag.Enum != "" { if err := checkEnum(value, value.Target); err != nil { return err } } if err := checkMissingFlags(path.Flags); err != nil { return err } } // Check the terminal node. node := c.Selected() if node == nil { node = c.Model.Node } // Find deepest positional argument so we can check if all required positionals have been provided. positionals := 0 for _, path := range c.Path { if path.Positional != nil { positionals = path.Positional.Position + 1 } } if err := checkMissingChildren(node); err != nil { return err } if err := checkMissingPositionals(positionals, node.Positional); err != nil { return err } if err := checkXorDuplicates(c.Path); err != nil { return err } if node.Type == ArgumentNode { value := node.Argument if value.Required && !value.Set { return fmt.Errorf("%s is required", node.Summary()) } } return nil } // Flags returns the accumulated available flags. func (c *Context) Flags() (flags []*Flag) { for _, trace := range c.Path { flags = append(flags, trace.Flags...) } return } // Command returns the full command path. func (c *Context) Command() string { command := []string{} for _, trace := range c.Path { switch { case trace.Positional != nil: command = append(command, "<"+trace.Positional.Name+">") case trace.Argument != nil: command = append(command, "<"+trace.Argument.Name+">") case trace.Command != nil: command = append(command, trace.Command.Name) } } return strings.Join(command, " ") } // AddResolver adds a context-specific resolver. // // This is most useful in the BeforeResolve() hook. func (c *Context) AddResolver(resolver Resolver) { c.resolvers = append(c.resolvers, resolver) } // FlagValue returns the set value of a flag if it was encountered and exists, or its default value. func (c *Context) FlagValue(flag *Flag) interface{} { for _, trace := range c.Path { if trace.Flag == flag { v, ok := c.values[trace.Flag.Value] if !ok { break } return v.Interface() } } if flag.Target.IsValid() { return flag.Target.Interface() } return flag.DefaultValue.Interface() } // Reset recursively resets values to defaults (as specified in the grammar) or the zero value. func (c *Context) Reset() error { return Visit(c.Model.Node, func(node Visitable, next Next) error { if value, ok := node.(*Value); ok { return next(value.Reset()) } return next(nil) }) } func (c *Context) endParsing() { args := []string{} for { token := c.scan.Pop() if token.Type == EOLToken { break } args = append(args, token.String()) } // Note: tokens must be pushed in reverse order. for i := range args { c.scan.PushTyped(args[len(args)-1-i], PositionalArgumentToken) } } func (c *Context) trace(node *Node) (err error) { // nolint: gocyclo positional := 0 node.Active = true flags := []*Flag{} flagNode := node if node.DefaultCmd != nil && node.DefaultCmd.Tag.Default == "withargs" { // Add flags of the default command if the current node has one // and that default command allows args / flags without explicitly // naming the command on the CLI. flagNode = node.DefaultCmd } for _, group := range flagNode.AllFlags(false) { flags = append(flags, group...) } if node.Passthrough { c.endParsing() } for !c.scan.Peek().IsEOL() { token := c.scan.Peek() switch token.Type { case UntypedToken: switch v := token.Value.(type) { case string: switch { case v == "-": fallthrough default: // nolint c.scan.Pop() c.scan.PushTyped(token.Value, PositionalArgumentToken) // Indicates end of parsing. All remaining arguments are treated as positional arguments only. case v == "--": c.scan.Pop() c.endParsing() // Long flag. case strings.HasPrefix(v, "--"): c.scan.Pop() // Parse it and push the tokens. parts := strings.SplitN(v[2:], "=", 2) if len(parts) > 1 { c.scan.PushTyped(parts[1], FlagValueToken) } c.scan.PushTyped(parts[0], FlagToken) // Short flag. case strings.HasPrefix(v, "-"): c.scan.Pop() // Note: tokens must be pushed in reverse order. if tail := v[2:]; tail != "" { c.scan.PushTyped(tail, ShortFlagTailToken) } c.scan.PushTyped(v[1:2], ShortFlagToken) } default: c.scan.Pop() c.scan.PushTyped(token.Value, PositionalArgumentToken) } case ShortFlagTailToken: c.scan.Pop() // Note: tokens must be pushed in reverse order. if tail := token.String()[1:]; tail != "" { c.scan.PushTyped(tail, ShortFlagTailToken) } c.scan.PushTyped(token.String()[0:1], ShortFlagToken) case FlagToken: if err := c.parseFlag(flags, token.String()); err != nil { return err } case ShortFlagToken: if err := c.parseFlag(flags, token.String()); err != nil { return err } case FlagValueToken: return fmt.Errorf("unexpected flag argument %q", token.Value) case PositionalArgumentToken: candidates := []string{} // Ensure we've consumed all positional arguments. if positional < len(node.Positional) { arg := node.Positional[positional] if arg.Passthrough { c.endParsing() } arg.Active = true err := arg.Parse(c.scan, c.getValue(arg)) if err != nil { return err } c.Path = append(c.Path, &Path{ Parent: node, Positional: arg, }) positional++ break } // Assign token value to a branch name if tagged as an alias // An alias will be ignored in the case of an existing command cmds := make(map[string]bool) for _, branch := range node.Children { if branch.Type == CommandNode { cmds[branch.Name] = true } } for _, branch := range node.Children { for _, a := range branch.Aliases { _, ok := cmds[a] if token.Value == a && !ok { token.Value = branch.Name break } } } // After positional arguments have been consumed, check commands next... for _, branch := range node.Children { if branch.Type == CommandNode && !branch.Hidden { candidates = append(candidates, branch.Name) } if branch.Type == CommandNode && branch.Name == token.Value { c.scan.Pop() c.Path = append(c.Path, &Path{ Parent: node, Command: branch, Flags: branch.Flags, }) return c.trace(branch) } } // Finally, check arguments. for _, branch := range node.Children { if branch.Type == ArgumentNode { arg := branch.Argument if err := arg.Parse(c.scan, c.getValue(arg)); err == nil { c.Path = append(c.Path, &Path{ Parent: node, Argument: branch, Flags: branch.Flags, }) return c.trace(branch) } } } // If there is a default command that allows args and nothing else // matches, take the branch of the default command if node.DefaultCmd != nil && node.DefaultCmd.Tag.Default == "withargs" { c.Path = append(c.Path, &Path{ Parent: node, Command: node.DefaultCmd, Flags: node.DefaultCmd.Flags, }) return c.trace(node.DefaultCmd) } return findPotentialCandidates(token.String(), candidates, "unexpected argument %s", token) default: return fmt.Errorf("unexpected token %s", token) } } return c.maybeSelectDefault(flags, node) } // End of the line, check for a default command, but only if we're not displaying help, // otherwise we'd only ever display the help for the default command. func (c *Context) maybeSelectDefault(flags []*Flag, node *Node) error { for _, flag := range flags { if flag.Name == "help" && flag.Set { return nil } } if node.DefaultCmd != nil { c.Path = append(c.Path, &Path{ Parent: node.DefaultCmd, Command: node.DefaultCmd, Flags: node.DefaultCmd.Flags, }) } return nil } // Resolve walks through the traced path, applying resolvers to any unset flags. func (c *Context) Resolve() error { resolvers := c.combineResolvers() if len(resolvers) == 0 { return nil } inserted := []*Path{} for _, path := range c.Path { for _, flag := range path.Flags { // Flag has already been set on the command-line. if _, ok := c.values[flag.Value]; ok { continue } // Pick the last resolved value. var selected interface{} for _, resolver := range resolvers { s, err := resolver.Resolve(c, path, flag) if err != nil { return fmt.Errorf("%s: %w", flag.ShortSummary(), err) } if s == nil { continue } selected = s } if selected == nil { continue } scan := Scan().PushTyped(selected, FlagValueToken) delete(c.values, flag.Value) err := flag.Parse(scan, c.getValue(flag.Value)) if err != nil { return err } inserted = append(inserted, &Path{ Flag: flag, Resolved: true, }) } } c.Path = append(c.Path, inserted...) return nil } // Combine application-level resolvers and context resolvers. func (c *Context) combineResolvers() []Resolver { resolvers := []Resolver{} resolvers = append(resolvers, c.Kong.resolvers...) resolvers = append(resolvers, c.resolvers...) return resolvers } func (c *Context) getValue(value *Value) reflect.Value { v, ok := c.values[value] if !ok { v = reflect.New(value.Target.Type()).Elem() switch v.Kind() { case reflect.Ptr: v.Set(reflect.New(v.Type().Elem())) case reflect.Slice: v.Set(reflect.MakeSlice(v.Type(), 0, 0)) case reflect.Map: v.Set(reflect.MakeMap(v.Type())) default: } c.values[value] = v } return v } // ApplyDefaults if they are not already set. func (c *Context) ApplyDefaults() error { return Visit(c.Model.Node, func(node Visitable, next Next) error { var value *Value switch node := node.(type) { case *Flag: value = node.Value case *Node: value = node.Argument case *Value: value = node default: } if value != nil { if err := value.ApplyDefault(); err != nil { return err } } return next(nil) }) } // Apply traced context to the target grammar. func (c *Context) Apply() (string, error) { path := []string{} for _, trace := range c.Path { var value *Value switch { case trace.App != nil: case trace.Argument != nil: path = append(path, "<"+trace.Argument.Name+">") value = trace.Argument.Argument case trace.Command != nil: path = append(path, trace.Command.Name) case trace.Flag != nil: value = trace.Flag.Value case trace.Positional != nil: path = append(path, "<"+trace.Positional.Name+">") value = trace.Positional default: panic("unsupported path ?!") } if value != nil { value.Apply(c.getValue(value)) } } 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 { long := "--" + flag.Name short := "-" + string(flag.Short) neg := "--no-" + flag.Name candidates = append(candidates, long) if flag.Short != 0 { candidates = append(candidates, short) } if short != match && long != match && !(match == neg && flag.Tag.Negatable) { continue } // Found a matching flag. c.scan.Pop() if match == neg && flag.Tag.Negatable { flag.Negated = true } err := flag.Parse(c.scan, c.getValue(flag.Value)) if err != nil { var expected *expectedError if errors.As(err, &expected) && expected.token.InferredType().IsAny(FlagToken, ShortFlagToken) { return fmt.Errorf("%s; perhaps try %s=%q?", err.Error(), flag.ShortSummary(), expected.token) } return err } if flag.Negated { value := c.getValue(flag.Value) err := flipBoolValue(value) if err != nil { return err } flag.Value.Apply(value) } c.Path = append(c.Path, &Path{Flag: flag}) return nil } return findPotentialCandidates(match, candidates, "unknown flag %s", match) } // Call an arbitrary function filling arguments with bound values. func (c *Context) Call(fn any, binds ...interface{}) (out []interface{}, err error) { fv := reflect.ValueOf(fn) bindings := c.Kong.bindings.clone().add(binds...).add(c).merge(c.bindings) //nolint:govet return callAnyFunction(fv, bindings) } // RunNode calls the Run() method on an arbitrary node. // // This is useful in conjunction with Visit(), for dynamically running commands. // // Any passed values will be bindable to arguments of the target Run() method. Additionally, // all parent nodes in the command structure will be bound. func (c *Context) RunNode(node *Node, binds ...interface{}) (err error) { type targetMethod struct { node *Node method reflect.Value binds bindings } methodBinds := c.Kong.bindings.clone().add(binds...).add(c).merge(c.bindings) methods := []targetMethod{} for i := 0; node != nil; i, node = i+1, node.Parent { method := getMethod(node.Target, "Run") methodBinds = methodBinds.clone() for p := node; p != nil; p = p.Parent { methodBinds = methodBinds.add(p.Target.Addr().Interface()) } if method.IsValid() { methods = append(methods, targetMethod{node, method, methodBinds}) } } if len(methods) == 0 { return fmt.Errorf("no Run() method found in hierarchy of %s", c.Selected().Summary()) } _, err = c.Apply() if err != nil { return err } for _, method := range methods { if err = callFunction(method.method, method.binds); err != nil { return err } } return nil } // Run executes the Run() method on the selected command, which must exist. // // Any passed values will be bindable to arguments of the target Run() method. Additionally, // all parent nodes in the command structure will be bound. func (c *Context) Run(binds ...interface{}) (err error) { node := c.Selected() if node == nil { if len(c.Path) > 0 { selected := c.Path[0].Node() if selected.Type == ApplicationNode { method := getMethod(selected.Target, "Run") if method.IsValid() { return c.RunNode(selected, binds...) } } } return fmt.Errorf("no command selected") } return c.RunNode(node, binds...) } // PrintUsage to Kong's stdout. // // If summary is true, a summarised version of the help will be output. func (c *Context) PrintUsage(summary bool) error { options := c.helpOptions options.Summary = summary return c.help(options, c) } func checkMissingFlags(flags []*Flag) error { xorGroupSet := map[string]bool{} xorGroup := map[string][]string{} missing := []string{} for _, flag := range flags { if flag.Set { for _, xor := range flag.Xor { xorGroupSet[xor] = true } } if !flag.Required || flag.Set { continue } if len(flag.Xor) > 0 { for _, xor := range flag.Xor { if xorGroupSet[xor] { continue } xorGroup[xor] = append(xorGroup[xor], flag.Summary()) } } else { missing = append(missing, flag.Summary()) } } for xor, flags := range xorGroup { if !xorGroupSet[xor] && len(flags) > 1 { missing = append(missing, strings.Join(flags, " or ")) } } if len(missing) == 0 { return nil } sort.Strings(missing) return fmt.Errorf("missing flags: %s", strings.Join(missing, ", ")) } func checkMissingChildren(node *Node) error { missing := []string{} missingArgs := []string{} for _, arg := range node.Positional { if arg.Required && !arg.Set { missingArgs = append(missingArgs, arg.Summary()) } } if len(missingArgs) > 0 { missing = append(missing, strconv.Quote(strings.Join(missingArgs, " "))) } for _, child := range node.Children { if child.Hidden { continue } if child.Argument != nil { if !child.Argument.Required { continue } missing = append(missing, strconv.Quote(child.Summary())) } else { missing = append(missing, strconv.Quote(child.Name)) } } if len(missing) == 0 { return nil } if len(missing) > 5 { missing = append(missing[:5], "...") } if len(missing) == 1 { return fmt.Errorf("expected %s", missing[0]) } return fmt.Errorf("expected one of %s", strings.Join(missing, ", ")) } // If we're missing any positionals and they're required, return an error. func checkMissingPositionals(positional int, values []*Value) error { // All the positionals are in. if positional >= len(values) { return nil } // We're low on supplied positionals, but the missing one is optional. if !values[positional].Required { return nil } missing := []string{} for ; positional < len(values); positional++ { arg := values[positional] // TODO(aat): Fix hardcoding of these env checks all over the place :\ if len(arg.Tag.Envs) != 0 { if atLeastOneEnvSet(arg.Tag.Envs) { continue } } missing = append(missing, "<"+arg.Name+">") } if len(missing) == 0 { return nil } return fmt.Errorf("missing positional arguments %s", strings.Join(missing, " ")) } func checkEnum(value *Value, target reflect.Value) error { switch target.Kind() { case reflect.Slice, reflect.Array: for i := 0; i < target.Len(); i++ { if err := checkEnum(value, target.Index(i)); err != nil { return err } } return nil 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) enums := []string{} for _, enum := range enumSlice { if enum == v { return nil } enums = append(enums, fmt.Sprintf("%q", enum)) } return fmt.Errorf("%s must be one of %s but got %q", value.ShortSummary(), strings.Join(enums, ","), target.Interface()) } } func checkPassthroughArg(target reflect.Value) bool { typ := target.Type() switch typ.Kind() { case reflect.Slice: return typ.Elem().Kind() == reflect.String default: return false } } func checkXorDuplicates(paths []*Path) error { for _, path := range paths { seen := map[string]*Flag{} for _, flag := range path.Flags { if !flag.Set { continue } for _, xor := range flag.Xor { if seen[xor] != nil { return fmt.Errorf("--%s and --%s can't be used together", seen[xor].Name, flag.Name) } seen[xor] = flag } } } return nil } func findPotentialCandidates(needle string, haystack []string, format string, args ...interface{}) error { if len(haystack) == 0 { return fmt.Errorf(format, args...) } closestCandidates := []string{} for _, candidate := range haystack { if strings.HasPrefix(candidate, needle) || levenshtein(candidate, needle) <= 2 { closestCandidates = append(closestCandidates, fmt.Sprintf("%q", candidate)) } } prefix := fmt.Sprintf(format, args...) if len(closestCandidates) == 1 { return fmt.Errorf("%s, did you mean %s?", prefix, closestCandidates[0]) } else if len(closestCandidates) > 1 { return fmt.Errorf("%s, did you mean one of %s?", prefix, strings.Join(closestCandidates, ", ")) } return fmt.Errorf("%s", prefix) } type validatable interface{ Validate() error } func isValidatable(v reflect.Value) validatable { if !v.IsValid() || (v.Kind() == reflect.Ptr || v.Kind() == reflect.Slice || v.Kind() == reflect.Map) && v.IsNil() { return nil } if validate, ok := v.Interface().(validatable); ok { return validate } if v.CanAddr() { return isValidatable(v.Addr()) } return nil } func atLeastOneEnvSet(envs []string) bool { for _, env := range envs { if _, ok := os.LookupEnv(env); ok { return true } } return false } kong-0.8.1/defaults.go000066400000000000000000000006151451020262500146130ustar00rootroot00000000000000package kong // ApplyDefaults if they are not already set. func ApplyDefaults(target interface{}, options ...Option) error { app, err := New(target, options...) if err != nil { return err } ctx, err := Trace(app, nil) if err != nil { return err } err = ctx.Resolve() if err != nil { return err } if err = ctx.ApplyDefaults(); err != nil { return err } return ctx.Validate() } kong-0.8.1/defaults_test.go000066400000000000000000000012731451020262500156530ustar00rootroot00000000000000package kong import ( "testing" "time" "github.com/alecthomas/assert/v2" ) func TestApplyDefaults(t *testing.T) { type CLI struct { Str string `default:"str"` Duration time.Duration `default:"30s"` } tests := []struct { name string target CLI expected CLI }{ {name: "DefaultsWhenNotSet", expected: CLI{Str: "str", Duration: time.Second * 30}}, {name: "PartiallySetDefaults", target: CLI{Duration: time.Second}, expected: CLI{Str: "str", Duration: time.Second}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := ApplyDefaults(&tt.target) assert.NoError(t, err) assert.Equal(t, tt.expected, tt.target) }) } } kong-0.8.1/doc.go000066400000000000000000000016271451020262500135550ustar00rootroot00000000000000// Package kong aims to support arbitrarily complex command-line structures with as little developer effort as possible. // // Here's an example: // // shell rm [-f] [-r] ... // shell ls [ ...] // // This can be represented by the following command-line structure: // // package main // // import "github.com/alecthomas/kong" // // var CLI struct { // Rm struct { // Force bool `short:"f" help:"Force removal."` // Recursive bool `short:"r" help:"Recursively remove files."` // // Paths []string `arg help:"Paths to remove." type:"path"` // } `cmd help:"Remove files."` // // Ls struct { // Paths []string `arg optional help:"Paths to list." type:"path"` // } `cmd help:"List paths."` // } // // func main() { // kong.Parse(&CLI) // } // // See https://github.com/alecthomas/kong for details. package kong kong-0.8.1/error.go000066400000000000000000000004451451020262500141360ustar00rootroot00000000000000package kong // ParseError is the error type returned by Kong.Parse(). // // It contains the parse Context that triggered the error. type ParseError struct { error Context *Context } // Unwrap returns the original cause of the error. func (p *ParseError) Unwrap() error { return p.error } kong-0.8.1/global.go000066400000000000000000000004661451020262500142500ustar00rootroot00000000000000package kong import ( "os" ) // Parse constructs a new parser and parses the default command-line. func Parse(cli interface{}, options ...Option) *Context { parser, err := New(cli, options...) if err != nil { panic(err) } ctx, err := parser.Parse(os.Args[1:]) parser.FatalIfErrorf(err) return ctx } kong-0.8.1/global_test.go000066400000000000000000000007731451020262500153100ustar00rootroot00000000000000package kong import ( "os" "testing" "github.com/alecthomas/assert/v2" ) func TestParseHandlingBadBuild(t *testing.T) { var cli struct { Enabled bool `kong:"fail='"` } args := os.Args defer func() { os.Args = args }() os.Args = []string{os.Args[0], "hi"} defer func() { if r := recover(); r != nil { assert.Equal(t, "fail=' is not quoted properly", r.(error).Error()) // nolint } }() Parse(&cli, Exit(func(_ int) { panic("exiting") })) t.Fatal("we were expecting a panic") } kong-0.8.1/go.mod000066400000000000000000000002741451020262500135640ustar00rootroot00000000000000module github.com/alecthomas/kong require ( github.com/alecthomas/assert/v2 v2.1.0 github.com/alecthomas/repr v0.1.0 ) require github.com/hexops/gotextdiff v1.0.3 // indirect go 1.18 kong-0.8.1/go.sum000066400000000000000000000010171451020262500136050ustar00rootroot00000000000000github.com/alecthomas/assert/v2 v2.1.0 h1:tbredtNcQnoSd3QBhQWI7QZ3XHOVkw1Moklp2ojoH/0= github.com/alecthomas/assert/v2 v2.1.0/go.mod h1:b/+1DI2Q6NckYi+3mXyH3wFb8qG37K/DuK80n7WefXA= github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE= github.com/alecthomas/repr v0.1.0/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= kong-0.8.1/guesswidth.go000066400000000000000000000002241451020262500151660ustar00rootroot00000000000000// +build appengine !linux,!freebsd,!darwin,!dragonfly,!netbsd,!openbsd package kong import "io" func guessWidth(w io.Writer) int { return 80 } kong-0.8.1/guesswidth_unix.go000066400000000000000000000015771451020262500162450ustar00rootroot00000000000000//go:build (!appengine && linux) || freebsd || darwin || dragonfly || netbsd || openbsd // +build !appengine,linux freebsd darwin dragonfly netbsd openbsd package kong import ( "io" "os" "strconv" "syscall" "unsafe" ) func guessWidth(w io.Writer) int { // check if COLUMNS env is set to comply with // http://pubs.opengroup.org/onlinepubs/009604499/basedefs/xbd_chap08.html colsStr := os.Getenv("COLUMNS") if colsStr != "" { if cols, err := strconv.Atoi(colsStr); err == nil { return cols } } if t, ok := w.(*os.File); ok { fd := t.Fd() var dimensions [4]uint16 if _, _, err := syscall.Syscall6( syscall.SYS_IOCTL, uintptr(fd), // nolint: unconvert uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&dimensions)), // nolint: gas 0, 0, 0, ); err == 0 { if dimensions[1] == 0 { return 80 } return int(dimensions[1]) } } return 80 } kong-0.8.1/help.go000066400000000000000000000340431451020262500137360ustar00rootroot00000000000000package kong import ( "bytes" "fmt" "go/doc" "io" "strings" ) const ( defaultIndent = 2 defaultColumnPadding = 4 ) // Help flag. type helpValue bool func (h helpValue) BeforeReset(ctx *Context) error { options := ctx.Kong.helpOptions options.Summary = false err := ctx.Kong.help(options, ctx) if err != nil { return err } ctx.Kong.Exit(0) return nil } // HelpOptions for HelpPrinters. type HelpOptions struct { // Don't print top-level usage summary. NoAppSummary bool // Write a one-line summary of the context. Summary bool // Write help in a more compact, but still fully-specified, form. Compact bool // Tree writes command chains in a tree structure instead of listing them separately. Tree bool // Place the flags after the commands listing. FlagsLast bool // Indenter modulates the given prefix for the next layer in the tree view. // The following exported templates can be used: kong.SpaceIndenter, kong.LineIndenter, kong.TreeIndenter // The kong.SpaceIndenter will be used by default. Indenter HelpIndenter // Don't show the help associated with subcommands NoExpandSubcommands bool // Clamp the help wrap width to a value smaller than the terminal width. // If this is set to a non-positive number, the terminal width is used; otherwise, // the min of this value or the terminal width is used. WrapUpperBound int } // Apply options to Kong as a configuration option. func (h HelpOptions) Apply(k *Kong) error { k.helpOptions = h return nil } // HelpProvider can be implemented by commands/args to provide detailed help. type HelpProvider interface { // This string is formatted by go/doc and thus has the same formatting rules. Help() string } // PlaceHolderProvider can be implemented by mappers to provide custom placeholder text. type PlaceHolderProvider interface { PlaceHolder(flag *Flag) string } // HelpIndenter is used to indent new layers in the help tree. type HelpIndenter func(prefix string) string // HelpPrinter is used to print context-sensitive help. type HelpPrinter func(options HelpOptions, ctx *Context) error // HelpValueFormatter is used to format the help text of flags and positional arguments. type HelpValueFormatter func(value *Value) string // DefaultHelpValueFormatter is the default HelpValueFormatter. func DefaultHelpValueFormatter(value *Value) string { if len(value.Tag.Envs) == 0 || HasInterpolatedVar(value.OrigHelp, "env") { return value.Help } suffix := "(" + formatEnvs(value.Tag.Envs) + ")" switch { case strings.HasSuffix(value.Help, "."): return value.Help[:len(value.Help)-1] + " " + suffix + "." case value.Help == "": return suffix default: return value.Help + " " + suffix } } // DefaultShortHelpPrinter is the default HelpPrinter for short help on error. func DefaultShortHelpPrinter(options HelpOptions, ctx *Context) error { w := newHelpWriter(ctx, options) cmd := ctx.Selected() app := ctx.Model if cmd == nil { w.Printf("Usage: %s%s", app.Name, app.Summary()) w.Printf(`Run "%s --help" for more information.`, app.Name) } else { w.Printf("Usage: %s %s", app.Name, cmd.Summary()) w.Printf(`Run "%s --help" for more information.`, cmd.FullPath()) } return w.Write(ctx.Stdout) } // DefaultHelpPrinter is the default HelpPrinter. func DefaultHelpPrinter(options HelpOptions, ctx *Context) error { if ctx.Empty() { options.Summary = false } w := newHelpWriter(ctx, options) selected := ctx.Selected() if selected == nil { printApp(w, ctx.Model) } else { printCommand(w, ctx.Model, selected) } return w.Write(ctx.Stdout) } func printApp(w *helpWriter, app *Application) { if !w.NoAppSummary { w.Printf("Usage: %s%s", app.Name, app.Summary()) } printNodeDetail(w, app.Node, true) cmds := app.Leaves(true) if len(cmds) > 0 && app.HelpFlag != nil { w.Print("") if w.Summary { w.Printf(`Run "%s --help" for more information.`, app.Name) } else { w.Printf(`Run "%s --help" for more information on a command.`, app.Name) } } } func printCommand(w *helpWriter, app *Application, cmd *Command) { if !w.NoAppSummary { w.Printf("Usage: %s %s", app.Name, cmd.Summary()) } printNodeDetail(w, cmd, true) if w.Summary && app.HelpFlag != nil { w.Print("") w.Printf(`Run "%s --help" for more information.`, cmd.FullPath()) } } func printNodeDetail(w *helpWriter, node *Node, hide bool) { if node.Help != "" { w.Print("") w.Wrap(node.Help) } if w.Summary { return } if node.Detail != "" { w.Print("") w.Wrap(node.Detail) } if len(node.Positional) > 0 { w.Print("") w.Print("Arguments:") writePositionals(w.Indent(), node.Positional) } printFlags := func() { if flags := node.AllFlags(true); len(flags) > 0 { groupedFlags := collectFlagGroups(flags) for _, group := range groupedFlags { w.Print("") if group.Metadata.Title != "" { w.Wrap(group.Metadata.Title) } if group.Metadata.Description != "" { w.Indent().Wrap(group.Metadata.Description) w.Print("") } writeFlags(w.Indent(), group.Flags) } } } if !w.FlagsLast { printFlags() } var cmds []*Node if w.NoExpandSubcommands { cmds = node.Children } else { cmds = node.Leaves(hide) } if len(cmds) > 0 { iw := w.Indent() if w.Tree { w.Print("") w.Print("Commands:") writeCommandTree(iw, node) } else { groupedCmds := collectCommandGroups(cmds) for _, group := range groupedCmds { w.Print("") if group.Metadata.Title != "" { w.Wrap(group.Metadata.Title) } if group.Metadata.Description != "" { w.Indent().Wrap(group.Metadata.Description) w.Print("") } if w.Compact { writeCompactCommandList(group.Commands, iw) } else { writeCommandList(group.Commands, iw) } } } } if w.FlagsLast { printFlags() } } func writeCommandList(cmds []*Node, iw *helpWriter) { for i, cmd := range cmds { if cmd.Hidden { continue } printCommandSummary(iw, cmd) if i != len(cmds)-1 { iw.Print("") } } } func writeCompactCommandList(cmds []*Node, iw *helpWriter) { rows := [][2]string{} for _, cmd := range cmds { if cmd.Hidden { continue } rows = append(rows, [2]string{cmd.Path(), cmd.Help}) } writeTwoColumns(iw, rows) } func writeCommandTree(w *helpWriter, node *Node) { rows := make([][2]string, 0, len(node.Children)*2) for i, cmd := range node.Children { if cmd.Hidden { continue } rows = append(rows, w.CommandTree(cmd, "")...) if i != len(node.Children)-1 { rows = append(rows, [2]string{"", ""}) } } writeTwoColumns(w, rows) } type helpFlagGroup struct { Metadata *Group Flags [][]*Flag } func collectFlagGroups(flags [][]*Flag) []helpFlagGroup { // Group keys in order of appearance. groups := []*Group{} // Flags grouped by their group key. flagsByGroup := map[string][][]*Flag{} for _, levelFlags := range flags { levelFlagsByGroup := map[string][]*Flag{} for _, flag := range levelFlags { key := "" if flag.Group != nil { key = flag.Group.Key groupAlreadySeen := false for _, group := range groups { if key == group.Key { groupAlreadySeen = true break } } if !groupAlreadySeen { groups = append(groups, flag.Group) } } levelFlagsByGroup[key] = append(levelFlagsByGroup[key], flag) } for key, flags := range levelFlagsByGroup { flagsByGroup[key] = append(flagsByGroup[key], flags) } } out := []helpFlagGroup{} // Ungrouped flags are always displayed first. if ungroupedFlags, ok := flagsByGroup[""]; ok { out = append(out, helpFlagGroup{ Metadata: &Group{Title: "Flags:"}, Flags: ungroupedFlags, }) } for _, group := range groups { out = append(out, helpFlagGroup{Metadata: group, Flags: flagsByGroup[group.Key]}) } return out } type helpCommandGroup struct { Metadata *Group Commands []*Node } func collectCommandGroups(nodes []*Node) []helpCommandGroup { // Groups in order of appearance. groups := []*Group{} // Nodes grouped by their group key. nodesByGroup := map[string][]*Node{} for _, node := range nodes { key := "" if group := node.ClosestGroup(); group != nil { key = group.Key if _, ok := nodesByGroup[key]; !ok { groups = append(groups, group) } } nodesByGroup[key] = append(nodesByGroup[key], node) } out := []helpCommandGroup{} // Ungrouped nodes are always displayed first. if ungroupedNodes, ok := nodesByGroup[""]; ok { out = append(out, helpCommandGroup{ Metadata: &Group{Title: "Commands:"}, Commands: ungroupedNodes, }) } for _, group := range groups { out = append(out, helpCommandGroup{Metadata: group, Commands: nodesByGroup[group.Key]}) } return out } func printCommandSummary(w *helpWriter, cmd *Command) { w.Print(cmd.Summary()) if cmd.Help != "" { w.Indent().Wrap(cmd.Help) } } type helpWriter struct { indent string width int lines *[]string helpFormatter HelpValueFormatter HelpOptions } func newHelpWriter(ctx *Context, options HelpOptions) *helpWriter { lines := []string{} wrapWidth := guessWidth(ctx.Stdout) if options.WrapUpperBound > 0 && wrapWidth > options.WrapUpperBound { wrapWidth = options.WrapUpperBound } w := &helpWriter{ indent: "", width: wrapWidth, lines: &lines, helpFormatter: ctx.Kong.helpFormatter, HelpOptions: options, } return w } func (h *helpWriter) Printf(format string, args ...interface{}) { h.Print(fmt.Sprintf(format, args...)) } func (h *helpWriter) Print(text string) { *h.lines = append(*h.lines, strings.TrimRight(h.indent+text, " ")) } // Indent returns a new helpWriter indented by two characters. func (h *helpWriter) Indent() *helpWriter { return &helpWriter{indent: h.indent + " ", lines: h.lines, width: h.width - 2, HelpOptions: h.HelpOptions, helpFormatter: h.helpFormatter} } func (h *helpWriter) String() string { return strings.Join(*h.lines, "\n") } func (h *helpWriter) Write(w io.Writer) error { for _, line := range *h.lines { _, err := io.WriteString(w, line+"\n") if err != nil { return err } } return nil } func (h *helpWriter) Wrap(text string) { w := bytes.NewBuffer(nil) doc.ToText(w, strings.TrimSpace(text), "", " ", h.width) for _, line := range strings.Split(strings.TrimSpace(w.String()), "\n") { h.Print(line) } } func writePositionals(w *helpWriter, args []*Positional) { rows := [][2]string{} for _, arg := range args { rows = append(rows, [2]string{arg.Summary(), w.helpFormatter(arg)}) } writeTwoColumns(w, rows) } func writeFlags(w *helpWriter, groups [][]*Flag) { rows := [][2]string{} haveShort := false for _, group := range groups { for _, flag := range group { if flag.Short != 0 { haveShort = true break } } } for i, group := range groups { if i > 0 { rows = append(rows, [2]string{"", ""}) } for _, flag := range group { if !flag.Hidden { rows = append(rows, [2]string{formatFlag(haveShort, flag), w.helpFormatter(flag.Value)}) } } } writeTwoColumns(w, rows) } func writeTwoColumns(w *helpWriter, rows [][2]string) { maxLeft := 375 * w.width / 1000 if maxLeft < 30 { maxLeft = 30 } // Find size of first column. leftSize := 0 for _, row := range rows { if c := len(row[0]); c > leftSize && c < maxLeft { leftSize = c } } offsetStr := strings.Repeat(" ", leftSize+defaultColumnPadding) for _, row := range rows { buf := bytes.NewBuffer(nil) doc.ToText(buf, row[1], "", strings.Repeat(" ", defaultIndent), w.width-leftSize-defaultColumnPadding) lines := strings.Split(strings.TrimRight(buf.String(), "\n"), "\n") line := fmt.Sprintf("%-*s", leftSize, row[0]) if len(row[0]) < maxLeft { line += fmt.Sprintf("%*s%s", defaultColumnPadding, "", lines[0]) lines = lines[1:] } w.Print(line) for _, line := range lines { w.Printf("%s%s", offsetStr, line) } } } // haveShort will be true if there are short flags present at all in the help. Useful for column alignment. func formatFlag(haveShort bool, flag *Flag) string { flagString := "" name := flag.Name isBool := flag.IsBool() if flag.Short != 0 { if isBool && flag.Tag.Negatable { flagString += fmt.Sprintf("-%c, --[no-]%s", flag.Short, name) } else { flagString += fmt.Sprintf("-%c, --%s", flag.Short, name) } } else { if isBool && flag.Tag.Negatable { if haveShort { flagString = fmt.Sprintf(" --[no-]%s", name) } else { flagString = fmt.Sprintf("--[no-]%s", name) } } else { if haveShort { flagString += fmt.Sprintf(" --%s", name) } else { flagString += fmt.Sprintf("--%s", name) } } } if !isBool { flagString += fmt.Sprintf("=%s", flag.FormatPlaceHolder()) } return flagString } // CommandTree creates a tree with the given node name as root and its children's arguments and sub commands as leaves. func (h *HelpOptions) CommandTree(node *Node, prefix string) (rows [][2]string) { var nodeName string switch node.Type { default: nodeName += prefix + node.Name if len(node.Aliases) != 0 { nodeName += fmt.Sprintf(" (%s)", strings.Join(node.Aliases, ",")) } case ArgumentNode: nodeName += prefix + "<" + node.Name + ">" } rows = append(rows, [2]string{nodeName, node.Help}) if h.Indenter == nil { prefix = SpaceIndenter(prefix) } else { prefix = h.Indenter(prefix) } for _, arg := range node.Positional { rows = append(rows, [2]string{prefix + arg.Summary(), arg.Help}) } for _, subCmd := range node.Children { if subCmd.Hidden { continue } rows = append(rows, h.CommandTree(subCmd, prefix)...) } return } // SpaceIndenter adds a space indent to the given prefix. func SpaceIndenter(prefix string) string { return prefix + strings.Repeat(" ", defaultIndent) } // LineIndenter adds line points to every new indent. func LineIndenter(prefix string) string { if prefix == "" { return "- " } return strings.Repeat(" ", defaultIndent) + prefix } // TreeIndenter adds line points to every new indent and vertical lines to every layer. func TreeIndenter(prefix string) string { if prefix == "" { return "|- " } return "|" + strings.Repeat(" ", defaultIndent) + prefix } func formatEnvs(envs []string) string { formatted := make([]string, len(envs)) for i := range envs { formatted[i] = "$" + envs[i] } return strings.Join(formatted, ", ") } kong-0.8.1/help_test.go000066400000000000000000000544451451020262500150050ustar00rootroot00000000000000package kong_test import ( "bytes" "fmt" "strings" "testing" "github.com/alecthomas/assert/v2" "github.com/alecthomas/kong" ) func panicsTrue(t *testing.T, f func()) { defer func() { if value := recover(); value != nil { if boolval, ok := value.(bool); !ok || !boolval { t.Fatalf("expected panic with true but got %v", value) } } }() f() t.Fatal("expected panic did not occur") } type threeArg struct { RequiredThree bool `required` Three string `arg` } func (threeArg) Help() string { return `Detailed help provided through the HelpProvider interface.` } func TestHelpOptionalArgs(t *testing.T) { var cli struct { One string `arg:"" optional:"" help:"One optional arg."` Two string `arg:"" optional:"" help:"Two optional arg."` } w := bytes.NewBuffer(nil) exited := false app := mustNew(t, &cli, kong.Name("test-app"), kong.Writers(w, w), kong.Exit(func(int) { exited = true panic(true) // Panic to fake "exit". }), ) panicsTrue(t, func() { _, err := app.Parse([]string{"--help"}) assert.NoError(t, err) }) assert.True(t, exited) expected := `Usage: test-app [ []] Arguments: [] One optional arg. [] Two optional arg. Flags: -h, --help Show context-sensitive help. ` assert.Equal(t, expected, w.String()) } func TestHelp(t *testing.T) { var cli struct { String string `help:"A string flag."` Bool bool `help:"A bool flag with very long help that wraps a lot and is verbose and is really verbose."` Slice []string `help:"A slice of strings." placeholder:"STR"` Map map[string]int `help:"A map of strings to ints."` Required bool `required help:"A required flag."` Sort bool `negatable short:"s" help:"Is sortable or not."` One struct { Flag string `help:"Nested flag."` } `cmd help:"A subcommand."` Two struct { Flag string `help:"Nested flag under two."` RequiredTwo bool `required` Three threeArg `arg help:"Sub-sub-arg."` Four struct { } `cmd help:"Sub-sub-command."` } `cmd help:"Another subcommand."` } w := bytes.NewBuffer(nil) exited := false app := mustNew(t, &cli, kong.Name("test-app"), kong.Description("A test app."), kong.Writers(w, w), kong.Exit(func(int) { exited = true panic(true) // Panic to fake "exit". }), ) t.Run("Full", func(t *testing.T) { panicsTrue(t, func() { _, err := app.Parse([]string{"--help"}) assert.NoError(t, err) }) assert.True(t, exited) expected := `Usage: test-app --required A test app. Flags: -h, --help Show context-sensitive help. --string=STRING A string flag. --bool A bool flag with very long help that wraps a lot and is verbose and is really verbose. --slice=STR,... A slice of strings. --map=KEY=VALUE;... A map of strings to ints. --required A required flag. -s, --[no-]sort Is sortable or not. Commands: one --required A subcommand. two --required --required-two --required-three Sub-sub-arg. two four --required --required-two Sub-sub-command. Run "test-app --help" for more information on a command. ` t.Log(w.String()) t.Log(expected) assert.Equal(t, expected, w.String()) }) t.Run("Selected", func(t *testing.T) { exited = false w.Truncate(0) panicsTrue(t, func() { _, err := app.Parse([]string{"two", "hello", "--help"}) assert.NoError(t, err) }) assert.True(t, exited) expected := `Usage: test-app two --required --required-two --required-three Sub-sub-arg. Detailed help provided through the HelpProvider interface. Flags: -h, --help Show context-sensitive help. --string=STRING A string flag. --bool A bool flag with very long help that wraps a lot and is verbose and is really verbose. --slice=STR,... A slice of strings. --map=KEY=VALUE;... A map of strings to ints. --required A required flag. -s, --[no-]sort Is sortable or not. --flag=STRING Nested flag under two. --required-two --required-three ` t.Log(expected) t.Log(w.String()) assert.Equal(t, expected, w.String()) }) } func TestFlagsLast(t *testing.T) { var cli struct { String string `help:"A string flag."` Bool bool `help:"A bool flag with very long help that wraps a lot and is verbose and is really verbose."` Slice []string `help:"A slice of strings." placeholder:"STR"` Map map[string]int `help:"A map of strings to ints."` Required bool `required help:"A required flag."` One struct { Flag string `help:"Nested flag."` } `cmd help:"A subcommand."` Two struct { Flag string `help:"Nested flag under two."` RequiredTwo bool `required` Three threeArg `arg help:"Sub-sub-arg."` Four struct { } `cmd help:"Sub-sub-command."` } `cmd help:"Another subcommand."` } w := bytes.NewBuffer(nil) exited := false app := mustNew(t, &cli, kong.Name("test-app"), kong.Description("A test app."), kong.HelpOptions{ FlagsLast: true, }, kong.Writers(w, w), kong.Exit(func(int) { exited = true panic(true) // Panic to fake "exit". }), ) t.Run("Full", func(t *testing.T) { panicsTrue(t, func() { _, err := app.Parse([]string{"--help"}) assert.NoError(t, err) }) assert.True(t, exited) expected := `Usage: test-app --required A test app. Commands: one --required A subcommand. two --required --required-two --required-three Sub-sub-arg. two four --required --required-two Sub-sub-command. Flags: -h, --help Show context-sensitive help. --string=STRING A string flag. --bool A bool flag with very long help that wraps a lot and is verbose and is really verbose. --slice=STR,... A slice of strings. --map=KEY=VALUE;... A map of strings to ints. --required A required flag. Run "test-app --help" for more information on a command. ` t.Log(w.String()) t.Log(expected) assert.Equal(t, expected, w.String()) }) t.Run("Selected", func(t *testing.T) { exited = false w.Truncate(0) panicsTrue(t, func() { _, err := app.Parse([]string{"two", "hello", "--help"}) assert.NoError(t, err) }) assert.True(t, exited) expected := `Usage: test-app two --required --required-two --required-three Sub-sub-arg. Detailed help provided through the HelpProvider interface. Flags: -h, --help Show context-sensitive help. --string=STRING A string flag. --bool A bool flag with very long help that wraps a lot and is verbose and is really verbose. --slice=STR,... A slice of strings. --map=KEY=VALUE;... A map of strings to ints. --required A required flag. --flag=STRING Nested flag under two. --required-two --required-three ` t.Log(expected) t.Log(w.String()) assert.Equal(t, expected, w.String()) }) } func TestHelpTree(t *testing.T) { var cli struct { One struct { Thing struct { Arg string `arg help:"argument"` } `cmd help:"subcommand thing"` Other struct { Other string `arg help:"other arg"` } `arg help:"subcommand other"` } `cmd help:"subcommand one" group:"Group A" aliases:"un,uno"` // Groups are ignored in trees Two struct { Three threeArg `arg help:"Sub-sub-arg."` Four struct { } `cmd help:"Sub-sub-command." aliases:"for,fore"` } `cmd help:"Another subcommand."` } w := bytes.NewBuffer(nil) exited := false app := mustNew(t, &cli, kong.Name("test-app"), kong.Description("A test app."), kong.Writers(w, w), kong.ConfigureHelp(kong.HelpOptions{ Tree: true, Indenter: kong.LineIndenter, }), kong.Exit(func(int) { exited = true panic(true) // Panic to fake "exit". }), ) t.Run("Full", func(t *testing.T) { panicsTrue(t, func() { _, err := app.Parse([]string{"--help"}) assert.NoError(t, err) }) assert.True(t, exited) expected := `Usage: test-app A test app. Flags: -h, --help Show context-sensitive help. Commands: one (un,uno) subcommand one - thing subcommand thing - argument - subcommand other two Another subcommand. - Sub-sub-arg. - four (for,fore) Sub-sub-command. Run "test-app --help" for more information on a command. ` if expected != w.String() { t.Errorf("help command returned:\n%v\n\nwant:\n%v", w.String(), expected) } assert.Equal(t, expected, w.String()) }) t.Run("Selected", func(t *testing.T) { exited = false w.Truncate(0) panicsTrue(t, func() { _, err := app.Parse([]string{"one", "--help"}) assert.NoError(t, err) }) assert.True(t, exited) expected := `Usage: test-app one (un,uno) subcommand one Flags: -h, --help Show context-sensitive help. Commands: thing subcommand thing - argument subcommand other ` if expected != w.String() { t.Errorf("help command returned:\n%v\n\nwant:\n%v", w.String(), expected) } assert.Equal(t, expected, w.String()) }) } func TestHelpCompactNoExpand(t *testing.T) { var cli struct { One struct { Thing struct { Arg string `arg help:"argument"` } `cmd help:"subcommand thing"` Other struct { Other string `arg help:"other arg"` } `arg help:"subcommand other"` } `cmd help:"subcommand one" group:"Group A" aliases:"un,uno"` // Groups are ignored in trees Two struct { Three threeArg `arg help:"Sub-sub-arg."` Four struct { } `cmd help:"Sub-sub-command." aliases:"for,fore"` } `cmd help:"Another subcommand."` } w := bytes.NewBuffer(nil) exited := false app := mustNew(t, &cli, kong.Name("test-app"), kong.Description("A test app."), kong.Writers(w, w), kong.ConfigureHelp(kong.HelpOptions{ Compact: true, NoExpandSubcommands: true, }), kong.Exit(func(int) { exited = true panic(true) // Panic to fake "exit". }), ) t.Run("Full", func(t *testing.T) { panicsTrue(t, func() { _, err := app.Parse([]string{"--help"}) assert.NoError(t, err) }) assert.True(t, exited) expected := `Usage: test-app A test app. Flags: -h, --help Show context-sensitive help. Commands: two Another subcommand. Group A one (un,uno) subcommand one Run "test-app --help" for more information on a command. ` if expected != w.String() { t.Errorf("help command returned:\n%v\n\nwant:\n%v", w.String(), expected) } assert.Equal(t, expected, w.String()) }) t.Run("Selected", func(t *testing.T) { exited = false w.Truncate(0) panicsTrue(t, func() { _, err := app.Parse([]string{"one", "--help"}) assert.NoError(t, err) }) assert.True(t, exited) expected := `Usage: test-app one (un,uno) subcommand one Flags: -h, --help Show context-sensitive help. Group A one (un,uno) thing subcommand thing one (un,uno) subcommand other ` if expected != w.String() { t.Errorf("help command returned:\n%v\n\nwant:\n%v", w.String(), expected) } assert.Equal(t, expected, w.String()) }) } func TestEnvarAutoHelp(t *testing.T) { var cli struct { Flag string `env:"FLAG" help:"A flag."` } w := &strings.Builder{} p := mustNew(t, &cli, kong.Writers(w, w), kong.Exit(func(int) {})) _, err := p.Parse([]string{"--help"}) assert.NoError(t, err) assert.Contains(t, w.String(), "A flag ($FLAG).") } func TestMultipleEnvarAutoHelp(t *testing.T) { var cli struct { Flag string `env:"FLAG1,FLAG2" help:"A flag."` } w := &strings.Builder{} p := mustNew(t, &cli, kong.Writers(w, w), kong.Exit(func(int) {})) _, err := p.Parse([]string{"--help"}) assert.NoError(t, err) assert.Contains(t, w.String(), "A flag ($FLAG1, $FLAG2).") } //nolint:dupl // false positive func TestEnvarAutoHelpWithEnvPrefix(t *testing.T) { type Anonymous struct { Flag string `env:"FLAG" help:"A flag."` Other string `help:"A different flag."` } var cli struct { Anonymous `envprefix:"ANON_"` } w := &strings.Builder{} p := mustNew(t, &cli, kong.Writers(w, w), kong.Exit(func(int) {})) _, err := p.Parse([]string{"--help"}) assert.NoError(t, err) assert.Contains(t, w.String(), "A flag ($ANON_FLAG).") assert.Contains(t, w.String(), "A different flag.") } //nolint:dupl // false positive func TestMultipleEnvarAutoHelpWithEnvPrefix(t *testing.T) { type Anonymous struct { Flag string `env:"FLAG1,FLAG2" help:"A flag."` Other string `help:"A different flag."` } var cli struct { Anonymous `envprefix:"ANON_"` } w := &strings.Builder{} p := mustNew(t, &cli, kong.Writers(w, w), kong.Exit(func(int) {})) _, err := p.Parse([]string{"--help"}) assert.NoError(t, err) assert.Contains(t, w.String(), "A flag ($ANON_FLAG1, $ANON_FLAG2).") assert.Contains(t, w.String(), "A different flag.") } //nolint:dupl // false positive func TestCustomValueFormatter(t *testing.T) { var cli struct { Flag string `env:"FLAG" help:"A flag."` } w := &strings.Builder{} p := mustNew(t, &cli, kong.Writers(w, w), kong.Exit(func(int) {}), kong.ValueFormatter(func(value *kong.Value) string { return value.Help }), ) _, err := p.Parse([]string{"--help"}) assert.NoError(t, err) assert.Contains(t, w.String(), "A flag.") } //nolint:dupl // false positive func TestMultipleCustomValueFormatter(t *testing.T) { var cli struct { Flag string `env:"FLAG1,FLAG2" help:"A flag."` } w := &strings.Builder{} p := mustNew(t, &cli, kong.Writers(w, w), kong.Exit(func(int) {}), kong.ValueFormatter(func(value *kong.Value) string { return value.Help }), ) _, err := p.Parse([]string{"--help"}) assert.NoError(t, err) assert.Contains(t, w.String(), "A flag.") } func TestAutoGroup(t *testing.T) { var cli struct { GroupedAString string `help:"A string flag grouped in A."` FreeString string `help:"A non grouped string flag."` GroupedBString string `help:"A string flag grouped in B."` FreeBool bool `help:"A non grouped bool flag."` GroupedABool bool `help:"A bool flag grouped in A."` One struct { Flag string `help:"Nested flag."` // Group is inherited from the parent command Thing struct { Arg string `arg help:"argument"` } `cmd help:"subcommand thing"` Other struct { Other string `arg help:"other arg"` } `arg help:"subcommand other"` // ... but a subcommand can override it Stuff struct { Stuff string `arg help:"argument"` } `arg help:"subcommand stuff"` } `cmd help:"A subcommand grouped in A."` Two struct { Grouped1String string `help:"A string flag grouped in 1."` AFreeString string `help:"A non grouped string flag."` Grouped2String string `help:"A string flag grouped in 2."` AGroupedAString bool `help:"A string flag grouped in A."` Grouped1Bool bool `help:"A bool flag grouped in 1."` } `cmd help:"A non grouped subcommand."` Four struct { Flag string `help:"Nested flag."` } `cmd help:"Another subcommand grouped in B."` Three struct { Flag string `help:"Nested flag."` } `cmd help:"Another subcommand grouped in A."` } w := bytes.NewBuffer(nil) app := mustNew(t, &cli, kong.Writers(w, w), kong.Exit(func(int) {}), kong.AutoGroup(func(parent kong.Visitable, flag *kong.Flag) *kong.Group { if node, ok := parent.(*kong.Node); ok { return &kong.Group{ Key: node.Name, Title: strings.Title(node.Name) + " flags:", // nolint } } return nil }), ) _, _ = app.Parse([]string{"--help", "two"}) assert.Equal(t, `Usage: test two A non grouped subcommand. Flags: -h, --help Show context-sensitive help. --grouped-a-string=STRING A string flag grouped in A. --free-string=STRING A non grouped string flag. --grouped-b-string=STRING A string flag grouped in B. --free-bool A non grouped bool flag. --grouped-a-bool A bool flag grouped in A. Two flags: --grouped-1-string=STRING A string flag grouped in 1. --a-free-string=STRING A non grouped string flag. --grouped-2-string=STRING A string flag grouped in 2. --a-grouped-a-string A string flag grouped in A. --grouped-1-bool A bool flag grouped in 1. `, w.String()) } func TestHelpGrouping(t *testing.T) { var cli struct { GroupedAString string `help:"A string flag grouped in A." group:"Group A"` FreeString string `help:"A non grouped string flag."` GroupedBString string `help:"A string flag grouped in B." group:"Group B"` FreeBool bool `help:"A non grouped bool flag."` GroupedABool bool `help:"A bool flag grouped in A." group:"Group A"` One struct { Flag string `help:"Nested flag."` // Group is inherited from the parent command Thing struct { Arg string `arg help:"argument"` } `cmd help:"subcommand thing"` Other struct { Other string `arg help:"other arg"` } `arg help:"subcommand other"` // ... but a subcommand can override it Stuff struct { Stuff string `arg help:"argument"` } `arg help:"subcommand stuff" group:"Group B"` } `cmd help:"A subcommand grouped in A." group:"Group A"` Two struct { Grouped1String string `help:"A string flag grouped in 1." group:"Group 1"` AFreeString string `help:"A non grouped string flag."` Grouped2String string `help:"A string flag grouped in 2." group:"Group 2"` AGroupedAString bool `help:"A string flag grouped in A." group:"Group A"` Grouped1Bool bool `help:"A bool flag grouped in 1." group:"Group 1"` } `cmd help:"A non grouped subcommand."` Four struct { Flag string `help:"Nested flag."` } `cmd help:"Another subcommand grouped in B." group:"Group B"` Three struct { Flag string `help:"Nested flag."` } `cmd help:"Another subcommand grouped in A." group:"Group A"` } w := bytes.NewBuffer(nil) exited := false app := mustNew(t, &cli, kong.Name("test-app"), kong.Description("A test app."), kong.Groups{ "Group A": "Group title taken from the kong.ExplicitGroups option\nA group header", "Group 1": "Another group title, this time without header", "Unknown key": "", }, kong.Writers(w, w), kong.Exit(func(int) { exited = true panic(true) // Panic to fake "exit". }), ) t.Run("Full", func(t *testing.T) { panicsTrue(t, func() { _, err := app.Parse([]string{"--help"}) assert.True(t, exited) assert.NoError(t, err) }) expected := `Usage: test-app A test app. Flags: -h, --help Show context-sensitive help. --free-string=STRING A non grouped string flag. --free-bool A non grouped bool flag. Group title taken from the kong.ExplicitGroups option A group header --grouped-a-string=STRING A string flag grouped in A. --grouped-a-bool A bool flag grouped in A. Group B --grouped-b-string=STRING A string flag grouped in B. Commands: two A non grouped subcommand. Group title taken from the kong.ExplicitGroups option A group header one thing subcommand thing one subcommand other three Another subcommand grouped in A. Group B one subcommand stuff four Another subcommand grouped in B. Run "test-app --help" for more information on a command. ` t.Log(w.String()) t.Log(expected) assert.Equal(t, expected, w.String()) }) t.Run("Selected", func(t *testing.T) { exited = false w.Truncate(0) panicsTrue(t, func() { _, err := app.Parse([]string{"two", "--help"}) assert.NoError(t, err) assert.True(t, exited) }) expected := `Usage: test-app two A non grouped subcommand. Flags: -h, --help Show context-sensitive help. --free-string=STRING A non grouped string flag. --free-bool A non grouped bool flag. --a-free-string=STRING A non grouped string flag. Group title taken from the kong.ExplicitGroups option A group header --grouped-a-string=STRING A string flag grouped in A. --grouped-a-bool A bool flag grouped in A. --a-grouped-a-string A string flag grouped in A. Group B --grouped-b-string=STRING A string flag grouped in B. Another group title, this time without header --grouped-1-string=STRING A string flag grouped in 1. --grouped-1-bool A bool flag grouped in 1. Group 2 --grouped-2-string=STRING A string flag grouped in 2. ` t.Log(expected) t.Log(w.String()) assert.Equal(t, expected, w.String()) }) } func TestUsageOnError(t *testing.T) { var cli struct { Flag string `help:"A required flag." required` } w := &strings.Builder{} p := mustNew(t, &cli, kong.Writers(w, w), kong.Description("Some description."), kong.Exit(func(int) {}), kong.UsageOnError(), ) _, err := p.Parse([]string{}) p.FatalIfErrorf(err) expected := `Usage: test --flag=STRING Some description. Flags: -h, --help Show context-sensitive help. --flag=STRING A required flag. test: error: missing flags: --flag=STRING ` assert.Equal(t, expected, w.String()) } func TestShortUsageOnError(t *testing.T) { var cli struct { Flag string `help:"A required flag." required` } w := &strings.Builder{} p := mustNew(t, &cli, kong.Writers(w, w), kong.Description("Some description."), kong.Exit(func(int) {}), kong.ShortUsageOnError(), ) _, err := p.Parse([]string{}) assert.Error(t, err) p.FatalIfErrorf(err) expected := `Usage: test --flag=STRING Run "test --help" for more information. test: error: missing flags: --flag=STRING ` assert.Equal(t, expected, w.String()) } func TestCustomShortUsageOnError(t *testing.T) { var cli struct { Flag string `help:"A required flag." required` } w := &strings.Builder{} shortHelp := func(_ kong.HelpOptions, ctx *kong.Context) error { fmt.Fprintln(ctx.Stdout, "🤷 wish I could help") return nil } p := mustNew(t, &cli, kong.Writers(w, w), kong.Description("Some description."), kong.Exit(func(int) {}), kong.ShortHelp(shortHelp), kong.ShortUsageOnError(), ) _, err := p.Parse([]string{}) assert.Error(t, err) p.FatalIfErrorf(err) expected := `🤷 wish I could help test: error: missing flags: --flag=STRING ` assert.Equal(t, expected, w.String()) } kong-0.8.1/helpwrap1.18_test.go000066400000000000000000000017351451020262500162010ustar00rootroot00000000000000//go:build !go1.19 // +build !go1.19 package kong_test import ( "bytes" "testing" "github.com/alecthomas/assert/v2" "github.com/alecthomas/kong" ) func TestCustomWrap(t *testing.T) { var cli struct { Flag string `help:"A string flag with very long help that wraps a lot and is verbose and is really verbose."` } w := bytes.NewBuffer(nil) app := mustNew(t, &cli, kong.Name("test-app"), kong.Description("A test app."), kong.HelpOptions{ WrapUpperBound: 50, }, kong.Writers(w, w), kong.Exit(func(int) {}), ) _, err := app.Parse([]string{"--help"}) assert.NoError(t, err) expected := `Usage: test-app A test app. Flags: -h, --help Show context-sensitive help. --flag=STRING A string flag with very long help that wraps a lot and is verbose and is really verbose. ` t.Log(w.String()) t.Log(expected) assert.Equal(t, expected, w.String()) } kong-0.8.1/helpwrap1.19_test.go000066400000000000000000000021621451020262500161750ustar00rootroot00000000000000// Wrapping of text changed in Go1.19 per https://github.com/alecthomas/kong/issues/325 // The test has been split pre-go1.19 and go1.19 and onwards. //go:build go1.19 // +build go1.19 package kong_test import ( "bytes" "testing" "github.com/alecthomas/assert/v2" "github.com/alecthomas/kong" ) func TestCustomWrap(t *testing.T) { var cli struct { Flag string `help:"A string flag with very long help that wraps a lot and is verbose and is really verbose."` } w := bytes.NewBuffer(nil) app := mustNew(t, &cli, kong.Name("test-app"), kong.Description("A test app."), kong.HelpOptions{ WrapUpperBound: 50, }, kong.Writers(w, w), kong.Exit(func(int) {}), ) _, err := app.Parse([]string{"--help"}) assert.NoError(t, err) expected := `Usage: test-app A test app. Flags: -h, --help Show context-sensitive help. --flag=STRING A string flag with very long help that wraps a lot and is verbose and is really verbose. ` t.Log(w.String()) t.Log(expected) assert.Equal(t, expected, w.String()) } kong-0.8.1/hooks.go000066400000000000000000000013241451020262500141250ustar00rootroot00000000000000package kong // BeforeResolve is a documentation-only interface describing hooks that run before resolvers are applied. type BeforeResolve interface { // This is not the correct signature - see README for details. BeforeResolve(args ...interface{}) error } // BeforeApply is a documentation-only interface describing hooks that run before values are set. type BeforeApply interface { // This is not the correct signature - see README for details. BeforeApply(args ...interface{}) error } // AfterApply is a documentation-only interface describing hooks that run after values are set. type AfterApply interface { // This is not the correct signature - see README for details. AfterApply(args ...interface{}) error } kong-0.8.1/interpolate.go000066400000000000000000000023431451020262500153320ustar00rootroot00000000000000package kong import ( "fmt" "regexp" ) var interpolationRegex = regexp.MustCompile(`(\$\$)|((?:\${([[:alpha:]_][[:word:]]*))(?:=([^}]+))?})|(\$)|([^$]+)`) // HasInterpolatedVar returns true if the variable "v" is interpolated in "s". func HasInterpolatedVar(s string, v string) bool { matches := interpolationRegex.FindAllStringSubmatch(s, -1) for _, match := range matches { if name := match[3]; name == v { return true } } return false } // Interpolate variables from vars into s for substrings in the form ${var} or ${var=default}. func interpolate(s string, vars Vars, updatedVars map[string]string) (string, error) { out := "" matches := interpolationRegex.FindAllStringSubmatch(s, -1) if len(matches) == 0 { return s, nil } for key, val := range updatedVars { if vars[key] != val { vars = vars.CloneWith(updatedVars) break } } for _, match := range matches { if dollar := match[1]; dollar != "" { out += "$" } else if name := match[3]; name != "" { value, ok := vars[name] if !ok { // No default value. if match[4] == "" { return "", fmt.Errorf("undefined variable ${%s}", name) } value = match[4] } out += value } else { out += match[0] } } return out, nil } kong-0.8.1/interpolate_test.go000066400000000000000000000016611451020262500163730ustar00rootroot00000000000000package kong import ( "testing" "github.com/alecthomas/assert/v2" ) func TestInterpolate(t *testing.T) { vars := map[string]string{ "age": "35", "city": "Melbourne", } updatedVars := map[string]string{ "height": "180", } actual, err := interpolate("${name=Bobby Brown} is ${age} years old, ${height} cm tall, lives in ${city=}, and likes $${AUD}", vars, updatedVars) assert.NoError(t, err) assert.Equal(t, `Bobby Brown is 35 years old, 180 cm tall, lives in Melbourne, and likes ${AUD}`, actual) } func TestHasInterpolatedVar(t *testing.T) { for _, tag := range []string{"name", "age", "height", "city"} { assert.True(t, HasInterpolatedVar("${name=Bobby Brown} is ${age} years old, ${height} cm tall, lives in ${city=}, and likes $${AUD}", tag), tag) } for _, tag := range []string{"name", "age", "height", "AUD"} { assert.False(t, HasInterpolatedVar("$name $$age {height} $${AUD}", tag), tag) } } kong-0.8.1/kong.go000066400000000000000000000267421451020262500137530ustar00rootroot00000000000000package kong import ( "errors" "fmt" "io" "os" "path/filepath" "reflect" "regexp" "strings" ) var ( callbackReturnSignature = reflect.TypeOf((*error)(nil)).Elem() ) func failField(parent reflect.Value, field reflect.StructField, format string, args ...interface{}) error { name := parent.Type().Name() if name == "" { name = "" } return fmt.Errorf("%s.%s: %s", name, field.Name, fmt.Sprintf(format, args...)) } // Must creates a new Parser or panics if there is an error. func Must(ast interface{}, options ...Option) *Kong { k, err := New(ast, options...) if err != nil { panic(err) } return k } type usageOnError int const ( shortUsage usageOnError = iota + 1 fullUsage ) // Kong is the main parser type. type Kong struct { // Grammar model. Model *Application // Termination function (defaults to os.Exit) Exit func(int) Stdout io.Writer Stderr io.Writer bindings bindings loader ConfigurationLoader resolvers []Resolver registry *Registry ignoreFields []*regexp.Regexp noDefaultHelp bool usageOnError usageOnError help HelpPrinter shortHelp HelpPrinter helpFormatter HelpValueFormatter helpOptions HelpOptions helpFlag *Flag groups []Group vars Vars flagNamer func(string) string // Set temporarily by Options. These are applied after build(). postBuildOptions []Option embedded []embedded dynamicCommands []*dynamicCommand } // New creates a new Kong parser on grammar. // // See the README (https://github.com/alecthomas/kong) for usage instructions. func New(grammar interface{}, options ...Option) (*Kong, error) { k := &Kong{ Exit: os.Exit, Stdout: os.Stdout, Stderr: os.Stderr, registry: NewRegistry().RegisterDefaults(), vars: Vars{}, bindings: bindings{}, helpFormatter: DefaultHelpValueFormatter, ignoreFields: make([]*regexp.Regexp, 0), flagNamer: func(s string) string { return strings.ToLower(dashedString(s)) }, } options = append(options, Bind(k)) for _, option := range options { if err := option.Apply(k); err != nil { return nil, err } } if k.help == nil { k.help = DefaultHelpPrinter } if k.shortHelp == nil { k.shortHelp = DefaultShortHelpPrinter } model, err := build(k, grammar) if err != nil { return k, err } model.Name = filepath.Base(os.Args[0]) k.Model = model k.Model.HelpFlag = k.helpFlag // Embed any embedded structs. for _, embed := range k.embedded { tag, err := parseTagString(strings.Join(embed.tags, " ")) //nolint:govet if err != nil { return nil, err } tag.Embed = true v := reflect.Indirect(reflect.ValueOf(embed.strct)) node, err := buildNode(k, v, CommandNode, tag, map[string]bool{}) if err != nil { return nil, err } for _, child := range node.Children { child.Parent = k.Model.Node k.Model.Children = append(k.Model.Children, child) } k.Model.Flags = append(k.Model.Flags, node.Flags...) } // Synthesise command nodes. for _, dcmd := range k.dynamicCommands { tag, terr := parseTagString(strings.Join(dcmd.tags, " ")) if terr != nil { return nil, terr } tag.Name = dcmd.name tag.Help = dcmd.help tag.Group = dcmd.group tag.Cmd = true v := reflect.Indirect(reflect.ValueOf(dcmd.cmd)) err = buildChild(k, k.Model.Node, CommandNode, reflect.Value{}, reflect.StructField{ Name: dcmd.name, Type: v.Type(), }, v, tag, dcmd.name, map[string]bool{}) if err != nil { return nil, err } } for _, option := range k.postBuildOptions { if err = option.Apply(k); err != nil { return nil, err } } k.postBuildOptions = nil if err = k.interpolate(k.Model.Node); err != nil { return nil, err } k.bindings.add(k.vars) return k, nil } type varStack []Vars func (v *varStack) head() Vars { return (*v)[len(*v)-1] } func (v *varStack) pop() { *v = (*v)[:len(*v)-1] } func (v *varStack) push(vars Vars) Vars { if len(*v) != 0 { vars = (*v)[len(*v)-1].CloneWith(vars) } *v = append(*v, vars) return vars } // Interpolate variables into model. func (k *Kong) interpolate(node *Node) (err error) { stack := varStack{} return Visit(node, func(node Visitable, next Next) error { switch node := node.(type) { case *Node: vars := stack.push(node.Vars()) node.Help, err = interpolate(node.Help, vars, nil) if err != nil { return fmt.Errorf("help for %s: %s", node.Path(), err) } err = next(nil) stack.pop() return err case *Value: return next(k.interpolateValue(node, stack.head())) } return next(nil) }) } func (k *Kong) interpolateValue(value *Value, vars Vars) (err error) { if len(value.Tag.Vars) > 0 { vars = vars.CloneWith(value.Tag.Vars) } if varsContributor, ok := value.Mapper.(VarsContributor); ok { vars = vars.CloneWith(varsContributor.Vars(value)) } if value.Enum, err = interpolate(value.Enum, vars, nil); err != nil { return fmt.Errorf("enum for %s: %s", value.Summary(), err) } updatedVars := map[string]string{ "default": value.Default, "enum": value.Enum, } if value.Default, err = interpolate(value.Default, vars, nil); err != nil { return fmt.Errorf("default value for %s: %s", value.Summary(), err) } if value.Enum, err = interpolate(value.Enum, vars, nil); err != nil { return fmt.Errorf("enum value for %s: %s", value.Summary(), err) } if value.Flag != nil { for i, env := range value.Flag.Envs { if value.Flag.Envs[i], err = interpolate(env, vars, nil); err != nil { return fmt.Errorf("env value for %s: %s", value.Summary(), err) } } value.Tag.Envs = value.Flag.Envs updatedVars["env"] = "" if len(value.Flag.Envs) != 0 { updatedVars["env"] = value.Flag.Envs[0] } } value.Help, err = interpolate(value.Help, vars, updatedVars) if err != nil { return fmt.Errorf("help for %s: %s", value.Summary(), err) } return nil } // Provide additional builtin flags, if any. func (k *Kong) extraFlags() []*Flag { if k.noDefaultHelp { return nil } var helpTarget helpValue value := reflect.ValueOf(&helpTarget).Elem() helpFlag := &Flag{ Short: 'h', Value: &Value{ Name: "help", Help: "Show context-sensitive help.", OrigHelp: "Show context-sensitive help.", Target: value, Tag: &Tag{}, Mapper: k.registry.ForValue(value), DefaultValue: reflect.ValueOf(false), }, } helpFlag.Flag = helpFlag k.helpFlag = helpFlag return []*Flag{helpFlag} } // Parse arguments into target. // // The return Context can be used to further inspect the parsed command-line, to format help, to find the // selected command, to run command Run() methods, and so on. See Context and README for more information. // // Will return a ParseError if a *semantically* invalid command-line is encountered (as opposed to a syntactically // invalid one, which will report a normal error). func (k *Kong) Parse(args []string) (ctx *Context, err error) { ctx, err = Trace(k, args) if err != nil { return nil, err } if ctx.Error != nil { return nil, &ParseError{error: ctx.Error, Context: ctx} } if err = k.applyHook(ctx, "BeforeReset"); err != nil { return nil, &ParseError{error: err, Context: ctx} } if err = ctx.Reset(); err != nil { return nil, &ParseError{error: err, Context: ctx} } if err = k.applyHook(ctx, "BeforeResolve"); err != nil { return nil, &ParseError{error: err, Context: ctx} } if err = ctx.Resolve(); err != nil { return nil, &ParseError{error: err, Context: ctx} } if err = k.applyHook(ctx, "BeforeApply"); err != nil { return nil, &ParseError{error: err, Context: ctx} } if _, err = ctx.Apply(); err != nil { return nil, &ParseError{error: err, Context: ctx} } if err = ctx.Validate(); err != nil { return nil, &ParseError{error: err, Context: ctx} } if err = k.applyHook(ctx, "AfterApply"); err != nil { return nil, &ParseError{error: err, Context: ctx} } return ctx, nil } func (k *Kong) applyHook(ctx *Context, name string) error { for _, trace := range ctx.Path { var value reflect.Value switch { case trace.App != nil: value = trace.App.Target case trace.Argument != nil: value = trace.Argument.Target case trace.Command != nil: value = trace.Command.Target case trace.Positional != nil: value = trace.Positional.Target case trace.Flag != nil: value = trace.Flag.Value.Target default: panic("unsupported Path") } method := getMethod(value, name) if !method.IsValid() { continue } binds := k.bindings.clone() binds.add(ctx, trace) binds.add(trace.Node().Vars().CloneWith(k.vars)) binds.merge(ctx.bindings) if err := callFunction(method, binds); err != nil { return err } } // Path[0] will always be the app root. return k.applyHookToDefaultFlags(ctx, ctx.Path[0].Node(), name) } // Call hook on any unset flags with default values. func (k *Kong) applyHookToDefaultFlags(ctx *Context, node *Node, name string) error { if node == nil { return nil } return Visit(node, func(n Visitable, next Next) error { node, ok := n.(*Node) if !ok { return next(nil) } binds := k.bindings.clone().add(ctx).add(node.Vars().CloneWith(k.vars)) for _, flag := range node.Flags { if !flag.HasDefault || ctx.values[flag.Value].IsValid() || !flag.Target.IsValid() { continue } method := getMethod(flag.Target, name) if !method.IsValid() { continue } path := &Path{Flag: flag} if err := callFunction(method, binds.clone().add(path)); err != nil { return next(err) } } return next(nil) }) } func formatMultilineMessage(w io.Writer, leaders []string, format string, args ...interface{}) { lines := strings.Split(fmt.Sprintf(format, args...), "\n") leader := "" for _, l := range leaders { if l == "" { continue } leader += l + ": " } fmt.Fprintf(w, "%s%s\n", leader, lines[0]) for _, line := range lines[1:] { fmt.Fprintf(w, "%*s%s\n", len(leader), " ", line) } } // Printf writes a message to Kong.Stdout with the application name prefixed. func (k *Kong) Printf(format string, args ...interface{}) *Kong { formatMultilineMessage(k.Stdout, []string{k.Model.Name}, format, args...) return k } // Errorf writes a message to Kong.Stderr with the application name prefixed. func (k *Kong) Errorf(format string, args ...interface{}) *Kong { formatMultilineMessage(k.Stderr, []string{k.Model.Name, "error"}, format, args...) return k } // Fatalf writes a message to Kong.Stderr with the application name prefixed then exits with a non-zero status. func (k *Kong) Fatalf(format string, args ...interface{}) { k.Errorf(format, args...) k.Exit(1) } // FatalIfErrorf terminates with an error message if err != nil. func (k *Kong) FatalIfErrorf(err error, args ...interface{}) { if err == nil { return } msg := err.Error() if len(args) > 0 { msg = fmt.Sprintf(args[0].(string), args[1:]...) + ": " + err.Error() // nolint } // Maybe display usage information. var parseErr *ParseError if errors.As(err, &parseErr) { switch k.usageOnError { case fullUsage: _ = k.help(k.helpOptions, parseErr.Context) fmt.Fprintln(k.Stdout) case shortUsage: _ = k.shortHelp(k.helpOptions, parseErr.Context) fmt.Fprintln(k.Stdout) } } k.Fatalf("%s", msg) } // LoadConfig from path using the loader configured via Configuration(loader). // // "path" will have ~ and any variables expanded. func (k *Kong) LoadConfig(path string) (Resolver, error) { var err error path = ExpandPath(path) path, err = interpolate(path, k.vars, nil) if err != nil { return nil, err } r, err := os.Open(path) // nolint: gas if err != nil { return nil, err } defer r.Close() // nolint: gosec return k.loader(r) } kong-0.8.1/kong.png000066400000000000000000002060021451020262500141170ustar00rootroot00000000000000PNG  IHDR<BpsRGB@IDATx Gy_3JӒl|ɒVj/J626X`M>D !$l>91%_" %_l|`ailFݝtWWکgzK                                                                                                    A% j8< ,YRiZY""R*H\5CUEJ0LSWtΪFH":D"1tСJ      DV~ У\{kBpXCLȃu)iBdif)̈i"DĔJkiA4 =L:aۄ4JIzB4c4x0̗aQЩS|Bp8@@@@@@ LG#XyV܄0/ R]s0/BN#. m!mk~ r6$ ~'2BLC0db*/fYC~ǞR ݝ4      y n˖-V^. y yZNUq1UӬYu %1AFaMK!IIg(12E)_ȓ"'GO"QJs:      =3Qx[ZR[J+LҸJ1e,6|#j!C( COC'H6OBy'y38@@@@@@2!#JHShq7KCziUK=pq#S,$duC.|D~]P~LD:(5t#1 [qc}Tz,saB]?*דfIq=UmPu܅CkS :@ #KViUS[)*%5+Wli⸙ثvmW8Pu)9W]u|T]RQ+K'o+4qɁGONQSזqخeU)*yY:\$Z_?sJ      17@~R}6Qh⥝?yfn?}O<ı&ڃxEW](me\ӾRk:v_q9&0_)qw M](o….(+U^.@Gb{;ǧ:/NxB`ڵJUSSӷaÆ'P1pGzsCCB#MUW!V'MBSH s}tep@Ӎ7N iΧ/yi|Si"xUIJjt=aJOu?G_|VH)ij!zF3폻xv*<1`Lob!Py*}bxuKG=CƋ im?)Z  Ʈ BB $`\E$,"-D/Az>02ƍ\(E/ `LeXFAodcYJ%wԠ :u{{ӈFw<wl fe@u\.ڿ).,C 0g!U /nn~.%H菾<&vXbp!WYi-Li\ĮI<}4;iѯ7w=R{!uHthpH`L.$^A$.-'Ď 6`݈ٲmUG@AuׯIӸ.P6}G-SVuݳ밮;hJ Ŏ1➚0?7ڹHS"@poE;`j0&g* f:wI}TdC"يi_=kc=,`ϯ_/Z-hIܗ7m ٻs}fIҕR8Zׯ{뻊߶/Bŋ]P,>Icrq繾U%];4`s#`a)O/_7% _)HT2kJMƌFӣ5wؾv<9ܦ#{s&o #,ISsА:/k%1n!xmg֎/iZwP!v`af^߲KRf r"It7I 6 zhB^EZƎۢ{ƽб8 fBiMm6HV0׬YSSE-[dL,nk]0r}k}59(`8@P6M]! ;'- UP0?#s228/%aWtt?aвK|h/L%BANljFtO 鷦K '3kb!xdޞZHbGW_msq%Ųi3_UE@%zʓ)Tʯڰ/,D憶}nci#Z]O4m/6휄e-ޠGY%0;ydvCyΜ9/mE `RAp0Ӱ~״@A A P?0p;H!OV(_ Ruww5tt}0== #[iwfCORϴ~{l))WkI9 +i<iBd>t˾)П| {zs˾AvbB?7l y4~K` m6oLo:7~# ~5 hB /M}bb:U0&;7&Cp8ָ-ND&$^Mz|l6"OhywɱiFc[i3aةp6g]t㍓s7!Z+Fcrn2+@c2[յ~e]q@)πS āȅ@G-Si\͛7 Kmfl ~"ԍ⿡o(3N;?tp"?ȅ\tO d9mnZc!MĎ1F"`ZC-C`H ;cQH/B3HYڎ3BAIC;̐+؜I;l 6( ]GRM1٫ ë~1F˚C|~ JCox  ui+;ӧ]oز6OE=gdw(u깞m===[%W."q瘌AisBz/B/E@}6|F @W8vQikCZ4<[!]("@zX&`wֽWzl/A~!x45]wT+ vA`/ gCψ*wgƗF(gf7Gݿ#N0v 0+d 0;-(edu<<#˗/)?Pmi"H́}N?#Y ?ῴy 9-5M2 )c|68ʆUߠ*;7DYynٽAȊ 󍭷^l '1|&>;1=cѢ(FR/e@rjYV !օd;l ye])m߶/䝏E6yu" 1ިׇ =LCO4p"19/|1M~na˧n6Ĺ@`B+_Ą qJi+bw$T?'~jm_~_8*UiJ Ccra2Wdl ^}gp & @Ncׯ/Ip¥%@_0>K_n-{|Ďw&ؑ!Ӽ%gsfx9DbMww7iv'9KcY{ ÃR2O}R*p?,>CҼe^ 'tx op"/05KS~DG1 +^6ź?"[0 śjw0&{ޱ=4uaE "Hi̐&!,*"!،p4 $B$HÃ[!Ŏ`lȃim#ܚBapx0&G&=!xO[Nܭj,eq<+ZgIUԯ<$*Yh-0CJYTI)' 6ĎRv/s0[WW"Je`-]]DWpdG0&d.6zCSDX*'ظrY5 qL_W?ٳOU-9Ԧ_Nu.O4!H/.fBcr&1Kc֒%*iT* `}Tʌ]BҨ5j XqMմuο3)|u%C r O QZY>0֢$*XP ]!x}D XpSrZV5%WyT w$crHi-U"a|09ԇ~HÅ*0Vai `LU3&Cp45]#L#p&1(Y̟9 Pb֯_qwЪM-}k[{z31hu|mA%@n –m3&19cTOƘ $ה/*jKY %6QP#Jj_j:­4a8Hu#l6v yvtxmw}ˆXnniv.""j R1GOַjha$dH~Mug*e Ɋ=Ai{w4v^&pAsՍϻ P:;/Xvݺi62 aߣ_ Al'."a* DcrN؊B< Eʪ>LJb)K!!#o8MW}8Gvw^į?qJ@0wZQSiUY:FMs%19WrEr_d׷t^!UbG#[HC_,ih;\v-3/`S笪_7d]0&A#:PB<h1P̏P-1$Bj6VYL;zzzCߥ%؞þ뇢i)?R6&jXR g0& n/Ę dʎh:-^d HVЪ%mm3 A @Z a%@& )䡃۷K>qFQ0n\8>")Ʈ.J$S`L.fN3$NLtqĩp `L.VN<&%M7@yYTqi[[#Go)M=YCRrߎM?NnчS:!2* y՚EQV4ům]=㢝!x82*&@,# U-G2xyy`Q*=M<,%9׬Fb]ޔRN@wl_qåAlwdgy[nN<כ "+p{yHܰ|E+;yxNhd&w-!ccz>.vhiX&4S|+:Wα,Ɯ!x8;|j SdN>!N|@K^I9K-[*߳VlF" uSUcwkk[-drbLP.nn~)& @wI0zA ޾~px<jn?^bR^ڀO>[ MݫoiP-46w!VPA1&;rbLPd莰U+fH UaY@ s[w?zSv$2):^D;+[ Y$G, P0-,kɒY%ǘ\l-Z;&Cp}/n:;,@p3J߶dI•A a|%8Sjl妛ѿXrM5Wtt7,BW_>Tb `LoѲ|djyMU ͆w0F5-c2OM3!; 0*FE'&Pȑawx'wǑE|Ǔ‡ ~z9K*@eoB٩Ƴ:Vz術&!>:5HL{!v% F5^ruM7ݹxfC l,*j1y1KRVγE PHgVʛ.\^znڅ F;Bލ7 JCE/"Y]~с[>1[>y`˄"a+:Vb8!6tu]^ uge-4 VdbLNGzF㦘V3U|+./ gVQSgU@q4q/uڿo8!˾^P9 =eҼ;|ɋ8vlz-< zByꂽO1B"|g fKFpᚊTSt;H} 2v}h %$v=IPsS }glJ%d1ݦƘ|7,zT֮P6GVؐ|C E]._`"~uo)v}~S^IK>ڼP;6߱kÆ>u8=~Sk9ٙFSp,*j0&0&'Cȵ,gP7g\k`O/N }Ueuk׮e@˽Éiӎ(oٿs9_e6.BVQD[в)' bZ޵d! xJS6xwX^Biϴ̂) 0B@ 7Ax!ŷvmxkS_ Ϙv8Q> ^nS'r3wnY0Z{zvBq"hGKq+޷l)ov=ּjbq>3qL)}rm3g[`I#)R>$FEH qJF =ҵ_c >mNgΜC$Qmu6gG^ͧmp/8E@NF>1z`צMre5_@1w~Ip`ǦgۺʬI}&LC {cZ쇎dLCcbؒRޣUoZ'QU[-&O,**iB`ӘBp*OI@aKXŢb`hH20 ] RSbd۬2y3yoQUY)*I E%ʾ=R}=~TS;'N$؎bj+j8΁מh]zn,IDvwҸnĕ #)M&\~S$]Z^/M$ ۞}7G[`L>oޔҘ\rq(e+x+KE<16hg y],&U)3?{y_sbڔ)9s/Y0_̙=\x9v|6'~FS6 .\(̝'.98U.?7 C*X!i[L;sKoVؗ9ər;eSWxW,6f_m躏d{ڕLi{HyByY <9vɾjTcJeLi^kjR)3MJ^%f$In*=' wxWKWCֹd"[,yܲk$BpIðv6 &nׁ~Q̍2{b8 ,kUo La2IdTbm7L&1#qj[֖d#Ώ=I4;v=;l{q]FөmRU\~{Y#wr'HihRծjS>jqL)18?tCCBd"G>TΜ9cMU'QU$TħpZ<ɷ=@8'?&Md ,C$$}[K:w;m,wK[Xa/~MMRJm"G4) oA^佑pJT>Nؒo,#ЕK~lli<{U gPMC>oϞ ݹY,Us;90ukY .< ̹cL.8b' (1wT!U)_O"faǓnnO~Zpl>.7(L- %ok;5Lo1=G ĵUu[Du"ɮιb!xd5h9){d|֤}xBk|9Ç_x޿fm79&B[E3g"3ϐxDDgZ?)/ilLҧO EU?kF["o$"H}_oa dbo?ԏzzĩ?!19zn[c2dГ2y q 3q8y2*N:e,//D({XpjaZgfdxMb QLql*L!)ż-T({y[G%yq#wKͽ4оs(f0'ﲧ 3*3#N%yš0&{A=2yLKhܚjOvkr ɷRRn۞Z5@+iRSbX+EEYdtPԼڪ7<#FK~ɛ`!7sgxy}P9υwߗx[JY8UY-=ccM0_U!qB0p)}^Dz|CTL/ Y52n(1Gnw! >GrۻF"aⵜ&4ɧNxFm;Λ'I#ܢPD11H^ZizNvu{9N5l,"ʉKeEn(&5cd>,np1ܳ1DG"!ynyaImkf"A,i ^k+^{'^96frX psű\b gpzL(SKfIN ]i!x8G@Q >=AkAHfrowBF5r FN4“G[˄]L&-"s'˜$Uc2 Β%Sz O<'O6+++8U4"$mjD- U< :?h2Jϣ'\RkrMa>x y;Dhb![MYLPGMm+ Jc 5OXDO䙛-"ņ ɠhǒD5[@9"q5}?Mcv\g |hmK\N1Z.F}o q qYuvYo~裥˫En6W2[>o懜h_ߥ49}U @1t.ز%__cre&} MhoTl7ZF,EɵDUU5y'vl m#5__1Z.b y(2(MǚOTR}0O< ! '`k 6|*#oa]GW2X0K!\0T#A"Q ZNǹؓu;pA9oqX`ok^8chD LNoUAmxKuU1}:-u9#^;u_gS".$ x 3q),$!@IDATRW II *xp@Ft#^(1@0&ϧBah;zh]N$'ՓDZloQ|s)M M§RyX4=@52FGuK#DťhQ[sß)Sɓ&[ڬ0[G+n7F6Uok `[O,NcyRQϲIFZvxhqn|4&D4wո\ ,U;s)X~9 x)')m):{rɱNҸgP**4L# :tBH uE:DT -h=v ɝ`3ƚDvC 1@uXao6iޙTܶگ应QY){n(E:j C6J[rD- 'H8,RۄϏ~?9B|YH~ҥz󤬣1ky(h]zn,$>#&Ai- A~ϭn, Nq"-W>r'ֽWl{CyRJ^ךd'e Qiņ$<ϛxWK -^Guuu L= x8=X5yr)Y[K>o?%L% [8ۚ YiF/9RG<<[Hz \m&60/E!ƶ'$tlRױ`aUtc9@,jIz\ 'Ś@@ ʻ@>ė/7.m\I7UȄ=ᴟyǂ?N셠ĜFTUYA ^ bd?2 ƶA4()9- 0H N"H|يq$z3ma$XSX >*Mz$c-6Ѷp~`gVxW>O"Gb$ H]I<6HUaYs]D']w&fw>h*˛6m&w  0X⦛?s DGC)?j12)^.e =?E5 eaEmRCH 73y2ov =$ g${GlVLFے^`QhhhP9'Yˆ{+b7kEaE5?I9$ !p:nQuI߾̩߷0&`f Ȕ"Urխ1V' Ul__8JU$|TTHd^'csmTHÕ^}<;l%>M^?QhnΈA;0d{)zqm*n1և|rש鑠 _ l.*@]/AA<^к];CdP9p#]ʑw A6x+= x+%PʂB?CJ'#vG~6Aga>âBTGeymNhmY{ GkQ4*hmTF mƜϒ< zjG+-h&uHX>6M_h|n%ס[nh ϻ YظqpV7$q mݚbz5ags'ik2OH0ᐵw)8 !zBH2H2&gqARD[̰mDa&6e)M`cgqc8(g*ijX*r%@qAAi,$`7Y6m_qyJJ([-9,~5QjK 3 "@זHq{H ᛭4`1x(WŌ  ô:s\w$]1,'XZ' Ća< ͛_.\@ KMR<;@Eh;!vi-ϩx[SO1.tj9# M&bFcU[ٳK[|Z1uݺ&ws2(/8+ &/k>y 5y\ymz/>nz$$>x=V8h087Z=srw@DU:,?طm A6@knqr4  PJBԆ??(cx1Pe _hhۇiݹVK@Jv#@|]P %P֎ۄ $ Xc 9(%C gn *,ga+LHZpyZX4|*Y_ԃhhV3O/ ŁCJ;(J]'=g\TTVjQ]U-*++DYYDAb ɉ!6"㜾$|&hTDbhp@ E=]9MQH6$-ʨߕGDyYGep!< ^v9Q=D8s=}FAڟbm '5z[bX-5M=ѝ}{)OF  P`7wQs{u{wlEB "Ptǒ%MLbOxZ1eJ:u&p6>#ߏ;8 irInDD5u"jb !,z{gND4*񘕇=LúInhQIF5I5$ңfDv+lag;st:%`.':xb1{'ĉBb:RQ4-q߲e=sy] H#q#k iZ@@  arRD P4MдWU%|_$'r34 #<\I-(~aO69.](̟'w I_s+ܖ: ,TW׈_LBG<>N[1q<@( *s0:w̸\LN-N1a%*\` 2䁴`,%^ray7.\ÇGE2o/=s#ͮ;u0 @&@z@Y#;0kn7x-v>BAϟ+.R0$&cy{m_s/?xѓz/x$؜2yf,r T.03fSEhb>ɋêP،&N˖g^<Ev2U0.<&˪!D?Bg |`JZhKϦM  PdyY "kWTr!hmfHK'V0r,Z:kJ/͹4Jݓʃ';ǒ*q%m1+^/P htЪ׿ RE^&5vI^vmk}юm'>x뗋 __Ig^֘= Pfnzzz{ھ(,\6ؿ} ֱF>y͚i{7m:`7+x44,6UU4U/܊IhM>Ju"Ks Eҗ.L;cm3K/cGZ@=_0q s_̈H*fͲڥ=4s>#œ|Jk/IҖ>gk~ mρIi!@Q6׊@F)x,jhh)/;x򤪚X+ĕ[$hO:|F"`ODM =%_ x i%A}B砏$x|Yp:VVVb3x"Gnaiwg_ⷴ%ޯLϪ֙G}Dn]neY^em@N~^e-<ސ$DCWܧB&DvhN˖\'.f1̞xwdߞsyyz/y}ұ0`?繾=6jiyL:JӨq-l\,yK%-&QءC֮Bn`6iUGZPv6Z"0}`JZ)#-%$(jY߲ӧ'x,m[QU6/W_s(X Nط*~p܊ \h=8k'O_y"z{{E4"q?^3<^@ t[GAG#7Nc $nfΘ):;:/w&ª+;?V('{ ]|(Lx'$n̘1l-U4a1 }be Z%A{Ƚ%.PGKS),:'Hۮ:}K6w!-'S\smz8+Z#cѢEU4W : <:uh^`4]wrz겏2"Zn>q?vڍ/v;G.B=A5<9.*3<9&O$*:6X7H$ kζVwAqQ]00Te߽7ϸQ.HOfeB5ֹ*RtcdAP:xF :˪0hRae k LP"{9*ھćyϵ~zq脺*yh֮1?"(q6ɘ|9Ҙ} 1'zPSﹾnvziڪЗ[׭}q"E/ $@1ӣ{:ꅺdFׂҦVL~~zϬ6ΤIʦFQYQE^J0YL@ )ۗXDBtWH>,7F*<N3;vmk&zXc&?beǯ޾8wP{zr<{~‹-}  >iX[Cp=J 6۲e-BwTT'eꊆ뭥;<7ۜ#NXH$c>?x"HDථ,+iL%Rލ#C0%MMQ&8@k4D}!"kWtS R3g]|GQ R-[B;jLGPI{"`ndzZڔB Sx4Foih(wLq.o"is޹g}N"-I  Y!c`)x,8񙐢u1“/T̟;bQsf^uk^@i]D? \Ec*~O*˻x`(F9in-8@Jojn^W7Y\sXPbT2%W\&̙(cLmE 4wS%Tx*A@ (QۊU7 |EPJX^/ʔ{A*_s2dJŋE9qsDEwISae)vG ;(7n@۪iDca$c\&!M*Z=܋.Ι꯶5`o)e]Ԇ5;!@ ՚}b҄fRkWu/0._K:>zU͟?/J q,gl*++h.Ϭ]q]*~!X")()A@,~|oHm-EpA@TcKL+gT1mT9kEQ;gH[)G`,2 VyơΙC'O"&O3U8 TU%KV9rF~?|y wT5i5ٻwIA Xl,fYv Y~YEBA@Hl’QU= جٳev*M c$ F󽣝`'vX-r침-Ğ}t||?t>8:y|`CV>$z\۳7՟6 ks|wy{['bTulGQ~I'x2>ߣ_Rec݆BتG?|mc\>pvۀm@_G+ ?g g:cni{#ύ<,`f͚Ig>8;XLXLSs>·X:AHCF,eih:M+":#(Kむ 0[l1^]'/EQt!5c5٧? ,+9uFwDШ'C:x:tvzt&s:kTh]Œ-0V}bj˺:޾b9\@Cyk b1!* FɌ"(08&X][* |o-"K|*4 a_GΩV$U᷶~Ǵxסs^􌞳I}99t;3 QbfS%?|'zfo;/ƫ/F`WRcrU_\yo߶۝T }W#z~*|zӦM}P>FtOXLQ[bLPYIeeJPzEXB#$P$p$B>|N 6Zka"-4dDr\;U^??I]=VM`:_ÝD2a#!r^!0;QQ6qsd4%T@Rp?@JiҜY}eE9aެLZ&˯ՀqH?AJ3KW{o~SA@/@ό?˟duoٖR_@FE+}F^&J卒̙3h rTTTP0vZд;#: ?,wH6]w_^^F&Nw|o2+bz\ϓdyէyMGyq_C7"_u" 2wh˅^ 2f/*2 @d}}zժ9x`X\yg䔗Q 01ťo|/G).\GGF~ي7HtQ"PhsFv$r,܉/;rUyk*|՞ug@bp8>Gn 1q |laZ {_b \if& Y >V<~Rjm/4'|/K/ ^ x ZOH ˞UտRt+dXodS[]0!QUE%[Q\?B+B5$) 7oȒp8L!NhVrS$05 ~L`EKvsIx<1RpQ28Ċ?;wU/sC;RGN- GjwX5Z*tq..Wt ⃭.C"+I $4QHH "ߏĖqR^V۵Fw/Kp%%h"~; %%E|KP Wה¨w4SϜpw^3 d7owZ V,]{v%VNA@p3!<._eD6;;EAr0u6)U%S\<n3Pě4= H+XGDJ/ ~';!=k| pN=MkdM܆iy+s|޷ ñVI]VqG#Z#{zdm81l1λ9(K[~۬1rEQgq}V׸c:p aPc"=#W@bcK옱([7]_H`vg~v?e[l#i'zpT9<_hBx>" Xe~?CN΃%ErKm-/%\~vm)SbxeGʌiD~ %AvFMs=s2Ioy۵*z|gB#):ny. o9FW|KSpn@䀐v }9`t4povAp]pKZ989]tc<((*5UJ -(규x~Ǝ>Vر@((`8 ăE}2ucEX}񸰮<`?ۀCLjzVT*1AMZoЄdjQ9:hZ"ѱZDCCXc=J㳉%K bk @!GK.s+˅\D.|ڡ]7R x DZnEy*H+$@ E*!̚C\R|>yggy!vhQ B PA5s/Gigz`I8ӣ%S~TegxX<ӡC{j`:c6AfN'_i|:D4jAc lJeL{єGߡ 'jHW~%=[+i{{f7 'cO cȌ b@v 1H_!묝 RYS[ m EU=7%J}YVe@"~ŒC;?Asܮ>^=dݦǎeA qJxO_{@􇞖JD̉!kӹZ+,S_K"$+Vv[8eVSn!,a` T0KGtP*;}/?s}n?ڋoOa.2Rec$0r,C`˖-5 _X)tq)`(&u6u}abwIUwטSySGv7ʟ*?8vfR]!F{㨆P %(G*M*Ww@xda0GMRty:Dʕ2;}하QĘ-0TjCji`\"t^tL9"cz5K^eEQ|0gǎLjPK̪io+X~3wm?niA@Dѷy +}$2Q3RrQp~ۡTN@1 h,} ,.]; $դxf: Owm_H4'| vHHx9_#CPTLaﬦHOqSCR.[~B;$ cŘNψԚJ;bpIpH|V<0s@;A`Œʊ'{ȸg|kj-g_sN"]8yA@};N#>k$ұIw<6[ xZUYF+c X9'#y9 Bek"J4r&ro򫞆)N3S1eE#G6OcD+ƌuÕx %KcEܜ kX8ʃ+ҊI;2)*ƙQʁh%~`-T_,h[M@jމ}F/yE 9HE2c =)͛7Ђ*.DqcC*bƔ>oP®D1 Sv]$ YY-)Έ6}^\MX}m)V6&V׊yGuF~ǵe9QI=3"9jŊU>jD_8\&?oPpTŃ=dvU gDVNFѤlҵcǎJy,5خp5au7<9ZӽˍղT?)_nEwBλLYrs \'$n†E* @\8!N9>50R|W7m7;٧8oS_`ʛP *i!%=8 ?EE#Q/5|At¤U7dtT; K7 ;Fd'@v`줚{evlM2&P-nɊ_xO%I4#'A g^U3oܰb&,(Er3f`6~{niqT0ޚ3;b ZhҤ!1䃀;Rkr=L}DIcjH !#ۘ kCnj-c:Xy:d4]xLب,S>a̠61<V"K qc@:-ǫ2«0i|qt`6@3>ŏTۭ-N`7fXk-qA:۳dDk>WXXIG+/[V7) @"pp3KW?_O]Go vw" @q>>_zpKW.{rX_H-SA⤉ˎ_ʠH@A>DvS<`=PYnh!u]igن ֥:99nQ6lC؈m6c۱ ƁMM"Pϩ<r>Ql%O3aD[n{f:K,V_='Ȏ{Wvmnk) Pq@@]^)@JǼŋɯ=Xiq:=0eN 9Ez ׊`2Ma0!u}:P;K"#pЉuKawM1cTY<:Kȃ3[1>}ON#`4, nGO?ȂϬڲŹi, 9@JDŽҕOUƒxхAx׾^Ffh`" n)-:ncIM9Q1cի0NL,Yي7jJt^w7"A@;9̜;1;HI )Y =\|>_@=pV9`xjDODq|9rk`IVnɜw";ZэcK?Kn֬vR==Hc2Iω?JVgFWOcPy@Q<%7#gVkA/KMG ibUX`W;lha_Z[gPw+1e<7hdLj<+ z%?+e<37HA-T1cyf<"m#/_D#lxG+md@KQQ\.bEl}2s@҄GqUp.uƒMNGN򰇞Mz4]EZWf&;ϧGQD/śud]o]F[zPq_ ;o?g<É*)35'&%_'D+z ضm[V{}"i(]OrEN GHU$cQ==t3b+ɤGjvh ޜ YbWpQ䷜} B}g9^YWֹbdl14X1t;l[(_zXЋh)E@̴Ci-G" 0>~j,;{H 856$vl^ 5D { zKg 4cξc!TN/F`06/CRϳQGth;skk/>\T,'?+&Gc ܟݼy3=JA ;޳^N fƍSKnW x) ;wP !؁4}r"Êtj!]ǴPdivrpY.qvu\C uT@w^ͤL`q!qŶcB&>YM t\aB3PKKsw~3鑏-YgA4'4vHڼ1(*xtV>E"A@#{a -MMtqjStVBSvN]A| z?;&c+pdד[ƎTpO1.Unb%HZKAM0Ӵ,yDLLuoFWI/"rQN׀hS󃰚?3|ZϹ@UMyK0=Ny~a UR׍9qe:-2&q dG[[[Nm\\18|4UÈ|*~=Ax٤[ʨt{^Ud!#<u~.%,#ou[[%K-"̕%>L,ZFͧPGtR3MNM+pA|(H,bA$/Qqc%)ml w ȃO\IBx8:И蜼T/KdrEyp=@xo&ᑫcQI&܇( ~ ?g/kMyD\Fh{m2@8 lC,r=kːmq²]'W @4ldGf:tˇz>Ͳ?ۉ庾p@e>bӦu9A 3̜VjC+~tOLL[;A $k>lBIp8Dupb(EX>;,I;`0t:~:R_o!).&!X<{ϟDZ*d%LbA>=F(mTj+ȴ a-ƴ-Qu#i!A=$LxH釧pt@ΤG{|G{~B~ҕ"B kٳg>j ဢ}pP8Rwv~Νp$Z-e?BH K ~84*{ T ^PJ~k{칳cvE#?ה@j8g[{ǏS:$kW~(>tVA@UƘf-56|7e{A@cm' 0i" $wܼܤz+1Uu^6az1:PWGSW֝v.B?P?0+Rw:1E4:sv5;y7 2Zv]Ώ{ٹˮY1 lPEې#Z;#\aYvN'L@*+I_H1 ?W> Eaٟ{NMf#,ݸqҗK4]Cg?lxi,MMMHJ===JdX'PQQAϬXA%%qr:8%Ur^5*qYYK(,aa bqx0iv}]6|㎯K*Ba.]K 0 _zn8u=gϮظq;]+&9r"m5L/L"GNH׉V㊂ww]aa;.]#@gΠo쨱g&/BuPCc#ll]ŮcǞ-Bf1;;@ HE BJ,DTQi AtԱI%^ {q t4Ht;zVTCɚDQ޷΍v-[kaٿ=u h!ߟXFMrZD-[Cj*͛7۶m\&c+  a*Uݐ Gŋt51m*{=4ed* u S&nܶ7 9AOƹɎ+$DّhN zǭ1sjvˆSwo/u! +UКRVVJUU'PEynMlnc{tUjC~&z|hlžHPE] s_!1>.^xJcW. @os H^jQp],oh8{ͱ6!A@p %]փjXU~-,%Nlr_vUUU4w,1c +J߰3!b;Thq8MrpdH[vX+*iŲt&-vxj"k̜={ClƝc?j&Aڨӑ.\A*Ǵi2ȸjDpDHHc+H7ku:_ʕoxmocAqx׮|lzf҃{;k]'$y|-ʺ~~*~!bJ<9F1M~Ӯ>|*-mRsS1^p3a8\eHS]Hϛo[~/iYa:ϠykEn߾er$(>|5.nGо>j… tAaekjL Ϋ1A¹",Rc҃{\֞N3 lDs6MaX6L.x=%k6DMzxv럃,/AAB}f<*taMkd>~&ˀ¶gwo/,‘~y h *p'i[n/v^9?~j&T[29ކ00;هm-UUe2SO֯YC'K~A =qO]]kFa&fbQ'2J7r*N8gUf#`*H4?V|%u~„ a2ӅI |p;JV7"bǗmrKՓT_+」Kuq{BVtywi>tÆG܉RA@3H/l7/mfEyAA*v]Ň%\֖V #I:egü*d -N+dK.VLHN܇w"}v+g;N '} %`;gA]vfHd-0N-,@PW׸,@҄b1zhd>~=X8c4~sBL,<Ҍ-'T@t@wC&XWٰna-9$8nK>n0GݓDǮ#ړ̽=L` 7Wm=d+@8r6J }OZ uU9$S&ѽ̥iSb K=%S(43ŋ)kX4A:Liapz̓<gIi)FC#amR,| KG#%wljQLդ9Ң F8XrÆ;w^`ҕ +a A R #rZ7αVZYv1y9z&_/щCTI9uȲo+wޥ. 2YlsprٵWþiܹٶRrCUZ1Bt:s ӭD̯I&E T_IO+/D[A@H:^[{S1Tf'Aa.fͤXcV44}㈈_GqRkjEMxbxЋu9|]tqɍ)'3˗[6={=/')Ud(q,nZWq%tDքnpMex^|C {_Ƶh+"#Գ)%w~ee%=cg0a5%[/Z$Lox= 眧 95 Lr ư.ӑ#G-Z">1aZV\A0lB$2I#EtdV&)X9UR 1dZTޜtæ5#@*aqCA@@ UNv)vIlXfϚesBL&r5ڱg:>/;22DH#bnmuܹk]}+g`mcaYXxc>'%A?4I*p={~q̟:n&StL#} 0krNH S{041:o AZd =%f g?aǞ?~ߺC^8O}PK{x cJt?1|qq㭍g:Gv1zr-rVO:6u=3"UVw*bZ*(_XUHB7Yoy(+)!*-+KV{ߔlفIW,[UX KPSm S$npM}}}ImF};c5Kc4ڵ(16xqNƛ@XwBuUajIyY9-@ #J@q0X`"/Gb8ELp[{qRw0uwXdh:9w#(>2ȥ[ [,K(e.)Woq6КtvMӾgZڗFA@ώWM  Lx nhgL 6ٱTS]=;Lp-DhܾMGp4\ㄝC>)|οW 3c%> &~xG s**ibM USr1dX5%/cjJ:n!=ͅAN#Y sHX'h۶m%kֿ n?t :h[t, 0um&YQ.   fٹ),GkE# ;eeFMo2um&FWG"%"}|W4C 3ִa ه~m..m&UkW?IDA@[Oyk.Q\=7˴,Y2n9=CCVTyw8Pyy'n&XV9mv,[9a#nKnϝIܗEUf+Rh[?ʤ 2٧S})ZS& k*7@*8Fx__߄g֒9/;܁@̝ASW^NX3Y./-b֌)d-̙& 6t +8e'gYR܋GH]̤r)Gz-%Vn07Hむ o^a. zXG:Z#N鄪 t61M#s5k '|򲬓l,g܄I**&y'A2EB:;ٱ/ۜ^R#<@{i,i (]  0'Pa8JxkjFLkAϑ-ya,0`i# lKd1=y;j݉/h<Uljx~DC_J_dx|/{A@}'<2ՌGx$I==Ni`ف~hi#=?(6ejFt iS"7\IzhXJa̙0l4f_w/l)CbAPq:ku_IVy /'{'@ k! @n"NHmu(Oimm;AZX5eE;EOX &I쨋EO".D,78i魖MKGQQ1Z<վDiרjtɦxx=XGp` zr*F n7D-- <0 l:U.ɲ/ Ƞo޽# P{w#p/qiXqB$<͙=rXuUShҤIxB׮][ىa:|Pb;@ URRDUUXe:M:JK,\-[QQ!>);@J5jkk>'id©`xJQEy9M<dt.?m}FÏ.Iw&A@`奫óh "@ =WO`Bڠg{ˤBӭft275"2'L'Y1O (\> p@nRIq1b ;vԂU1c>?o>Qwo/0T> " ? ʁ[QQ /dV,> Ԍ$Ybq>߿4B9ǚD3ӫ˷ƞr7wrnmwI}i0^={(1,l iCQ+&8mH 9Nx} ,Bxol trqu&&X?# cHg-^n}]4 ppC]LŲ_XeKZ#Hݢ!CO*H@ V 30tȖ]`ŋTw^ػ%-r#;x\pQ 8OӧTD+@."HǫK,3">REXpϬx~taiP<@F𛙚6IKdQh&9.]D{RKkJG Ⲯy,ᱱwk12# 4"!NV*} sR) d_~9Lqxҡ f]ױW"ͦkz3ׅp2'Mt`zklljȎ̙ ម?8x1!P-! @H=isMTAyTEu:(xk1I}]ɴ^&=B?6ؽZr0yΑZ9lk9۞P<6w"`Ew&=d|۝T [] y=ovPuQYF I(o*rȡK귴ܦnSee͜>f̘N X.1b 5;:F~*a|#l~hi@ eA]}fE$;5&<<ɻ6A@RB@5W 2r }2Ix;Xr@Eм^k`;NgΞ;{6͚9 #$+W+U>{x^W;‘P縪yގ;ZE)/_oyMvWIh>j {dэ =.+xc0LӠx a՚ D`3qlk{xuw S oޯorD"Wa m @+' $'<>nSe4`&∎Ӧӌӱ莞ɮ4"9c͘6Mv҈sVTF̭p?i7<4o[XqVvHw,ɻ6A@RD{.du0/r `{%wL1~}A> =\Cx{-Lm L\ ŮRC0]~})٦ vz'TWߟiGl UBxdkBGT*t1|4|+#wWk/JZxQ#\?D?ފq/B"5w߲WC{t|fUzNSW=7ưGǙwNJ#PrI[wG@wƍWr<, D8%ҌHyxx)z0M}y9v?@eYX]GTTV GVL!g'c:w\ f#G'-[D&}5qO2I˷ mOt|O-Ãw#XC@6-3uhmAZ&J~{4?_ ^2Z=9<RAؑ<޻6/Di7jIѴ}ݿe bA޹}f'< iz?AxtS!^O꬐1r^k:Uy&2wʕdWloouB`Ƿ SLJ^!6g8:M7Py;>رՑyݹᯕ'QyO͆'uF^9Ol[wO*߇;g;2TLv-8oDF7 .݈i-wC(IH G/KrBxzʡ7߭;ww.7Q0/]QetI?^c*ݶmgz^3;,1jc<=@%umI)c3$g@Zu0T^i_tZv}jTΓT {} QrZ y&{ǼL4! @dE:N*V(*ĕmrdG10SpTZ>cK^??tx/ӸוrQ(Ξci>w;ȃti^ɍ2{"d7Qegy 3y#7*r29U}>i aLv0[ P|b>!9۽3~ԴO9);rqaÆM>M|?g cNްT?==22fPpU QR@ @A)@t,m~fWd_LMZK/PTNYi+{ }0-^Ϟ߳DwApa+7-+ւkV=Lgk+ fəW= LnI>WܣztR^qp <=b~&qĉ2Wّ&~Y)8=~#i)_RAҵHj,GF,]5ʗlp|ǼLx͇!=BM^˓У")KŦIy&a[ȇg#?0mkߋho&+KWℕoqwK}n''P$%eo=rB!Q5/ƣ9wGNz8ƒ^x FwtOc,n\Iȶ۶na-q 8@r|x&{hLPYFFaeZ>8^$>Cze/ɟ+ &#üdano^:h-d{_s.}Cщv| 9]4=ᗜmO˯YdLF\3=!n3aᙜ3L3Cx)*;Ȧr|}^7+ؤIp>~ G~w}y\o6!~8nӜ3*7l8b!>g(O#[#6bg졤ѱkxI`?fz8(M]A:gq4]\`6:U \l?cI'e,\ P ϝTd`/pi:-Lo~!Caog H{ ŸW0KKK;V~mr>9P2xƿk~$Ts^rk@joQiގR)q1O f`χpXB%AccP8d{@DB'o \`=g d<؟ldy9?S]SxFÅ=]l&Nh?h1H~I /3^"\m(q@:.ea|B qHhꀢH e̓t ,ƍVz'7St鑶 lիtux4^J,zDLiP{%€#fE(̦+>9 Bm-}YI5şXW~.qxg}Ձۗ<&$4ƾn8 !*:=lYq!/FFGhH6f}L$Rh,SI5motԘžGNDqq5s? KOoP"s#@&i)/tL- 6|K|pc#"pb E7>o_go6@1S BJ阧jp:s4pa5v} m `46'Dʟ9)_5Ob|{r7Lf &nYxI꽭} |߾b^Ο y9elc&4Ãq ̌Z |>v38|*4p?рY4#5=G8şlE$lЈ5lD|,$mCd<|zQGlǶqlo?WG6.qy,*NG^^eB(uD-Ա q{r7Thh'~RD(-E窡/'vz[#gC^ O1fyPWE̟Y85/ Mk[}@SW}r<>0-TKo'ذ $?ō+Saƒǁh ~֑bFDdk>]bc{?'q/ynDYbGL9ܿK{ih׍4)^J-uձ<؃@Mf<'ˡڒk,/OyEY~vP;Y eQ;}/:XӢh*Ε򫜖 y5iVeF 0޳3Cd0.5B քzZ(=]6yh{xj 9q;NgOJ'O1sJSX{{HفAƸK!/77Xu#ryHF֖/YQiR5sMNOY7McYcC$TQD$M }rK-6t؏LKo)ѫҵBэ˓ou0J{  #! 6K_tMa97Psn9m muuX"2)nTyNC6[nb 'kJF5fڵh$T 'Z~oъ54L^C&ŻZSU¹%\UGsK<}rF}yܳgϰ!A!aL其 8h P V+RGm>:|=5eܵJ YVR2ZG5  @_ $$<`3夦T?o0y•k~J#OA 55~_&#p^=[!ߵږ+tN=G/J@F|1Qj>mVMEԓAJuvSW9]/WRA/Xޝm>22Ԫ.LOjN. W|?lߦ3]St(/|B}hѪ5[v|\XRE+K~B_<d0{%t%P_e؜$>IBONg^/K;AkMs/[Ӣ=ͽc{/ MuQ0::*̙Eo Da[UU144h{OUao@xh+#gƧwf +,YX[QHouS2z K&O|ںHhUXWS;?H~oʒi:OyTQ-O  cr%U*z4T1Ia}TGipkaUڢOFj\P|UQ+b yGGGkM(Up$vCc!?0 '9Rnwny-ؤ `ՓbxTO`Gs%ӳ<dwh*(?2iƛYsG֭)AeSᩌѠo#ф/B{)rF*򴪈4iMUzv/ Pr#bSuwPYb!fw[|JK69d)`栤,YybZSNYxV4GA⦷:䩓׼&[YJMR<͔8}_=1S@¥To {ic^@{?lRTnB%7_LFd@'3m>0F?s~,}p{꙳[}Լ8MB_? OSR m{}Mb}vl~ƂN6ӧO-ʺq[ssFo>i;4ֆg;:ZJk -Ue{ 'rPbu),++ +B>lH_"Xiav }dt4x$r_t **7A 9Nm<08(hJ Be*Rۭ A(rg9H9Wx:ӬQ~=[8޿lҩC5^h8|+OvhKOcF2 :pzÑ8q$S+rvC;=N5%_sIg4ӂ5T>wHC) @Ƃ"@L~s~ xv_)JǟON_NOc;@_bPđB?7w dqBtS[Q4<17+Q_Vv6{M1]t; 韡=@[JxN7VW%9 \7 nR͉>)رQE743gϊ^Lk۹O=vzHM{ЪFF9['{9'@;vꔻ<8_cccHJ6pp,j4MMJBOH@ce6쿜hK'Eʕt*-- ѯґyF i_̳Z:P"!Om 0x+Oko5aE-7* 68tݶ c5,O kKo>O4Ŭ;)cfMcgX?qL(@h|U%;^d͝JEѼŪ-s?zJ.شi(M{" ^4䂽\aHC?L3,.\n7WL!̰l768y"<+<:^ؽ[6,SӅ|5ּDwދv6f`%yjW <@tPub5j ;ܸߧ>Hr"W Яƙ"#X6^ez}.6$dhd@;뭖 != YMKҠ~kY*f*TΌ a% +W@o_U=ANn Cߧ*sS;hׯ@ / }ˀsD|&d--pe6q>KowU*N@>O<"ځQװ)3o6PkiE7ӕ=H>#AL !bZf$BheMYbmudLyKR*qqQG*7y>bCT#[Jؒf|{% ZY:&S(j[P/Ό '+v ՄE^^'u)E-NRͮ,2`*]B`6E^N%7Ur2saP+}2 i4`e={D]Ci.^2$|JmmmmmMehZ[U?4}kSﮮXc㦕M@  )Z)顬+ۼUMVQ[ ż7hn&39cEdO#U#_y4eIdjj:%y?2N3XCC`O^mUV3䩓&(4nmpszv*]џhRNG[M%@P4n1Ҕ7r]3>nKWy7C{9@,8^)\4֒{}r&i;[V6do`o)mD*DFtԃ&Rix=8H5bU6hv6 f̫uvxͳwd{mut^h>XjAGip$ ir1uHnH:ONev4UT ̛צ2" ~Dxs7ӓW_ !Q%C'pCx3 _{,bQn׮S*v'>pEѸYZ$ZRSJawz3.T>Gy~5/{ᣣ(]]g4k4%1[f!sixv6qo{|` (RujWe1̗GBSS 5C6mS^tJ;niPMP~d|Ѥ2 #';]!9'`37 #hRsJi^4z0zQ(slf\l̖yq K}?r}[]uR*^25q;̋--'ca pj.|~ڵk绩9/kTTEg꯼MLِF7U̳J2'@qbP !Y蓓%9'p1M <:*v)6c;l/i }qbci'Feѱ}122b3<'aJkD5wSiSfʉ񷌢PRK2Otvv"y@HKZnc]fuP~]Q*S:L9,W%]pAEBzkJ(<Qd d<\Zj+2-g7C<ၯ^5ٴ؀~ cKVX]GD$f`&1oHD]GL.b7sw0kjR::Z,Eゑ54KU/hxEۼwbKꊻϹv%@bRL)r.IKDvNPL\d<oϤSva5>w>//JZ3MٙFF3|2&^֑Gh&z;j?bEEk[ys`̈́[&?^')|HUfۇ)/74TV'14e4)rW֭i%(R󔍷s)T*P8 @z3N/3Њ@.^xmT,VhLMGeJ}Ś U#)0H>l?ehhHٻO{i3gxՕWZ̟?_̚1S|ۗH?/0:X_Z}6řSv<׸&炔֟a4m;IBx$CIEn(<YEc xR 7?eK'cNpfuG2txz|l^yʒ('Ck}2 祶74yU5(#eҰzC{>Am5[n_dx|1zĐ`=xc-zCy._>_\~ebܹbYb]9Xxc#A#4gٳ}t/cޜ66%zgL۳(c=--,!Z;s'~>uA o6YrͳTyn^=E}8xڵe)"R (4[Q0CgΦ Y@ 4!U `( C<5zO#VN-q[mUK|Rd)=G6ۂ[mh,Sd.6ČaqIq [JQH̜Q,f㒹2 |0Dx:  b`H}rD%AzQ DQ6 HQqA"i~2FSc3xO I]AYaaд #l01   ?{!8lcc]V_KxL43[Y:]yL6w?K.sy#-7VUl%GUw5d ǂlc=OAIrYO>آ%O=]ǹ@\C GrmV֟vi 89|ħ^y5 x[$v=A!ĠmpgV,eɋW١{o,dȈɌa%9e38IZ_ZkqacxLֆuN"PZZc!r! ^T:m !yy'ȐU>uE/'1zAlοڡFE@QC΄#}O}rUuF 6A;$ŐȰ5:xaXm[xl?v{"Dt48 7xdoT6$5UW՗ϻ~_uP6ouIcU[k`t< 4Vm/_GD#>ӟZZZNז[bm.'&m#Rd=x@}uy+ZJKrҜ!;\SKqWnGu8uK!E>@n$bN S8w/?:U\-O#oikPy,袅[Җ .82Mu՟b}~.wO{yw.*)2q7TT4UoOƏTAEU>n| :ZWQq4@hzE@]nnPͲ̶ƚ=+/}~EӮgdC7>8eNW~H+J]lrJa*ZI0-YuMiMs'Kn&1`^+!6eev@馪 @M4=k%47ˁlOvm%i*cܺd_Uz~ NP(س<;!p-@ Q)Q9S)ǛolaRߺlb著 ՂD7>ajB-ZeƱ[iRB2ds6#i YYdsɢE{ׯEOXP@ %S–L0xdy e?+ 9(a^$5C{n3P=B|f*g|QSc"B8 X ,YD뿽f,P\@K+EaC2IU9 ZRmQ  is`Ƚ6qE[~_O`tF0 S)ES `)Ӓ_i.k;qOЍG{">܍i,6@֮];\ Uy9ya̦gb5,2`"}oe>y Z|r?*   @|~#[KA-oNwkl#-޶t鍊RAMtegRΫXEϵ>CܲU湧QyUD#ώH@Yތr IDAT@@@@&G%ୋV^(|G{xgbnY~Ehݒ=XTۗ=ݩloHӷV}@@@@@A%`+$;Fe/l|HziLTFkTI|ivPwo>ɘ~Ddd# ry(x 7>Oڅ~RK^K+Cz:?i5C\ꍖ%4Z%/JU~cynjWjؼ;'\}%QW|>SΌeDd`6V?Llҍ=ʡmjуB@1cOo0Leb!)ERO֔5U Xv.F.O5PSTMoYm>L8gpe S;?ZW 5mSp NPoڢ%?I[.4%l7)?do[j[|y28)sأɚ&SߧX+NWe+?~32´K[ČF˃<Lieo!9zuVR{i (r4M'vz=]iKկV(rMӢA# %tDta:yl2M-u:$똬뮿Ɔ,)0py02{_cik-%}uu(+OΚ5_(mLa釻;qU6ZB`]mg_rٷ|>ߕP #͵[~ A$dd ux?~ua^ۻKCnTfO;(M\F%ql峡CQUaYiO^_rYWYI`Hn{{@&E      6<&}-_ZXomn۝jv}?(!EU^7p,Dtp;0(9X8@J=hk]a' GYttiv!VG@@@@@S`Ts夲<-*K-odƥE|n>o#XJ}օR      Q0xx,Y6VW= nl )sd9dI-[O4 H(@@@@@< x^wǜK ,=vn86F6}<9l˲R~?p,TYCu0xz AyKgtKHEnR5*$̓q#_'W5"YV%T9sI"      $oo7.UZChzֈ}{^:ŗWU~%_M6v-GOY-Vzаr(pvwq}8dxI3A2/ YPK-V/pages/88C443B8-BE51-4600-97CF-5D57601D608F.jsonXn6@V%odnsC>A@IĚ1Rlŋm& šf301m A%FZY4aĎcGM\SwKqF\x%SgqpF:t?|1PIčIja͞{i)YvT@5NXՐyJ =:l+5`1lnuIw{BYco9GR3XN284Efw1(F/Sg[eV蘦* 3 ?plw% %Nwݾ9h45Pu =bV-, J4xT`Miۖ ǔK*\ʑR浳ړ؛!2bץ^BD.?ʁ}ȁ簆gmV)ԟ Nb8[9t5iDT2cK릊dG XLw3Ƴ@+D=dD,QsM8㉓ƶQAzCKٱFtU=>F﵏M\f<0`}YP xj;Pv}yx5qvs1NDI@;Rwؙ3 癗m}/,+?O>2$Ar;.|K>媞ow+Zg@^*mrE u?l1eSC*w{;PK◇ee3images/c183b4f7489cfb6c0e08bf1bf321776c3582b22c.pngPNG  IHDR+'iCCPkCGColorSpaceAdobeRGB1998(c``RH,(a``+) rwRR` Π \\0|/” RR N.(*a``K @ [$)^bdo!`5 g ͗f3K@l 蘒򽆡&~ JR+J@s~AeQfzF#0Rþ9ɥEePc1JbyLN@IDATx ]cn>rعK֌FCHH` !vx C%؅͆dYIJ##N b'sX4QUhi4W9O6@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@1a ؼyskMntY_>s|3`A1_GZӫ nQ:m,ۚ3좿 ¿?424֞&qux1ߘu&<;ީ٧~- ?@@c4t@"0<<;Jc&@o Lp5J8otXst$i`SS'|<)c'|<9ݓs:% Y8iCHm_g.ՁI¯U{ҜHȣ0tgxT<+ ߩѫ׭{@XJTx @wO3M7[7MR׊e/,8 T%o<"=%&Ɓ*ѡ2 @zhHOY@U \8VP]J= ӣ7zIڋ@{}4ڨ @dr" -(J-ޜ ,w_%Q\h*>gekp2/er @vhNYS@ R_kr]//wosteL Weǃx8\xD@ m4D a5yF(^.Ndž@e~1f|СO0@b-@@!\J@'Ω,onZyh}4IY&<x@`C@ Y4$H- Y+6ZJeLR02 B;)0!Jw>Wȿt2Ač @#441 mؾo&6pVD 40*K#G~ۍQ O Y 2[?]y/}*@VBdwJHI& @hHq5@ KT#m8 G99@hh@V*=.N 'Ұ8 ֚dȀc^Yst~aC@ Z%t@ R/p ֹ2ٙ!,%d튲i6w9a jhX #,."@YOz|1yޑ `dZ@wk8 >ٯ4Gߑ!L_\.϶6xBCȒ Y*mH`˖݅!߱bM0 piip;*shfu&.}(  p   ︣o2x}rHG`V.F1`kOĂ d\zX`˞=]AȻ%bQ<4/~`B| 8@ 4d# Є_96R}&NP@]^0' v@ n4ĭDH O3Gn$ HQ"@k*rwة Oh I i%Fz@ Km؏KW0hB8 XsF:iy4S@M4 h@~۷HKnKHG@j[k©ˇޞX@ 4ġH ݮ LV*9#pB4odIDV)*9ۆ7;N#NǹHf;c>\=<٤; @T4D%K @n}C{A`Z%h@  83)GP EHo,%4$#0!<?>~'Y@`4T@÷; }(I"  B'Ja[r<,%@ 4d$M`[L\ @lƤ!όWbJ K" OmeK g1*!ؓȧ%ú @xy<@ Ό _:Ab&UIM|,m$@` 0xI`}lIqJiAcu?(< a:\D\(coV*z.|@ TkO{! SMf@ ;w[I^~sa6hD@҂Cu>t4r D+f\V4g72k.{0  쿿ݟ3a`2ːT#@ hHA!Hݹkv3>~e"s@@$~ض-7H' @;hh6q!8;} ̗c^& 4 :N Al~ 3(@@d,# ~֫\./:@Z*@@K9 X,y|ﺍoy\޼]B8'oVh} 2*P*rATڌ2m@Io#I:G$AgJ~/n,I-#I Q q?ylt+QEA @\hkɐ.DT*| f+H@ 5u{jK@V *IA v~Y8 9% mQ&x  q:^$زewwei_$jpG% ڏ8/d%" МG#@vaIM K:Ehc?uߑ{-!}Ąmk}?)]F{B-`|P#GNt>H< K$[`UrqUsB@:$]۟>vݲL, S c{~Kn+ Z OzanUSD@ P:JFX,Xo;~='_ @hH^bX ~(C^/x J.^%@4[@ek}7e Nݼ\>K @X;ΥC@;w8g䀛/yo  Zk>@k&4@ zzDoL  Bpy?-- @hVyS) l@4tR@)Z?N`@x;:r( hCZIh N~ H@V"'_+_q졇 V @V$P*ݱ$@ 5o.z  "VI {_|Q]ӎ@ 6cGG>p8F!Ѹ*R`;n'aAq: m2$Ǯy {d"%@az4LŁ w~ QE[_E>a"+`%j_xrH" P@( [7ȗ-Q!p.o m1-g@忍D ,moH#"p@Az4a }SU﷍&BE@ ç܃VҀ N놇y$1EH  HCOE A#4Q xI;%k@cqn?~C1KA 0@ ,"7vKڸ  ?mp!x@"WxP`;qL0F@ Ek]{ݍg럏sBIK!*Or@l>qzw ~.$a ]~xS?5"@h\a/uݮʟK̯oĈ @)<$.@@ #wmCC9>@:% npDZ:E 0 `˘"1{H~I#$A@xowޑFH ,7R@}k`{%W>$@xl02`О#9&*i+Q@ v yG| C@H@Nka9V /#R@=oHc$I%GZ@$FL,#@ pzCB@u†Ct[ @ >>yY&E@h# RŃ򅲷 lM'O~jQ#7+gt]@hJyuݳ}Ϟ:@ 4x lٳg}Y 1փåRimH@4;짤k\H@@`%ž[84<<ܻ9%@@ʓ д͛OlmdN@@z$$"@4DK$@'} H+ID@ w>j农@ v!+@@ I/JRI+NYرk/kޛDX@Xwxϭ6 G %#j! p @|ccw%#jhX"@m~#IJ`I2 D 1:vt- @@ 4$H$صku/]7&DBA@ 2c灤#@tmCH@tƺQOr)v@Z*1C@KY ߲!eL`߾}E߭xc% @HtVOܺ!! pY.K$_`u 9@@vl|z# @ f,!9Z`70[.! @^0X0UJf@I/O$Pܳ7Ӓ'T@h<oD$s c, R +B A! z /KhH_#@VsE呑6BG {TNWk aM  6{Җ)@R (^$STe  ƣGz}Qe˘Ys̿(ٝ@@ Uy?b @IH={F[ғ+r tD`*o9rtGb'RR(@*Y@NsE@  ުɿ3=!'t^/Rw;Ugcͦdl  x*זN'H=P!}3   +=d Y:Ooߕ@@ ֚+)r-~Kd[#  r猌LE RZ`V{-KC=Ă X;So6G`u4Ώ3. d >  6FMD) K(JD   ^ch@ 27B>  @'%!&LKI @[#&2@@³i(@9z42*|@@ 靮oĈ@hH~0MЉ@b[@ hCP|#  |pR@$O+3Raϳop@ȼkfI0ϸtys>  qM6xBH L v^`&xnSB @@l Mkvl+{9/θ 3   @a&l.;zzOC-?  Up&&!Z.'ޙ%=TT(@.0{C? Ш!  7q+[ĶhHXJZ-jbEZ@@LRpCX Xއwy@'"A:O%e_L@\Y@`l ߿ߵl  ]ᒑTR@hh91&D$\  `O{  K*>]  pK\v .iI?I,5Ҍ  @^چw2,P*w2L@@@ uFFKnH9 WBM@g# >f4,ÛYL@@V |# @9/D2C"a%$ JOI$9@ȰYǫl* u.E$u)N g@@@ kIiG \&pX csq\9ƱqukfqAnM Ȯ=kuɣdGq39Wk|ww E{">|Ojk&)>pX a:\DC0?Œ$ižU0]Ţ)( RُfԖ^Wkz_ /Zjb**Yh`A7.S(}PG [YzKc@M;{c2k*Rjˣ#@94t,!h&3G6'm9ź_p5}$G/ c{M<=N "~653q}q*)ҒUԇ h(Z׆Cmd13Sfjj(6xmK}N  A(ˆKNg;˜t7}}׸r?ΛV!QBIՊ3%{U.|gZ n-{WwA1QRf {Mwĸh U/U^k6L8p.$燴#RJ8|׿2p.==#t-lmdA6(ʒ{}.tK~:]uõk]{=i3>6٥x9:y#@e';eMN/#8Q:uŠ^.ٚA@vj^S2|@ tH~L3/RꑞU=~l ges Fa44 q5K 6݃łYnA>yօ֏t00` n{YC[`Q*UjsW3gϚӧO2UK"Иs4Q)(J}[gP Zi7ѝi 2  NNMISe褀v׊~oo_X,6 rzSd6FLT `IFѸj6I2WnI+6W]lXݽpN^/‘:vZw+ Ld 09)dA6л&a~qQJ7LÇtyNKC !P~:,gHGmr4\l+oe/ekqtn,VX+$K-^4\@+NR`[Vu<ɇEwW͖Zbht 8 :^xJ йffڒ8czK ˿3[Niyŕ+6mA)  HK6h(@@K<5%[[+LSd8X^u2yT*C&aOI*Zk +j3ql ]˯Bl ~; ͖UWP ٢'R==ԜH/a^ʢL=cd__9V& `IJĕuvAvdܱ_^뮛2FUeuiUEq~oX(JE_սp.;Rצ|~|׆+w,IQFw8;|3V䵌us{SD~w߱ڳC}/M2˔ b˞@`=99Fo l߳j[7ߖ o!3EV\r97|F|>^j\>g qptyui kJ'paCOt]uy^˯™/7!5SM]o^"_'3}^W뛈CVϛ9d:IkMmxѡ"Eȴ^S3f2l(0Z@8i׊~P :, 5YJO*UQ 7Lm{푌|, !DqNjlN۩ɦ6T\ 7. Ra.Y&Og9u!:Tfdb}TfgfiXj^?=snӯ9{if!NY }k|6*`C@Z˗|]^;7∔ JMiK Q+UwQtAzN XHt(0+hZcC K^Іʾ6VD@ n~sE^;4D@$`^\.דlҊ@*IIj}JV@/ۺgΞ seN]MQh]]e]NHY7:Pe;\*dsGeފg,(7OckP}nZ&Ԋ=tE I:_#?,nV#@j876 'wMLLyp>Y.OIGtmu:t@}݂t4 ٹ2ZNd9m=/Ob_G({Avm ak@^3ST⯟-6X^p1 d@+ 2Y\Z@VfK*Zq8;6zWVaRy2ޱekJŢPΦ^U=ʬ2)dU^ gZ2҆\HCfQ]YB' WeBJRp7KOwҳdv#x .گ]:/ e&x_ 6 k3rbѾz.z {o>cG{!@o_p޻l3.@ _8 p>}:ܵ6 ~*>tE;[ztIq3.=sP2L'#1!Q~$>(DrgX {0;%N)L\*Ӟ2 `͛$4db, J{Q* 7Y쌀vIP~>v&!Ċ@t"{韒l @[߈,F ah|)GkL8qYU@70E:ѣVu"ED&`> " 1,5tX(1@sE , dEm JTcˏ͗no<[絚.m<S=7[pȝlmrI~YM|c܁ç?ѲTsy(g@:`kݥ}ixIfwNl }WOEy,Jϱ.mlo\/}u:|Ԥ aa>F!#Y]wV*0<<;Hm8őip^"m#FF{"w$a`ffs$'Q Xw*23?X0Juߍn3tt(~ Io1]SN֜N88C[d)YBakTOYytB$ b$@Ii@ŷoU'S.a_t\@֮䲯[;e]LwurE)rvw py0t耎YF;ͅ>KX89ZM?R#CkOfT\3̫Ѳ5. c01>6,lgf3:43? _Ȭ]ƬXcr2[tswtl 8(=»(0sJPtEYȺS2mnrWf(2Y +`s\sy?#E 87#Ο+nz 5~F\if4wftx~,X{SiFiLgdœpL[ҐHdwI>lBAH=Tf.L2|rTu3x hjZ-ʤ#4W@EJ:3 ._>߲jsy>9.`s]|uuʥ>Mйlt2ssk{^ _,)kҚKޒFδJwp0]5GhR3cc㲏Ic[5-jK>v{N%2"A4Ͷ' K\/„Ƥ~ #߂;Sw|lM|6}n=)tU R֌VugC[Q We>-+6m2W\)(sll;j._vqZpy*H +0{ymCCsQ9Y.K)׭]CK%eê KFIO] M'dC$rG_ aRf{Vdg͔^]RxCߺ@B# r 􂮿/\Rw3ښwzt_vn2̴ A LF@CwhcNCJCt-S{$\ /@X`˖oʫݿt뤛qX/ <H Ph/mSfB!6[)ǯ<\.ο#I@K/Lʿ+~d~׭]~dUy7<:d@db~5=4@(9 P("gdz@,=|-i7Uf}r}p{0uLt52ueemAq+DT;eu%311aΜe@z{{Q2 H{ g(Қ Yɮ׮zZehJɷ7¥=ngfe%0 2d%Bl PnU8{njjjґ'%i7Z:"P[ގDHYDuY.KD K RԉeuyToAёϦ:d.D1?e6Yz`߬_tLlDusLeU<0ERdhLʆd$[qp)޹%S r|=]y,%xSizL{|_F%S0- "U県_+]7`9IлA33322Y7B# OOwX1=]]򋖪icāc2s@6q[##:4Vآ#o#7R6֯3k~gG'ݖΐ03+3Jk/m wM\vhnʾ jJp$ ,P8+jlpǏ /d!4e 3wh|+&lڸnI/H߰"KLMKcT\$6l(r_t/ 8D@W>uxqo9q䮯&=#?n٧"ַDWubg]}uxqB!4([]P79z" ac4Lz/ఴP([*K|G _Μ:}ڜr- oo^3=Qbvn}؆,v 7m]Wmi;y?[]:&5e6ҽqQrq0da5Z@U::`u%ars Y2Qkw 7淖~3|H.D`@b+N_YkDuL#=jϗ{ t]]GK4y5lAnr,Zuܷ,hwq{N~.߮rq:<U`֬YY:07d`Fz jR:]c.}˝}(a[@?|/|M~=kOS4^;@n6}ٱs,Wo\sMhR2_ԽL8չ~2y X_}Az2 ]ÒZj(S_J|N.2dn4q ^A… aS3aC:V /w߫r_9hD@+@Yds^xz@_ZbрH8q<+g??iYp\xVkE_}?r.{F dZ+]zZR-Y ^'ϻ.U:=2۷LEEb^'ݏ:@___?Si.{TjrW2%Xq |sztk!p@]攙IBvK%Ɔ@[ ="'&V n5*7d~R0M@eψ^t<|R>2^m!@ }C+$,Z}%<5 UŒD@ڬ"% h%ce?sfpZ}̭|٠8 e,I/CH.sXp)0i%\_.|];C ̧Tg:GGQ>CUdU̘i355%i“SW. sh՝+3hY*^ 8H*+]M%@%&R+hc@]&+u=Y'<|] ?5a7i_> 'JRw:-őtV@)kKoRaCzNX=t ?ץׁ>iY`],VWvpsW^Q@LONN S16b*ǫ\.!i9ITJI?%E h%kbB.Xewƀ蘃j!ϛ w8!agC $ߤ埚4-hJc~Sj^9%HjrDFR)@}*5=ں nWk+ӓ+ru;@{>Ya@ d#@d>?1w'%83A,7[OM$9=Hw&>wNWݒ */I2P@{?]uހ5fw jX#i-t>?>1}| \vWAr} s9'ÉSb*{ ݵgmsEʆ@ Gz 5yꋜ "%R៖6ev F+8{S|[CL+s6Ԝ(!Y]'|ܼ2T@ tww-- & 2LTc[ו-H]+9L=XjHsihkeJVft %zefvYp b2}ZٟYg1NT@ ˖GM$*@CLV'qRo+;7@0gr9~>2 1{Rʾߟq1/45zn_Zo\jJ/=w|2OH@.Уsh/?IpnH:frZ+O^L e:W?d2d:‰E1d6֯?0f?d<駟e]fP'6Kb R@+~?-cZ,I@`Yuטl]m''yC zA[@>G'.9Z`~Y>i1i_섏@;1BBI;,w8׶3^BA@{- hLIk<?3#ce d l;Am@&;w}s`UB,  각nm7#$HG/?k| LO @;hh6q 5{H(XN 'H`[z t .G{kfVVnzwwX )/R.y*$ei`@J4-=*1/'@R'&IN>@3&+w+*wg_՘ HO.o+D!-a$ *o8*}h.7nn5ż4 HcN2smF Z^ WxQ*ZgC:! >׸]gVyw$;nYNOHՑ R5hVP(=Xr1z_Wkp~M~UԽ&gCb(;,ޭlD&@@d+NU/DH@{N^ KW;g^-||W  |{Wt6'!p *h ,B@@_ɽĉϜAV 0U T*|@@Xs X,p/عkԿ    }=`@Rݻv߰쁼  r<]O4s"CRf?7+  ,/w!ܼ]މw`@HΡ17Ż   B<#|NC ªvw}_@V'#  rSq_q]_] C`9,{ J\z/+ś  Z1GO/USf7[ιE"v  4"cn@A`),kؾkϫ5'eޛ@qeu75}O)3/)k,JI)WQv@@nЧfOW :3.ٖK˶\dI־۷,\5E|-^}>Lyݷ.dB03)ĽO¦U@@@@@%NNIн?BMIDLXҟg". N[[v94;J$*lmZ^nZ` ;=eG\K^z54@;{W3PplpA'gBEÓq]g( xHo_jpy"ӯ 5`0 zR'/~|^P ceqqŢFH$Bh" +p8LHxEY㿑@@@`#G~_|_5m*?VgI^Ֆ_C0Pˢ[%SXb<,2H ,ٗ;\%[G[Ik|Xi:}3( ~~0/ȧOC @5;3C!6ާg3$/yhC/@ϿAC9yמ\ó*I~{fB! 1`zz_3[5CSy&aVCO? B)CeBMA8beLM !4M}>SQa!SaA! (TNL-099ISS.I24tH  &1:oQa~^|c2,'&&&h||H0G)P':s ,d pEca_2#]Jˌ qQ1i1L\<@@2G@\h~G4G@O1`FGGidd_VM@缱ЫgΜG}"@\O~}_XX@TҲR*). }. !gh8C̈:%A@iZTē}~oܫbtNeP^Vf濗ߞQ0 myxu :k&/$x<C BH|ȷ#L%]*++WOeo$Rv $VdFxufBŲ0jE 8kx񄵬%%TFUB.s380@kց2K:S߱m48w⵴|&}mX>, WYUI5UUT[[0,j-).Æ末DF'7>3yMᾯF/8@SOO7UC0H.'ow[; ϴi.Wt(P%AK먶pĊ%]t2c0 H0t=( VCsEE%ʍx22dIm1NPoo/uuw18?r{x(3 k]]E= rd_^^FWUQA@\5004HClxvFB p /Wo5+rjCe#n ~fO+IEla}Q[,: Lŵ'ul 5DL@8!ƱOBEy +?&\K PWWS?gmR5{H AJOkڝJƆZz ;oZGQ4h<(@ -(VTVXش:xuvtR{GavVBOT}̷:XK l{T:wv-V ~Vڹ/-=G=1F,hd:g*#xlUu%aYim9ÇGb>*+:t++!3 `VímK jK>RY nh,4ٹsgJ@I8ؚ^ᯮJx9sMEtЃg'ܔF;Eё`pd&cm_fx 6OMMiFvse&&;QOr: Ѱ_ZRb\ݽwB!,8gk&I鷾P= `~ˈԇ@q% >!^%~:Zv *CiH@ H $Э>3VJ#sGGݺ}UJ6 3/y뭛.fjapiĽX.Eڲ #n6m*V27`e/m CF&$@?oGy6a{0׶Ydx|czY `0")EqnBee O@a_݈~AB0IоZבl)Cm6<ֺu'I"U-lѶ翰P6-+2k4Fjko).hCCCƀ~ON N%PTTd񤿼{Kn޺ŧq`6X4J}V.*3@oJF4S{ 9s[,#em7Lꄹ-5YYaZ 9;`|b¥$6O@V9ky֐H W?GO1`82)áĕT Wzj(듔%jiiɉ{svS"`Ur~nDZq >cTj>U~$8H'/ Ig[>LoVa?%kB >?޽RH`>`Gg'줙 ,E  PmM-Z A (9o7@XQ %;ΩԐv)0,Efm9M?J[9_~%/WR> 2 [<22B==$\rPm] C /iIN @J3{kB Vo=zbǣU3Pkx<6mބ>Pܩ [$(ΏvjG'/h_^EEIր *c𭛷  E >Ҫ-']8QioUz5Dn Xn\@`g@OOrX4</ZkdںZə %0::J\H4:[o"82Լ 9/ ͝b^k$?8uw@7(ZSUE5$F$wDt%c[4ϸB'Μ9yњS! pJ+Z~;J.T\?5o͛7x?7TD*QOOVD)gZ6ʫXA)!$X[k0_#߿w_M  O#GOuS_2@S(" . < ;@>+ee: pT]@2,o CJ3yS_O4 YJGKxˉ1C.hE) ,..R~~^ Q@Ʃ b D@)o Zɑkjoqyi:wRJ&cqzwN]O4 YFЧzsB2ߒZ .U?}@{H>^EQ9Y ZQrĭ*Hrc/rJ$./۾Vʋ%p;^zw$ݲPcmO/_,I^|qɪdy Xkz@Q@92V_|ܽw2L{O ďʸݐ_sfbk![R- >d58?@CCC4`X 1WPEE9UUVR[ xUbSFN(X@ 9ƛ\֛JyݻvҺu܊z(@`C@C)044Hrd̘엳;yYppyBLFtٷ|/Rl D7srjkt4*Iso۶6on@@@@ c :;;3V *[?xp{JEvCR={L*ɥ,ufń|    6#P__'K%[uT ^ &W MlN?},ifA@@@G`Mj޺%{ 8fp:Ѷgo)$R A@@@***hjjl٢k{׭xtާf'Y\Lzk7q6ȑCFTjA@@@lB@jkj8y&b+ ֶfSV@׋- |>?Wql!:T_` y!$ ( ʘANt{Kشңw]ȏI=r'_`tIynN.m$J +O6nU=%YS$@)W;.SL9%6@    غu+Rt*jo$Q YS$@)vڑL%+jSS#P-ȩ۷o KX1) YCG~F駲܌#߹};‚:g8@CCUpP@9چySrؒ @/pv/ꨪjO-H@;v_8.5^ەl9O J*zH:K 2A@@@@lIdA )i cǎ']"@BΤy˦K1XW<׼USdZR\#SRdb    jذaC77x[3_!Gv~2Cչ "S@@@@@)󩻧fggz i ;=2Ы3~!Re<~}EzpK@@@@%hݭtPu-UXHlHsZ[[9WLecC}BP@?EX>Vk it: )eҬA@@@@@MV7)}Ң#@zKK>wט1wSH_{UZD `/Ht>bgqFc]:ŢIH8\~^43c,Gl9ɖ] _.LEK2M<핹G[R](_u>|,ER:Fxw>_# )_,ՂO^c QǏcgOHOveB<^Ι{\3>? i 4Og{_I"FwC~Ϟ|s!eU lHoSM?A\ e+//w_\ggghvff}vg-.!^?؁,@ȋm(޲ Hy\~ϡ~ϕ\6\Td0Je(#?g?+ʌPȊ,IhnJ[&QB?pbr&''i_kߧɾ#$N@0Q^~{ xvO=GvWC-595ROV蓖V}lY*]!+N&ReaQc?3͖o$SH*d@iq Ryy峡Nƍt ;lYZlwiVɈ-ˑW<+&AJ;KR+lbYvѿsuvv"Z,F@&d/|Vц )dR {t_W¯#<2ބ+_ipeOׂ{k./-[6)ۧޢȇ=wp- <88Hq99[I H%52Z*,uƁWۚIMw}e}TSS÷vء #; ȳZMu5ɳ륢";j;ucm& Y"_{T8g>d*S#z{ 0bk y4   0P(j>= |F|GճE<ɽB/9sf6۹NX&EdRQQa29xIxi7^$pbl8cJ#eh^A>yqad7V4ǽjî;vo3|#a?O?F%lub 4ƀ>_>˯)9pf0 @@@Lrȿ\G9/GRn3uT<=SY9{r`A_;v8_%LpG:pV`0@'bf@ 3Y φi;"ِmTV @@@YdR R'9.ss '@ sR^OyohlVyN u; O9ÂKrv>qs?92 ߏEAb$Do'C.)v`! }ܩ[O0Qr*)'_@ N6<1(=#R4/1"!/Fy0mdJǓv,'sl5I1|)HYV'x"eOj=񭳧fV[I0pG|"WL b[Y̥o.( F1D8 ci1/ec[tc 37-& $k$ޘ?db[/ߣi'޿[oM`{“J9$ ȃ{5p.WC0 į(-ƁyCA$"w6$Do1 8O,ab1~ h|.ۘ$|4vd߉~2|b]둺I~']gN2klryy|Аɭr_TRJp1//`Y& {ŐA 4ˊ+==4/̎ ;$Pdv *F6 Ѕ Q,Σߚ;!y%8n \Vg]1I s%  h2jN>O uyr8{&/4zH& Hw٘d?(eièc>x?/*zvsH!03V *S)>?{,6~~# cÜ7\+n }0V<ްX1/lDF}'ExLRp=o1Yʖs#[=yf;I- |JB)S} k_1^x5|5YE.x6qcza>'`?3ߓ%47Q^X<eܲj.I& n]2 4ϙgmԗ>Јd*/;ϛڪBk9vWB}Q|3cET\=hK}rJyEj8΀ӓ<H1ҙ{`++B6&%@Z ca8a62,"da|TOW/MzoC\hy sfGx{3a.HUMTصlq^.vg7pq6+ɽu*4d߸Y SiGƟ\횦 *:^fTە=_R^|&)PJ@.Zv^IBqF÷9wԤ9 2ϧŮ=k0G.(*Ѫ5^ji+ƟTe]ݑE@[D2?ۿ| 宴ܙ0ÿ띩Zy@/.$\ԘW#J Bgb3 nT+*9޾WUs=?JH6Ñ GZOe&|EObC|EסtAS)Yk_K40Z{M㏩Gt#[13٧W=rGO>3w}>so-)ҝCŶU(Cmł( _BZ!ڜ#wB20$C y"Qy/={컒N+^q@Z 2d@|$09dyW 6A*amgaf`1}c߾ E5+Wk --myS\(z;a0리ao66vflm}ɒr$4_lFk0u>?2V>w j1>. YF4N j=_ceiN5*]SPNz"So$dđhԹKCھ^"& {aUړ~W.4 )Mcp,F$ HtdNdKH@9}ekZnea+@wm뙡ݓ1?Ucw5\#?tbYTt]M(QSԥS3G4Ηʞ?#011D'S"v;ȫh^rPF)Ɩe;[/1(Bh:)cxN +bޠtdh1YcA~xF.-jU`iBSK5/9^^y)}'OjPx|R/l#ǣ{#:*C'Rt!̒0-)_?ZUd//ȟowlw?hɂ~}1dubQ ư`q0]9&c 9FJሧYj6N;c%Xt/aqt>Ɵp 6/3WKꈧ>C+K)RW8H(uN&ovc9*q 7:J,⣁’Ni%lqI' XȪa,)'+ |e<6`{'k AA???tD|vTyˑ~*ɿ#xbQ2pܨBgeǜky!g~;söv!moɿ2.WjttBO軳dEIpvt5hg/ܫ}wtZ?~?W7i: Ieyl?|7 i[7hfѰ9oBWѮ Raєz6L*{5 ^R:u(y}F'hvv֍;@g"~z>d'D(Up64i-Xvc~`ݝ$W:RO+G8o:.M7 (֭UAȐEzC*vLKxK gUgl9Hak;olҥ3[cn{Jh?O ibbV5 MkixÎliGiccc PV+tvlX]?IK/ T?+w4Y+WTE0ش1ش rd @Dىim碶8zr;&LNzv⿍t*))" NPyCX^to@^({iO8)8zh+'n^X3xs\GGcc. &&q͆u=簥iͺ?voP^$6kx&r (no"jw(P䡩^WK~qIu^i&'idc v4j"[Ȉ-,H=Y?kpĉ5 ?1fլxzj6n@TʬTS#%TEo<V' [١8ETiyr5jTd0X}bi+lcMh5[F=3gf|ysgޞnb`jӏ>m ELmW|Usd}y'tyq4oFڒ&>OI<[qoeO?Vo]ah9[Jb?e- EIꢆvA 2i4j@!aMCl̐ί8Q`P$~+~ r4#L yU[(<׸p:57:k[ݪDVwCVI?3l1UPKkI~}3+J PSSCTBa16Ag,s1>zX9P]Tk:cd 0$~`Ir91,G]#joow.֎'G;};v@`v S'K3Ӕz%(i;_:fh;MP3h"9ѣWomaBV;t2gThC٦NJ6"ۉ@{̌Idj?&@F[o w}&-vPjg?n1}SSӓ~Ev;^ wd-,FbTp-S0d=q{ "VYV~cm6)lkwܵ>u7XE$,B`:olX\'0@:f'lGV ( 2Au, g ~NsU5JZZZJHUw %+u.z+D#~}~gpF[L ҝ)́7RHOP9C n6k]KtzJRo0r=Z>xHPȵ]l-w; "p7"BڮϘ2slO@AJTHiPIЌb䨔;w(&1,Gkh&t%GJpM:D#a6?fwN{ӗjrcZbtAy[b+khiRUd.;|dJ$n#]ieDN@ qC>ACdGXƟ%%`Y /G">Y]UOqo 7UsL+ݻLh ML@"(p'<T$EdoTD"awރɏCe 8z`y -Bʵk|,`dopBF`:pV' *r&ƟԸ{TD^ <֭yV쩋@ R8:LПA=4[ETX9DoDv6?& G '`dǩ2 6N1T^(Jqj*rZbW2F2Oː(ְFC\MSTP:F^hOݟoG)}ן\2D0e;Ϯ~FvD-rmnGLxKWS$H 6q?ֱwBQ>Ï>$)PfNzS" `xSCD@ i`=*6HG<_NjmLO;O.~HnP:f'ji w2.Fêb|pmip81P[VJ&H$Hğ\ܿ/P<SE N"'{'{lV[nSf'ojO @u?c @]Јx sE=ΡI' R&zpi;TcmbYK1AsCt ?-[[ʻ@,ꧾ{ 4pQvV&0=Z~=q}ebO4d -M@8.P,}B aηJt61ќwC+׮]c˩ B,5^rl`nh&_ VWLX- >?1'JlA&'qlpPbQ')1>ǐ0[[NTc(77'(KXFe4_`~q>@G̙3XtYqsy189 y%Wxq<ֺJo(k>xWʌW7ș5KǨ|$i8fO@08oYf7>!@ {t=^rZnI rٙ@Dz( H ͘ˤ|$e%MSx+= $dŞ]u>0#"r43 d(3X% GSbB6xd`qyLMMj]|k@ɣE7顏?Гn ; \qn߾jP Z.lzQB 𨽝>wh m(D֭[t $(!}U0仦áR޻ObEp_; Q \ܾX?s    7o]ZnKK_K Z. $]N7o254    `6ks!,-U^·gP/ dwUmBz$,]@ӼŞ# 6mڔ:Q5d#k@ >\[i5D14' {k{"Go \Q@{{;o/L>/S2R^* <k o-# {=-H    j5R)DRQDE \僋    `/9amѪ~@'ϝ3jK&y@@@'᭚/|@7o"1 TVVZ[TgvY 8j'ٳ4^H   .%066Γ 499RPێtM}ۖSwra;^x!?F/H    `?=yF'<$>~@IDATFCC4<g H 'tmz$x0(pk˭ v (?]W_v4e-y0[韙MFkdF$ |z2ݹsw/-*Fr]Q^a#ŝbrs=#VQ*ĹJ?}@0@ht7]&0<TK J6Ar9 癸8rZ@`iAŴ]#Eue IgIQ! %v:_֬](6yẇĭWceK]&0jP\A@=􈏇U9cK&A K cG499i? E0_Y;P"eh" gFv]磤V^E׭DasJnG"Qs62:r&Y䍵,µj9g)MOOӣGK)߫!xgo'bC`YőB<xB+1IeDbؑp?xk=ju=}sRʄm]hwPggJotZLJ:x5dBU`^9-C{b Bc$G@uMa 1S1tخ,}I]]]lJ( "u6p%H0oO?W5MTgMc  ITW[K'  1w/xҽlMMǥ'I^Pbהƀj}#oEg0&\VН|S '@ z\eF^/,edJK'GM 1}<կ)w\f ٳN2d|Gd !UP# _`  P_㶜A bxv;ԯd(< 7L1-0qXUawŲ8mݲ:R?(Vx.}(b2Tl`.U/g}DZfm@U@X7# Yq jyRp^6:b9." ƀںZs+rBߏ򤿓]);ib+ 1yy^@ z90/nVVKJKJ r t ~>EXv 6#p TY)m΋H? n# .51#6sr܆ !}ᰱQ{hff6rNTPP@^y/rz #066N/\plqTXXHe?{o&qy̪n %7KE=77=3;ޱ=o}-ےe[e[$[a&E6(t7DI܍2E TudUefEd_2xى/^4ẙ4Q?zuΙYa}-bd@$$ή-dr_y+@S YrXrK:-Huuubu{ fQFFGi_/Zpc?ɑ1s@OB|~Lb{:J|.%o멾nM]'i%gydx_CgG4e ZF{f:w\Rv\y֥ꓑS,4<4LCfV0Oa6ӌ[ @I A3'#K@7LKeM!Gl4pߗZl@c?,HάTb6e?1H $Ʉg@}mM>w=.(H g@'eo*߭ P(D!mA6B|"|yTB= k,Y8k6{Q]5lL;3Ã9D7 vX&" Aqp6NbJ̼T%gb*++],h)]fVUu,"oY= (]'7ҽ8%)~x"9pKss;;M9M 6TWs|Ni;NgH Ω7Ĭi3.«@̾53gvό pHή G,YH4AD5 G~֬Yc!7\z5+FEy7bFU,Y?;Sٳ#puAd&`hLeE* WդJ[ Zyaeuإ%)X`D"$O$`MQ`qcBGk  0,Bi)-  PYY 2&^kvst(Tۅz"X$~=`8@_nJgn]w Y-[Gݿ -ӂ9i#<}$[.R [\K-Mz )% $t&)׏sƔ`   XdIrc}з}}}Ad$w}v\W[K/=أXbP yYZZZ"pq@$b8ah߉c${ :ʹ#AMtͧhdnߊK[:NLNtA"'x֯_GG'] 0IBS)t "H@liuڰa#BR>m6=Nb/ $N]!8t~)qPŋ/H7qav5T  #1 0@w 0l ]@" "&1(%[Ž.![mog>| `ttOS|''h H  4k)cttt3E   cO俠ۊ3|5=3 Rtj !ÇcsPE%`+V, Q5/P,<ߒgd_/_N[6m%KU/\7 @dCtҥd8^H@l*>SSt|γ1NPŅ @@ p8p\ ~t8(B6nX7Pee"R^L1?rhZAb| kҢaO#|r;` e'PS]C}A:s,MOMg/  7 hAYQlYA.(9]iٲenZrH}Z_0X̏jisGc{sKO=)|  n;X+{R,9e4`ȻŚ@ɻ,  456Z{ 1YxG]Wd@xt ?[e˖b)@6HgOӣCu2lyhpP N ໲*)X3+be8>7TiTSḩ8Ո;'&20pOMMMTW[2 jG, 8]ٹ%q@|A󭋲j*@o e@)k)ouɨxXvaŢ7\%g v_?p ҋ J**B?=~v:?wnܸ(>__=.f@@?D@u<ӿ@\H|Ti܂RE}&}øVl$VL~fgŋt9)j6 Xأ 9*++ϲ-8t)nP@r$(d|<3ߐ 4h+(~t8̧x,OvOU%K<_UմC qKGu^pć422bo# @"`Di4RnaXo K.yɒ%Fn?y lrޕ+V@oo:vokK{oYM@;+/O~n OF8yEj' ^/P @VZb#G3^q+ex"?yYO^lqOy*/eG,_ 8H6V*՜Z*(ʪJzi=ooQVh0\h[(gb6DкDܠG}q8鎌#'> ]r=J*k|!+ο%7tw:W"wѠ5@M(S)jJ@DE76 DQT}htl}iHUW+̙3ɓ=gK85TYk2 } 򉷯 A@b> ںu+qu^_Hg`7sg:Zm0ڈ-wwR[=kkz5 N-Wd] wX@~)zgI#9O A6===Fmiдa0i 2>ؑuwt܎*QG|=/~I(/ϑA@@6KD@@M¥ч27u[׮]뿝IDRYTmEy/9p纵kS/!WeKǨiߡCQ-0(3k7l%!,Ƴ6E} k~Iz` *xMRU419Ay[1`wlAR˗ibrVW^aþ{pD+S}}=/ŸA4K QWh)}DS%<7L.؞Ayp@cc= w9>>tGW+3(<*y8g"&lZMwN5fV$uih@0xGgǍ;'=R ^# K |K@?O*]eWU ܂S{4x>t.%iMəjluVUU/H:jB hfgE.@OPZ cڵk¥b]֕+W('Utlh4;/ 1%W-Ecx>C@zÒF"yO]- ua+a:]%@@ `0V-U!G@-Fy`un t_pxu׷nMS$'?"ŋi%/9L@ ^!n:w$=R0(ѭ+Y6.@q Q@Hb?G <dfz #3MOMO$mqJ$E"CR@61ނիɏ 'jkjJTW'wL pVޑvɒ%/QWwݹ#( :::Kt*eH5KjC۩ʞ E֋A&ﹹ}48%;.Ѡ>䁀RL棚gu,0-«%W ) Qe?(f+`Pϝj!{/"uuT. NP_@_b̖w*"bP  @U-{b %^ߚ+ߚȧu2ܣb/$455.@Mu v rf!$2 >qϻ՟h@ j?ͮW\448D…o Aq?xm2xKd'BAZh-YVZ/J6̾ ,{!bh2 4Ml4~& zm|r KQGFFnN6Z@؏)ϑG#w%XEkyg˩fmg#@8LQ ..WtS+7Ek @@jg=oE/JB9EjCRX+4dG,9zX۲%LlDL}{b@v2 "XYER.snVY5E?LV&9w}9B$Hwlݲ9|N A'..k鉉 :G##P宵fC"Xfh @8_pnP%y܂c74  Xf mڸAAsD豣ɁyUTzu:^@Ko>@ee3l޼F٣… 0<E=̭z nB; $ѵ{QO*?pcv}_( ")/UFD!# ~ϟttY?Q}]z@ rK^:% U6}Mߧ8  P$1xxI$nE\}[;?}\d.,*Q4dp}?Gk  =ꎃ0 =:ʭ%;(ߢnyh%ujll [ti[YJlsmw8uT Rjhh͛7{I% Q% 5~$=i@@~/r[bӳ._rrŐ$P[S%Z<zkFF5;.mfAxY{[[۴J*kjx\E@@^> Q$1wP/JQۊ!I}oωbGJ˸zc^QzHD0%'/Q5ttt 󢵽yi&wo-j^D˖-XE;}4]x1(AuEV6Cw.]3gRT2+/E(I% F?T@gg֏|dcOxJ'75Hj #pQ(mֳ]R Coq0A@"/%KH$QO"RB p7B!,ql-]Jp40uuE)mUɽ5A$ "{) ࠗTrUFlIUUr߸q3K龭[t(!ԕ]/)m ha P0 Z'O, f =p`wqx*˨CJ-#=oVN7$?>Xҩ8ȏkHӼ3FF~{eKdr>%=nwx1u=]iՎՏAAߊ^y@O{y--A vZ'-Y݄)za7;"^(  !7nxkYY@@ ]U'eew 7 d/zO6:6)}VFB$ ؐx1<9bKW (B]oUDڌbz ''"unLz,sb[[xe;ί\©QVZrjn||[ 6b$(IgO}LD!_`c F@\Aca+%K=&z'<0̣7@ sgE  eRb &df],eeeF)-%x_x$u}{Z[/oN"=S$ !T^wo6`1'B`*:QZ1Ԃ> Nt  ~cp P@mmJѠ{F4j~^H"b Eʟ%!5@c: A3ݮݏ@Bƛ}qDwr q֯_WoҫϒZ;G@+8J {u+Z{>wBzn j ͋%#-8%xӦM "ef /E[DP $B4[b2߿s#$xs@yfs 6n$0ɛiÆT!"b8tZ"q>`2$P;ܩ  `#P]V˰= 8}[X͎|} }W,_Y^¸ hf[8VdzS?(!u׉/$ >m[ȉ,*>*֭u=7N)nF[ 6u25: l =HLn+VP}}M0ڴ rRl޸wtCCQi]ёp@ t,9Ok&@@ -ׯ=œI# }u^Vdz6Duu6ԦFy ?a_W %@OO q7  ФA7`  R,8=?3$"~{=_ye{mO,x啗i%3^Ob9s>K[n zX@mooolIxL{9{lby{. .fjkki541=z0}@yJP Uix/K8vjrk׮˗Ku{] !S^ꋷ754WNXABp{W^~%nQx]***ixxD%eKϓnH q477l5Mvq^1]bpѣGiۿo3D`Ws_T 86lz{_nv " ܰ~i`G۰a=%)![~GA\=|݄Px4rID"JuDg~/^[ J!pѯ_:;_u6/gK-L/YDGen˗-urT'&q9ÏIu莾 藛z '>q@@%06>N$<4@@ M( P4C]1*p8r ]C@@I}^IwJ׌?*x9sZB=.S)A,4cY{/ 7xhQ( ` #G @@@-}1 HB8p@075@o>42nH7Z~l  J ? A%Ѩ{/xo Z{~CCB "Y(@ , .bGS */#o Ih-@@ #!zwiF]@ yJ:P@ _*&m{R6@.]L=,$#˗w^/Q%o6Pr J('6 ^+h@@ccHlR SUG>U[ 9BΝ)  '_~syLv_{^_yN" SNѻp4 `_ 85<CK4zcߞz4=D^p!vFBGjH@xgʕm؈UeDX.  9b9_3җk  `kho^D y@@f33ki3ZT&im]mo{[@ :w\l݆MȐ@fffŋH5@XC%000@{{hjrңB-ܜXT [O~z.2K;  MhӶmJ"ar4$?3O2  ly%4A@:44D.]2oh9d;D(.w5- @a4%?]Xi]}6P9˧ .6  zzieDn@ ұŀ4>1^R98gdz6$Rxuo_|*væ]@"07%"P`^J@S#C._CSPH  `kQW?siz^+'Һ4q @ "PYUI7nQyyyAux0gܬWԂ 0}]EY COl?ǎ"2N@vMnZZz BBAP@,׮weqy#ݗvQLlŅ ;NjE>) ΃@-[.]ŸTSSSl( R<~@@QվUGPrt /LM{=K\[ TUU6476RQ [!2$q1433X{@0 =m]9r_dv#.#rGy6nH@@2"NogN#G"ԶdG_MoV35Wݡ7@  E_BB aBhtRI$ 9:#ܾ-ql  ض~ t,*@YYnbwOZ [e '}gOn 0X[-A٢" F@m8Q lBbS$(N UI,tW8z}BVdw}ːl @\'`j_|* ,-v1ubvdbQQ@$֯_X%!FAL,cU"7GM~bvdlܸEAJE  j}܇2 y [\e"hE@& 7mژw9Yl޴ Kd '(OM ~jڦ yܹsgu @#]++**++iN7A@gOƀq{8 @Q@Ilذ$Q0{;k @ p1X `"  TTSSccL   hnn$ 0L#9.ST҈ @mm'52P@Q֭YC&k\U^eiUMR @=PHpN 9'*F ˲{֮yh!d-Q[r"0,aG[[۴fjf9oi[A uuu5A@euurh@ iN8QwujCC~" v!\Dv5yS#E@ ZIpj<ۜA%i禾RsٚyoIc_eˀk + -_8㴥Ne̖}{ZIj8O`7@@e+ls8g좞D3-X<_۳[ʍL$io^_)s'F]3/\3ppu54 =h $knw] ~`;ގ]se`򕡦?Z.`o#]m{D0k0Cj4 PWé$H@gc֫h40:9aH%wi"@&vF`劕Յ@@@6 @.< ';<.>zlxݷ` ]D7mb5ՙg,/Ǯ!7@a*+*Q R *hjn D i hbj4~F|Q -]R24RsFboool7ޘcX!l24& % p˖.vP |@@ Zv?L9ca%㋪ahm2[UZ"nS |-K|@&tuv3E} Xl'#Ŏ'?z| 6wζ7n6W:&9q4@@^bVl% @@&˖Muw {SUoCKr&:S?nW@w뉲_jtOB\L8  Ʀ&*//E eAϺ;q/* +{ˏM+kKu U ]1d_f&}|5D8  %p_ 3rδDIKws׺w(ԐJT6ww}g]azAݧ@@^KK΁d XLQ!@ ֶ| @o=E~&{TgwK x1u@ S/@h_ԅ y("z_w+rMtβɁk`SAo7 */-<fRo8  fu<% 0kD)z@IDAT{j 뻲I?j ex󴌪]m%_X{J=ROv0q5 /,YzBIH%xԟ8Ѭ!QIJjă/Y̋lh1"e.[f~xͻ4yFMy+(E0@&>Y+v&~tL~V\bB `==oiGirM31Nr UG,@h^L ?g]/C||Vq HC  QMm4@p@]}⠸ @'qS Yl"M 󪆷K艟2I]u 466_-C@<K(l,4)w4Lxn˻ + BBPfUe=5; m$mweQԘ;r 4c){)N`bz|Bu(gic}<9BP5ʈ`<_(x0]Ŕ$΃f@C<~@ Ԟ_bf1A;wf-eE\ӿM74c1'w0)Ŵ  4677%:< OeW#ݓx?w^ NBW0giRsi}ΝskZP9p@ jGF  ug?L=V?:2L Bj8·r(mm4W?3%"eKsQ-r$Z4, b_Qp#|4:~hxy@y9^n@ ?ȏrqCU@rBwjD\ T r,m%!ƷK0-J JA_`WC[TTTWA&ûw*"T.l{k;b_ @  ,Ȇ@|NR}~@/aZJ0~4+%̓oc oO^R3C (d|Bl30Sos8p@Y(LŨ@<@  z@ J}O*%1 `T9EdfAj0(e@E!I]@58 Z?p P[E |B>BCT `#Lz(;d@ނ n%6s_F'|󪛦c|8C  8S1j^ $IY!`pjUgkG& &7UhF_@}r9Z)T{`m@_(2BD0 ':%kiz:8FEexaT & {e;.|1c5U1r4PT=.HNvƄx08MXF_W\]#m\^ sԟ9Ǭswj]Wyހ&h[\N 53aПx Ϣ1(y QT%L0QG (x;x= +I{oO+i%7łMڒ~E1*h" 52oH@xA@a#5 躩iR o$\uK:r4 soRC==o>70g Uqr-_"BAxnEc14)'ĢX<:乄R;RDĹDUd\W뾕 .7ǸfnpY n/RoK1nǼN"Xe(d@nuKohiG쒡D M[ UζA;:.ʈ5ʒ7lϋu: e !{80 ?vYɤY]m] O_ ׯ={zN~+}yR0u~ͺmȓ^j&qt8ghbipqM y,-"A̋!7\Wʀ=?ɼ[ lİGf6_ 6>hlX!BBl$Fa<IQp "O A.+ eeelƎP,Oԍ  ʫb)Y" (=JI4]JRiy@ ?ݶbuD"Q<'?b/b /A 3Q+3U!%a<`Ár6#B)weQ-~KTolD26s~$,A0RIm[.?`@ʐ_ȑ  DțFhn.Js3|$9qy|4³ @6yb%AY9PV. pPN2\d *T.dt߾0Iog3) ;wY6/$}Kv}VM2ϒns\.\deWKKKrR-Ά9n…F@%~jzfgvghffiE(-aX7j%el*j Vڳ+JC='`j}yV?=HS70>HKkϮ׷Ճɴ\:TNb E~m} ijji|b&'hbb-+h@:GCghhվꩮ먹ܓVU)40H}Wóe셁=GRy?TKp,-mў/PȽGzG!pr I8E8 / sP/6'w (DKB l`hs[-@Fpm_\=mWgɴzENû.^W[e} A@fxup7 ;W_# px@5`A(B  &*y+jg烫0vng#o)=nW-볒Hg C7ý1Nek/u,`079=GHb(EMqJ^ ~%PVVFw\?eT|~EE9AvKib!9&PU=5f[]Hio - p)zxC_?pLq]Y?.[ Ny V@,SBλ a f$H-#b0c(Hsl8:/xE _HcYB*QN@ڂ/ zqCP89⸜"6[D ->FUKKK=WYWݐE3n6&(+ni+fB3dRm(e) D")[(1P)++O~nPP7 l(* cA,i8`CBp'4sq`$ 6,MB<#Β4<$ yk H A:<p֋ XrPb@䀿,-DM ^FH (!ީ^{{;Z  +\L%/o>az?zcoFNM[50z:$PpGF@ 'd@nz* :xL̤4ؓ=ķ0:z%D9?&|Ia>S$5擨[3Xn!VHJ7XAmV$m+I S@Ⱦ]Tz֯ɤ)ȃ$rͧ}, N)5,eBm%|'fEA90>MȂ겋 @ I~oJ=zv;Y=mv/WEMaj?pEk,S;%q>%ދl-5[e.k!pS IC<CrJP0/.lkkUAg[9ϙ\n;Eͳrd.۷k0Dќ|aKl0\-)ܖ?HA/-\oc|fHlw  p@4hT@@)=</t{ҋwVW6AXi mר)X{N^ktѼ"b\),ʀ@)LNNY  R3QʎPY ED.{ ~#@y Xj Sd> 6g <@JI`xtͣm(iƇwK6Ɔ_@ r a Fu=oW[ ~HL`t/wDp.#Gs3Q {ִֶ].-{p@wG ꚩ i-˘%Dž61.DF+Qo@R)h$dWRւ_O)p$#>ߕ9^*bS`|d$011@2v dp@,vDs]V4w-w ; Q Wͤ_ioTX ( K@XGd׮>q<X!F/˫V m#d⩈H(Qtη.g2i-Lq!@1P6'>"ә2=LDLq^:ãpyS :r4X,S;aK0}7$4 "8BGT]ʿL=wxgO4>ujQy A@Gî(^bW<MtnkK|}`J "?/ӮGT2|>gg¬4QPծv.9sb'}} 6Eu K&lfqՔpT: <'l$Z D " Poӕe-7A08#hݥdw}ϐw$t*jGj3rJa==m&[Z&8`":p `E 3P x 4p@Oww%.MÝ+j---=AL͑Mip#dRA{G @|B@l70tW^T;?[oNCMu8?҂RT9.O n=;=>k& ׹^,p.괕@41lh+ST bj )A p߹3j7 QO&@&0d"u_q\B cLN HQ9ՄSNɋz'} !e ) #@%pcAP%m&0<3:(CO%R $*x9z #a2@@_,@UpȊ`^ނV`(E`߾'Y`,P)a/=~D@$P@@TEV V`(G@3u\S+WSqh  KxUڤAG*A^0g<@ r(HM @ȱXo}A)'|E5G}3 3\Q*@~tM#`^]Tץr '"dPpcY!U!pUH  Ux]U hDܹs'yo ~<;}*B5b @J+z˃}ӃjA% px +sw:Ca8yyW=TL5r `m2[o| T 8 5z 1Mp7C /HZ8 guԾׇL~ ;*:uij,Wʝ#W 05=ԄJ@@r0HA^"׈}Ŋ p|:P3g &}kH ]] d 4a/p,l%PH$tbAS )]nx)LM8O 4C> ]@ l}}ٲFtsOM(N)WZvB0E@xuH  '0J_AΛ8Ti |Pt!M@ ;wa7Z*% |Ct5M%0=6M}9A@Face 2G!Be_M+#/`DTO/Η_r};P]v] a!%$0  70ǡmHWI'p{i>rҞڒ@C A!pOLL#$)iOdis^=zNc1/"U@1`(z@wkuҴBB>A3:/O:Mccc~QzHD@D?uDAb 2i;ZΝ;tϩ?!=Q^З?=bI>Ww(eH/CˑΨ%4P4{W)!ĻOyg -e n/pu).ӻ5k.d@DW3(+1F Oycߥ?pi Y‘tBy:!dF =e̾Mgdسgd.cƘ+R%(38aʹD~JmdY}¥7?k]/$ ۡd\e V|@lӓΛ;vTO]JOތZ!@~nBwΗ4;&=u(FHgrFo͑)cZc>t *m(+2dI?^s[;q lٲU=: 7wY2}h^{#1T^p^>~!pu׫͛ Pzr (M[+C+y̟hF(@k4s)eeSs81,:/x7ޮ)CB87Ϯ[^zic!KNmheAoasUX ]#jdP3nY?.[w+?@ggNT @g}VmE X[[ais"Wmc'3/@6AxS~?;g[Ոbe[OxK` xjj@;`Ӧ΂IAEؕ*%2'y!d:ܺĖi/KOy{Y[8?th36 1 J]i.9cw-[ 2G<+qI"0ђPCCG]ʑl@GUɖVr#V7hKѵg42Ш76Ν9')%'@@59!her#/W00V=+WPcX0z{UF@Dc x|<%p@;QYՖo}^,IVtf e* f Y嚅]Ux s}}}je4I*ӣb;R1 ;eH|U*31a[ I@ P?,++;%}*dn yXԖo4d2F_irGz6(ҜOeod>[B&s- x!@2~X|y<%;gqY4ՕO)Em[]ۡxZ9_B 8+9gcF57jo*dn(M[&;NW-m$ç pp,;tpuL*Maߕz5eR7M`KUቡ?Z##k{w^۰&R]}jbʕ;%}y fpb"xt'q&)H%+;]L'ޤ-C@1Gc ݻWE"+ծ] |#{2S==3]@Wk4sdF0+ݾe{h(P $bfp(ox1 ֶv̳2@Fΐ 1?#`\M.h(.:7EuYPc C Ot lKNؖ7u*XCZ-.р7D"a#mÇz~YW ̇?Pȸ6<,G ].!4:֢S465kL:BKypo3%#pAYU>&|]Do%bႏ@4#+)3E#:ӔxsVBEz 5U!,2dALIU[d#l:kkK!H T>>]:]joO0G`tLKD\w-ZS-ǫtv|m{T8Rmh63yT`dZΦM#xc#ŜQ=7ZW6, A(F& Dуf)5Oǵ  N\s ^ J~Zϻڷ X|zgUj8:Q쏏ɚ{Ϲ!A@  kC~1fG29|qq]%_gTA={ҷ:3Ha #]^9y!p\`XzI# ҏ.?OSd@I 0-7uQ:=T]qq i2dS^hs &wOV$7ySi|+7|ٚ*Si+Qm<l찱c<@~ yg~K18szg:Y؅5#;" HGC7)UQ϶na'b3‹ן8gT#fR qYD9vZ?;j1U6=Hߘgtp ]x-2xU  F 0 oȇϻV惒+ehͭ;_t%/3չv_ YlepnxO*+yR=zÝs I=-mFy_x+ufm1W"W)pwb+^yVE`XBrW=QmK5776[P4'O|r]]? ql222bʭuMM)?|vmZ .2zeнnϽ˱Zǒwvv.qrd+I']jv^!qApF>hH<0E> .XZc~mkG;ṡ 0 I\% @Ƕ*"w:VYe}0yOH4w/]J[+0:.zzzd$@R5E?u)S7 jqAڢ6uv*g /&-`*CD̤Bmm"Ζɼ@)/2E ppDoݺucpƴ3|U-5#GRerAۢ+g!l[;g=qȏ@ncvS'rEdkns4`ywX2MWkjo $IDATjfeke ;++-jsKTy9Ek ~E ;wn嗕3ןOsɣi5l2Ͱ]exrQ)9\dS/\pLx{&XFpKצfʴAYEnnz._yºSn6nڤ8NiӦ*yq*;vP6 =l&ORꅷDW=I/]3hƶohY|i\Kd@z?:m֙Jk'tdu2ba5}h^@Znm> ԼyyzVѿVviGvؾm.7N'/;-t}q_nX]9".P\/fwhlt뙓aI 8{zzG>sJq59G>^:, 1U@Ngޑw&pEFܶy+!ӿEr_o>\y׼N jˮѶ B2~S뻫izYP y_5~pF̞5kdd9gϓϤCRמݪW:%(1J#\2].{i}*d#=k4 @뮻UgdMp}qjђ%L 90 v9sg52m`Μȣ9zڷoꕏ2w~nGmaMZbTK~NwQf*d8@!PHmB`Xweǝ'ՖIdKڢѭe4W JyһW{;'^SgUg=S͞=kNtYjOYϯmZ~J]H$%u $sG3yOK<|ED .R>/,T:d,૏ŵ Mݿ(}L:E͜1S͜>C!N'tz:3pd:t=^=в/)PVVv3@v&SNUTce+CCnƼQKD9(ƨtL:w┼oBk,'3xG`:ީ5A.޲r?0=.z?6m GLpnPaXʑ)!0RUVV*NP%#*+*GF8 QQG!,_CbA02CXnGH+g nk, >#1:< T+W[&ۭrnNUՎԢE7,U'!&b@dw&W$(Pe\>WTGy|/uYYH1,S!YL;qg*/9~rYι ڶ-O_^jHRsM*R)ar>G|Nrھs''_Ěrޜ`,n"` @kːSk5X<5)@VhR+k#= dq`FITCUюDQΓV zHKf:q=VCie`oqDOCe??F  7|_IlLU b |g*khj ~D~86Q/ P2[7wn|݅wI/%h:+K lCѦ DC  :ӖD@vmZ?Y\/ \p L^Қh ,@[#P[6wͿ꟔@ oC1KWw*"AmYB @he PH  9 %Pιx9>*R7@ SFd* Ց{F*ήÒRّ ЪQ~|!~H'OȾͿZ:"@td* N𓾩t5FhLl " IngV~C29mFN'Z=-#UC k:"N k5fRGmYo6Lsfk@ FTZGt? ?\j˂F#RG@ F4 ;e˖U-oXʏ]''^˶+/98_# Zٷ"Κ*z-Jҏ9j5@@ 0F\hjjK(? @8Jk_h]rt`˹= 1b*de'U'ӆ 2&n< nJ^ .&\l+]Gǰ}-7%/Jo玾@<-CojG35ѵl9_35>~e̲X8zR9@%FI6 6JN'+vM$x.Y}2RVok9w~7Λ3xɩhleY&mTP52r@ummZ5Jޠ'L%^!wO'@ c&dnOF"*$|duC"zS]@ 'FGb<#7~CfR+wʂutϤ(E ~}ݍY;Y\5zTXUQYpI ) Z^+^sg}lfga/Iogi@ 0alxsc.QYj%}!H_Ommj[r)  PSp]YEhZŵWi'wo5/#HKy5]`Xۛ+y|o3wݍ|C ֡n P0D")RW q[ ͢ }*d$q:|mC< @mB{θ̭)oi= &߷ƛíD@ tB@k}R)~ثYV:::Rbi[U:>nK> AbVZYvAƣvwu5‰|l6*H$sy|Km=KҶj~Q"[Ǻc [[tWFYuNVGZZ/5~Zd\7 G!hnY COʲ=Jwg|"S+ +& qgzGRWϗٜ3[9PeDӺ2 @,# @ .ZkGjv$cs Qc"SF`!K p WVch􅓎wiB&yXv$2oΌ5 tIn @fX^)C+nx;Rn ]>!dg,bG2߽q2)ܾ\ iT 2Y䓬?Jo@8 /@9C65?俌SKX[`Wp%tL7:;CCeUtPUֆ*Z(>i(7XͲ֑"ԨeRd"Ѵ5F[n:_ @16[bbT2ۗXb%k@ۅ l[/] :7yю>6'U @3Zfk<qM2)(^ @emEIxImUx])\zim<;#%!͔- L9xW`dy3ܿS[uN2%T@ag]XfgR˥(@x{E@RЋk>- /bW6\!_?*X?ꄀOpVƱp)S P2 iDZ](@߬@QY OOuou!eTxIY}{YFdTQ.F_ۤc7Rɨ˹1Eo ¥6 @~dq*t<[dAm*Kg 9 9t)_A;&IbsH<66׶/F/71"@!jjjPW)]y(w< CyɲnnJ Key)Ly\v)mxSRC{dM&K@(&'`(%׽GPʚby/I%.W떽Q}YbX) _D$;V_]SWTH\*d/w/!%+mzG| *ݿP}ż UkiC2|)R)_52~we$;z?T|$5UE ,[hܚ0Ta|q͒%Q_#oQB^E3講ȫ[솒! @p U٫;|\氾W5)G'hzێ]G۵6Λ3A3\̔_|\%I/@6A\pv (WoRڬ%t:\{&}Oj`֟zxnT|WWV%^ccc:+OU.Rή v+EJA)Ĉ^ЋdYZ}]KQ= ۪CY32xy8VJ)rk˗vs&MHF"-I@2 C0.Gp*Od륙''Zck3Ikʡo3Tw(sMkxˉDs6-י~;}t P xX7UC` S*;T!#$>>(sm薮eM6OGgJ@nfUcBZr/i_}0:93Hå .L E Xٶ< _:?1xǃ}uCktZ}Zn.>"FNLwdʍ?qO|?w&i8C|' e78uڑ_OI h5J5 557i?!05׭'X/%+v.IT]==F;ZcD\ ':K @6$Z |@:.7Qdqlj@L 0w}~5>7/U\TwZ*ℙkhʭ;Zx!Q"S4 0$FReRfP]2b8g,G!'^ٲjA]?7r?fthl:y#XS 0YG&c'WP65N*t6mF}J߷,ƹG%Oۙ @ PT~ G&j;#ڬHF#7}q}57m}A\^z٣k{~ёr5ggV[{a0N~?MG|G)p/@HP @j.}m[ߕX8pڲ緯\3C͒c>#OlfUbFke%yL"'^d{94ZGhj^yݶC3Jq7#چ+d?OȰߟ+8*7zbʧƽJŭk:|xV'T'@? ֣ P555!mz.fX?55ןcٷwȕgN|5gKRY7ȶ40kjo4ZD}~ޜؘ0 .G(E@H@ % ﴌkW_XlYe~ vV-K% țuJoRPǣ>WX\[e'h~@/@ۈ", 7/Z[5ʼSfz˾tʲJ@pUW9 @O#-R Twꚩ١~aq= @Rk  @vYMۚZYW#tݒIYvc,syk Pp: NN ^X\_Ij> ym\2GA樏k @@&',K:KI1grH}F_Q"I?/@8Y5@1.ƩSR{[rg򀀳\\_Yxu  xV6 C@]]ܔ.u:Z^gd+}L=X @Wp@JIfٹvS!].7WI]YſV?jF7rF@ Q n @jj;wJM2$Z./`A,*%[crx|Ŗ IL RBjS @IOVkngD/oLVb~#D@Y @ȃe]V~Ƭ3J-?KEYeSeR:zhԱS n (! 0E7̶,zC .)yQ&* @txq @dWy&tˌ1WEuU@"NuW,atB@ & AXlY˕X6|!p&xCRe:eZ'U:@@tx  Ԕt%TGR d Q'J9˶ʘZl=7lr3 v<2D@p@(†9zto+3O;K>Kn1J%ք2TȹirΒZ<^ӃrL~\3({st2s{^يoW2CyM8QR"                                                                                                                                RAIENDB`PKQ& user.jsonα0W17sI-l-88 6) &*_kl_=G'x49wLߏ8%p|x8pv liUa+T&#Tii5Y#<'@T(U*n¡.u Y1;0LV$gHcl‹!PKaR$- meta.json]K0mIfiz}7PFHt Ii&2AQ{rsuCjIY8X֖ 3$*G"Z |\md=xa T(yQ2- N)B)UJN^LQ:;3~e2c'58m T pzznT)AK{O }R=v:Mc6̽*|Pˇ K7z}x/QsV &MuA5}iy9nyu)▿oPKNW\ R previews/preview.pngPNG  IHDR<>lksRGB@IDATx\Ǒky ,ɲlYv8qvL١?$w 8ٱ3J۲E+X1333,g{5Z- y0u2տim:!@*        @@G;<®IQ!       aGG5)*       }@@@@@@@ @&E@@@@@@@ xaפ##D.jjjjsm\ZrSC}=74P=K(ؘh8W|<%$$HĈ:m @m@7PEE9UVVQQQ-,3gٳ稤J+*<"vEpQ]]:(TC ]X%vxDDJMILȤ̬ ΢lꐓCrsTCrF5N.@(ӧ3tQ:~8O2pxѠ<3D<8hhKad LNheC뮥\ @@@@@"H!]qX6lLjWK`CD ^2na] 99نL@@@@@@i x8`oee%mپ-Uk+jxq % ki{~     6!& f۷-\D-cNPx׳] ʾס۠{.FӠ}WC @phl >rZʛ;qzaiC=_NM+)=-|p"#Zĺ;~f͞K -c'Ou&Z% O殝;7Dw~ԫg@}      `KV]KVֈ~#믹bbb xX@+Vĩ_M tخBo`!4o]wB_0J@0ddy6d$XŁH.h=qWcÇS @0} ۽w/}6q*-X>ط,,::X7cK\b(@@@@@|%WRaz]Aa!}6y MrָYpiMQ-#G\\ wؘ)7'LjlNHYssQ^ΰVZI@M;dc]&[i@IؼuG$:dH /EC.D?wh׃@@Ze\ZZJ&L)N5.YRg?#a2}wp\`0@~mbVYKoc6ճ7VQQQt~ V @pbhMߤC @ :&zN Y xWqQ^h3RmaM=:x!JHHJF  P555TU]M1єD 6:|qzh͈aI Po:ÆOGާ@@@etI:.'鄼s/ofqŎ whĤD%~$%z;R.k.ԵK޵+uqqZ@@ < `LGBG;bU;Μ-W!DQۣcN?G]oT@"@Qq1m۱n;h$\:It%t4t`4pB ؚd6ϖ\.7q}r熆 .ohzj8҆MI^9tߢ{ﺓ:u U:?+W6 E&9&Cp>NIx0"҇xĿ> ;W@O=EMl|!Q&}gK }ui_n>t<}DګĩЈaG*{<z0\`Lp!x8^ ;4a!X3Am"zHw~O~m߅ XK"sD{Cߦ@m/\D|=vY^tJ?34dХv0 6@`TYYI>\K@^0&_8&C𸸝mw\_yMrf:l:0"zpF}[Uӿ)@sNgfdзvfk>v 3,믣<ou\lN9yC `LG_eLjㄬ}(/1ⶇQBD@Y?saB 0rͺoՍ?p?%%&ZU|n޺|}ڲm[yDV+W-_SԳGH:_1نbL>&CMKtW(oS[`/(8ɱH-0eZ G}(=->{̺2zy૯dw -z{!z+h3߃aPOsѸdbd?gn|@ `L68qLal0$7H/{v,2C"'ccBzH~H 1r I)!yێ0~_TXXdz/~I2߲r'Y@N0&54&Cm_;wqzw9y,v8S+@9{&ွjviW3gtnJ%-vOs-P^ ,OO^M);?8x^F%1ٜwʘ Üs)@)0' IӦӇz :_̘IUU+ 9'%%؇Ae)KKǿJKK9IPy<= 2D+ `LdK⫙GO D.MxwpA%B=wNY5ܹ¸dY} ןN>m (< <|2dP]d-4VZMqDz^_Po (lK@~x7h9V@|'7?鋓RB|<=ö5Y6|_D;vb{V^)Um=O>m6 c}׎c2}KT]a $ G-F O@׎4 mvŝF96cΔϧӿqL&O6%o>XWrQ'ƎͲUnl `DܓXOb;sݢ%˨ AcrB|d!n춲+t1)m ΁ bza`u :qoJ:vhR?$8_ΰtY(2H$ ȖV4`Q0&;0&C𰰿|8Zr &f@$P_e+WӼOh=LsȚ-۶SAГ˻Cs/ 8AmlF6? ˘q@ T0&lzLm3祬~OްlpZ%P_ߠ~CVjH&0aTU[nn]z0߳Yysmc w 7b]rZ**H `4F5/?+dsSI'N^[&(~W_yN8ݸ™X|MO=>66hμ G -]d{G# `L65yY5&C0%_~"HQ\ [z G5/jS9nwMWA[E옗Ldyų/dz^yح%ǘl4QkbLar[O2֮߈,&sGqK@Z&MD@ѬKސF i j+#uv:yy&191g]{f""V})D:_|Opo޶yɲ>'HE7" dw9k@ X%h!xߠ av!7UU뢗'Uo "@eUMh p?I-48kd , k LcϨwc2g$(&Җx!@1;J;u\M\byɮߑFHI@{$C):L }A(!, `LfRf<./'SF ` 7&!@S3OXy;>fqf.̛vi 'ف1Ifd9PGEEG;a}ڈaˇZj,s/#EzFV-m<<Ř&3P< k3XgbW,m9uM@ӐrF!0D9-=XMhgA7֮`MTl+"* !xs:|Ϛ#D|-G@Dg$-"@8ͻ ^QU:d0]},;HHM๗^ȆGKq)0&;?c2c\>7jj!x\D@"xT'118J)Ӭ*:rػ/Gl3ϻce-!1&;?c2ڧ)-YCT&(psdѶ;-(E@ zom00q*A0&21zLapزmYuc@v `[\j mپ660lYFdeZF2 -±&w:ڴek}ձ[K;Nll '&M:20Wd ,:vϓxyKۉt+;RseۻӾn_|} vMG FlǨ1cbjxwYc굴}byyXOmNrĽwϞt7Y a""к"V7os Di&1cqF< l)ӿz 0(oS~H 5QUyG(*:H5˯ ƚO?4*qT[T-ۢsc2M3bc"؏6,[Aq K>4ŏ;{[n!}Xj ?oC- #d<|K@ttotƜ0Qҹq7,yqO@\sm\k pRsvTAcrpp"<.B%˖Saa ;G;@TQXTB.3\FIؖmΚ=Cksy~7PvvӛztFiiW#fͱYaaSQq1Z6,JOc0&_̤`di^ ~w4 H9`vdh9%;E1G?8)Ӝd.u͡RWZ%,;wHWA#n];SfF&)I&!%ml)^pP;H旇K/~cJNJ ZQAwxuZQ4ʴdɁt`dg=$n6% ou]k^2R A#Gi)vRg_͞mif@7\-]9b:usc%;+K䦑Ç{h=o*#KW>$8f +pqpDY2ȃ}wiҋK?ϯ4nd7jkkl搕-!>zz2"9c٭GdƷ7"hiZB:O:` ' ^z7o ŗTIeljx{ >CSB!+Ltڅ~ Ӎ7qM*qlle*6ms B=!ɡkɭ:v,^apؐv[ [&5'Pɱ}9a[,{Cٱk:|PX|ޭcc-FΦg,~=&#̝逸dF=y C] 49c~1H$û'ZG@<֯H%3sATIKӊ0qny]:j/j>| пE|@ٷU8Rdln;cL>y~}: !xE }Q[Unٶ>FhF@BNbc).&yE?7ՓsuQV:7ydIvRmOqqO ϟe Y6hhCuuSum-TS-++{oy{l5 aώkch1 ]!]v:pH))Oc.&9]3K/qv6@crBxK$mJʩYk~ӿKw;]v 0a4; wMꐛpm$89};O՘ۭkbL6'0!MV /\1&ۯ"mLg~;~G[o/Ҵ&1l⾕Ic2J&[m{8eJ]d923T|h`$S 9x<./g y)hey˻,S/^KG<{u4ypuF 5/IR'SX6o;(uu~]kQ3clIl 5]ԜFFWT۵GvRvoN[6C٢9C,;[<$ d{9_?Ql޿TA4==:wEjdZ/hk§'~Œ@by'4] |Q?*+Z¢WQez6NeJRR"sXD E5,=G$K)'+JJܹs쥲,DXxo\<@*_|5$Sowj+s)Syʩdw{>p=tZ[ ;aLYwDʘ Ïw*.*]"B׮)33S=gKcQC&Q|ot.э5^ڊ`a`]1Cx_q>Վ"2і/U: ($7y76:H(eݓF*G |N{بz'yvc#풝J {Ӳ-ݠԖ\#OLt i" "Bx;"ZByy'G%>"wK{#^96&NlM6|,EpF8c%ku {ox<\t;kz~[C~"qYS`O`jK[purcޡ0Vڼ_DC!} [6oQcmwK\bltMGP>&C[\j bwwDr:s6ؕ5J!SoD02E%%$BtAIhfGDPE2CὡweK'Gee9ʫTypmsD4ZaL8)D9&gx,4 XڥsgڥswWcY*O!x4s/fg1l⁕c M%gÑSik*nq$KK$v8-Q:P%-h !/]ŋCƈKˆ~crNnWah߁}A:}b+ P226q?Xbj߰#*f;o3otUWҠlin {?6ݷwo V)))܆H0 by u :bLvF Ç~(vrKԤqB.ÇhϾ}ta^^ Yb] =PZ\xeeODNQ9$Ah#i]tI? 8&O f |ڦV;1 2؉feS,1TђK QTTCżLJJ@K_8/TBKUdM Il*kp8@U5.Zr%ysm ;Gr,`WA_6b+Z/ީ\ggg^0=Pj<^rWV 2d㙆*p!xkdWK*{.g?k3:oNJw5g%){)OK8ihx4%w`IKd̤yPP+l6LUoɨ1G-/xɊxo$M}NVX[.lZȿ\B@`ccC@,eG*'t'iAoͲ9,[vۓIcs-d]%zX"KϜMH< 5F&f1Ht5!ě$@=oI];Kw%k'X'Ǵ$T&GOX,+\9T}M-] ՓpMn8"D5zpw:UQYQI]vV9[@.f)=M,P+˔3G9)'މ' m=5T:]]!ǻ!Ye-hȠK}dysͲ_>쾓S&FLKZ**+IDo|f EIl(ΐfc2vӝhbO{$SwMH-^xϓ|DvQuMcH{f0 y˄#~=[Zb/vm+OL%yOvz-ʄz[6!1KJr7..^Mz-.gYi(nHٵ<Ca{ug2x(U[J>)ȡڈKRpD-?ly|&R,}۰kˇaPdiӿjYy{˩OӜLu>L[Yyl^0&fKFВ˔0eOl$8#rxQ{w YB$t$&۩}l]tqѽq;Ա)z x;|OXh0=VHPټ1==+ dbWTl{XMJm urLS(';Qနw!b/ JiĶ@}4L Zo}/ 1$pp~/Vu?e/iiթcG^RFW,2J1`/!x087q'}U7ӛnqUGDVF.H 3D{O?%19PrG m57XCƀN4pdAXq޳7<̼Iwd( @ < _#<Q(H ''d41!Ae2z -CεO.ғGwP JJ@mu宧IS~` @'Lr =ӭ71P% KZI D@BBG3{Kœl#ע#Bz:drwPU^2a5BxhAw e'.wڐjJ9vHxO1jDg;\w`zrP`m([WL,{.Īa%hٺUAF0^ݽIxzK<|<;7~]p8sGo|A~%Yw :u@GO$@IDATwclm&[Ԏ ߯p%}Yv]<]=@ [#j ۄ)S9fKY$[ ɭ|`y&{^" o8!fby˻{SO{o(q=DvYP|niϢWng^/3V#YkUn4'm"Llmu%_l5<dž/xy4"09;t҅n@@.9~Cw]l 'ëdW^4$N,<=Cv\ M ԄQ~q)3=@LDTH8Xlj5Q n֓]=W;pԴ4N0&Ѽn5{H j\Ka[@L1/˵xְRDnxu؋C<7XbAJlmv^)69m o+mR#m", s7,D51Kr]qB.HlKЇ>8u45NIy 4!PWYK6BjjAG#E|He'LF4V{yxO?} I)OHKJ$S&2NLJ)*6DOc/xWvKhD' T" r(mp]G'rks{k}I$6V%!zi-tJ5i#/N.8U̻[ <1驩*d<{W^`kA b 9/)!Z:ULXq5"4H Gnɻ'gQpP'آ97Erئ/"ɮ:T"G-nl*L5Ɏ-V 6µW3CshJ M@'l*VV <  I xpk7{~HjzO]DUxYBxkOIb4Jc$)1Iw;kK!,L_Mfd^''Cg"9qo Is[g-p *ղ!񞑤c1$ōڥ 4t`L[b,E'lx ';Z@@ԲEK @7<%h֭jo*-|H -/3^Me#-=dC{/I4W!£[4{4V{Cl"l \^U!?Ge;6o][7ni|rIS?]}oW`@W/x1ګRt۽D4PAa#def[a$@e )7';r! >xc";vǨZ^'Rp()U?$q?RyK?µM!s),ZUV΅8tpaDd8rI<Y"0% SS_ma(@D6sӣo @#тǡCiiX^/iL%[Z hu&0CCKKqO]tQ^"(/ ]l8-˽&NB|m43AxXj5:rK^G #A@`o @+xȤ}v Rp=|S)?Ryx-\#8:[qgqCP%/SmceJsûBgYxTЛOwj Px &r%$a+"R(,*7~Fc9 ۣ@vx,o}G?M 5%j-ѢL-o +;T! eJ o!,LMhڈ/Bv;4le<< SbO߾.fs+a\H@@ @Rgn}Yǻ~L'Onr4 ǻ"H"CMb/8%{=A: !RI"lhqG<7ܼK7D`/Zue,n$ }4Dt9zwm~bb(=JK9Ã0))VH  4'OM[ˇ:t #s{vvaP ֬7"ك)ZWCiGH\,/S_dGDEl$wJbEM"le)M- {CjKč{C<9|n-K9nu-]sՕmA`َ @H`μ.^"q9.Wke9SB+Imߪ rywT/W "R$?mwWx@, OXb,7ג-TJ?CކA@6-YFӟq ؋@D }:;&jXI,"A ?]W <ᔤɻ!@D@bp@'7oDp #SQQ1? EZzyx>RZ,\[^z%cxdA<3gQ)rBR`*l6 䇚em^ K "7}?,Qs0xX#cRdXĎ 𑀈2؃9.^B'OrDkΎF//2\ Z8~Ϸ%Rd6-  `sd9ve&`s 8,MvzG= l޺N>cO`VuW^;-ltpS$P~-NeӦsznM/ ~IϮ5g^}^_/d̑WAjoDuD%oq4H1wA:"0֮,2 7<۪Rll 8u1|ߣxıOznxT̂gv:{;@U} T"}0HߌѸ85p oS__ݼ!ZM"[ xl/|~ݴn&'<LowpB[F cw5pI @ v#vǹs˯P-{3}).'YM&JGwߛr++b- j=y!"RqIz/-+h \|=Ce8nݏDHaa#`fF: nJo7:]u~(wJj<}R΋~@"^d9HEevAA! !Y.$c F}_ߜN`g"wӻh^ H8 F Y?^'P/cILd&O;wH;wlJeoY IO ]Iwj]Rٛs'O䠴L <,**IgâJ #nTɡtFr\JIHm?Sw[? '}ղJ@αqI:}ѿ);p2;~^|uo7 ;kNy Øvګ1 hN8' :V@0JOږ,[ɂBpA0@[W&d2C٣uebR q=x 鐘Ks;&UUTYp;[@JQ&" ه64%)r;P(;3)..N,_[ʤ6"ȥI>}ёGxKJk%Xl40A2yt&lL'7 V8$UW  `#a#xYp W&k2єW]OJHaOIr;ȴ4LI"iԻg/%S"4?6A/aZⵓI;N'0MC/uӶHK18kxO9~ةGХC̟&g˙li[s!.m~A@ , ȲaٴD ,gs/̻zAO6%.A=_>ԁ#D>54{)DIKK޽zbRsT UG3~N''&QnG8xTG HqN8B_>䥀OUPm!|䪋^yِKT#'9`#Q,]N5?ZkT@/xpP_z= ZCO8xէO/Vp:ZOvwa3C>Nq9CChUb!)gp܍<ڥ rHƻ}|YWzױ#/Ա# {9bDn%WV /;YTgQBEӾpǡ:V  FKo5#C 'x?k7Skk&dAM Y1l >'!zJUhQq 8yI*,ɗwa+"xeQ DŽԹpKm%ь232ꫮ%kn: nCVjպ ]?w.liφ%8 aG o|aתFтizvJ<R!C+Ok˛LɻĒ꾌-1^1@jjT~o9dT.ә8$m&owc[~+m‡l|P~htQ%HJ?"1nT훷b (3@{?irw{ :vs4X9 +xٳWmA3diXVp! /F q;d!e.,&u]뉼7C!n?qӞ⠧e:8+I_!t=9:p'R?-h6Y% K#XTBkׯW CưCslZ]۷+ ';J@ vEǎݺ  `s<>kK.vTK/ˇ]NIKnoCVC$iaQֹSg_-Ю={J:7-1m\IY+3ԟҍ7r@BJ <!-؛+W}A`Zx,W]1믞C08!"LPŻ}[==s <} "⇙זAEx9sgKV2y&O(0O~%ddtV"K\dL{ǶШk m{@$e+웞|tE<% !xزe`G1co/̓Ya^ Sk1!5sT䜈:/J:A(Ϝ>KgϞ/T!r\_-k1m.S2ޑōN:YjLЧx>GKfʕ{ȼ%.+qWw;7n d  N#pqK[ i^8BB1O;rs;Qש` {G"fғKڪ x˹j7Y"Ou;j@=AgI29Nޓ˞YYA F6zm7._IgΞQj2s/ߟ3M6XYF7k\'ǎQy xأ)`XBL_}ھk7?]3o Z.7vՒWͅT޾5Ň>z+AQ1{~|cUUr6 Zˆ$eiMRR"%Ȓ˰Ff:ƻ$5M%xqՌ_,4j S'3Ǻ6?P1iӿXA>^meyWozud!KCaZ$#`{c_WPbxvhۋ sk\]OdW!Iڵ'155Tïjcnwջu1ѱv-啐@>zqBHߐ"c=ɻ~9'w(M=pcU<.ȿCNHO};`#Ko՚#SEa  `kcM˚hD'33nzJINIy^%U2B  %,HztҔ(,Oi)X :y,_=k!c[3#UUe<NOw @@%Q(XN]y׍g_xjkd9q;d"[Zb3Hiىxf_rN^2{r#QeS8˨yL%R݌$c妿xi3D-0eZ gwG2O_aI`UTYU58  l)xTWҟ_xN: s=, Q#JgG@o"regf5^K<[I7'cߞY- $EK҉'%wKwq{ B  PlyefC[G6lfC! zLj.0Wh"ݻvC!he \q3/{/BH۫&LAXCR|,Ch^ւ yl'x̜G?TCԳhcBPc ݻ*1h&2E2ĐڵgyXuwx7n!af:WP9FMA[ w颃1l}qh@tt AI̱Bzhێ7z')q 4xju@ `"z x eddP=!f=F/C8ejK0&XEsǎ/yw4KݳocAں} LGp#0leԹSG/9NѦ-[iCn*0esDdY G4 X&xL:._ejRMK&* dNB!Nɧ| ࣛ&:,/xKWy|>V"*un~K>~w}w1γ'o~J,e%){{-&QPK6uwu=_%KmsP˥KE蛞R#mЬi,KC%~W#f4 )GE9ZX%($&bE4~Cur_QXPwU+.O=LQݸ"cZTO}M32]~3ٙY%hf&\MLwh@%ڲ9X% 949/ij\ OI)h4RRSԔT52$t-ж*cs/Lo"effSxI~ʲkXF[߸˜ xDZE}#ǛGG' FLrJ&U-PYELqׅD溽\I7'^QZ8&Q2?FH+!Mp"K/ >A{zPD d(ݿgb[<'%&e䤥RԩJ|TA~vii6qikk7Jr\:dʅ#[x3͉N%£,z9Ș QIpl{p'&1H~هFBB<'&D.H  wdL0f 3Ұ\1.󠼢"\ \NJn+C¥K.;*'o{:RDepb&TR$('$ecbk-!I)_56o!o~q,1m`{GQ?J_U+$ % E`Ŕ¹m?s/JkyU }nU>1y; x`')C8f0 FJ0GQR#*\ 2LzKT/ &L䬏̝PWw$NFV1WC,aF 6>;o\;Ke|k0㛱#lE;5|B)b%ysZ3\E =PcVsk{c09-f$L==paǘ`h>1﹞ [Tq|!<)`H H,Bf}C4آ//H@us CGOnql?höl.?ɵ `:(|UfzfwwK| fw- Mx ĭh4 P`gl}T;FvOdBL}e=qF\0jӣbzWqTa`(A:|z7EY]Wy-OF qD2跛Vp<za'&B[/?hxqRk''Sǽ5!bĘ."e*Q&O̪6e2UPb&8xp1HtB)Z݇򾵀 ׻<օ:уwk^Ø Nyez;=Lm覬RGNw _z3ZKuWkC@׾Dqm=-7 d/M56,9~׭sQ  ?7nЂvDU,w `)/7TSPo8? kݒ@ʄ y9HO!4l0 Kn,ӧ<=to50xc9 hȕ2@1aD?4ETztn.w7NO 朡ʼne\~MR 7bWS}C:3ܕ3uՋ}gF11LvV71qwkn^GdvJ3;Yg&CMm-mݾnb-Cm۱SxI@A`֌TȤ;rٽݼ}ݸI&" 9~i2~ lZ;:3)! ۢA"⊻)):{30 80  0 $5c43-<&k|̫#V Sku&"!$ yU|!Qc`p6EX1Q_`ZTT7xGEDxxB֞O+hK{jINlvÉ9 !?H^ZHc!<Дst*ݸy9 L23G r~VulU!6ګ9QBg;ҕC㣨 a6f2vi0#.Ӗ2 ֺ  2=Tw!8!!,)wfh ;T/ R,tn  rb'E!"!,m nN[nNL3YB9 [fx3k܎innlg B[**+i?/>~ǝeV' t2A@1iԩOԙ6N` 0*حŮݘfr7RGC&[Y=q>4VM I&dK NZr2|;i(V,1sXmk 5 \Uy L PM_馌9=L:h/tSܘC!/{dÜѺ\JV3r"$nl'8bɺc``협kv XC} )_\ Egp2ݺ}ǙY 6l޸AYQE 8 S-}+9wG|-odk,ŚaߚoՃwTy3xIb-/|6qԅFL^/m 9bҪeugK`jk%=LvMgϠWlȊ\{{ P[ o"g˟uE4-ŋ(5%Ŏ=#S ɭ s0h2(KZ'նnyE8_p%& z( 1Am;p*s=ȞL "Rk"uǛC (dYnM :clѯkk#+kϚCᖪRukT9lMxW<_0Aӡ"oڻO Uue#T7ks'C5#!@41oo޶w0R[[Hl twGS)lغ7H_sS(W˶I[ C9>J]=xz=5Z/- T8$16P D46f%E]/be 'Oi |9kzںZ- h'IʧD^͙MwSN&`e;:.VP4SEbk#qG3oeh*7f8$NV'EY {(y4Qlq|!CVR&P6{M%SeKAt6Ugy u?L=4TJM&JV:AWЙ?:nwѨozt4+ZP:4‘!Q^QA=C߷xOE_mYA_Sfw۹G>0dGK}\yN=Eo]X7UPIq1BZ4'|;(}U¬9/&R8bh|nأ9l%g@"K#9E>AN|@IDAT# :ØnSָTIabV]uT]U6,(㙽k~Cp<,gKBf `{6o`{ %ylhğa@OFؠт)s@gSGK{\XDzs2*9+'oz|yY9}3pU<,#=!é?MQU*Ѭ-Tهg8ys׌dl*Kc `wVC2ͽP· Ds/P m IJ[w(DcmuTqj9t$ioH2EE<.GgpQsT? s PxmbbZx(aD)ŋZ2 @FH""@"A˗,b(})-d!A`h2<†gP] -Yθ\tMjij湘 Da\KOҤ-#h.'r.A령Ɯ0'Qbg<7ɊyiĵkTRQL=xXI*Deشa%|*ގ]{~=A@يF:uꌶ`455ѵ7}[k%MMLzN`qx1[Ev<.c+WHv)ң5ʏ/Rg;xMyf4NR =y;ܹtO:-o`l!3i ]Xx!U~r:zLy;(_A@+&<|UoPC<G2lhH.'7(9b-T2X 'u4eR5dCK.k݁gw5WrKD+]D'  Ѻ5M.Z9$#eLx?uVpUxy44ӥ+k1ңq6?ZfSAŸ[‑_}uA,cXvs8\Ig \shz5kkRrÇt.z\.@`6I^Y E@%ž+I$FF £._Ľ[\cdM^7nަGF9@gs(UA :[RщLf5hz|i&U0ؾ5I=Υrx4%;)Fʞ= F=k.\B]]Vw0w:::"l2]A@3O";4,ϞꚚ! A@755t. g??a(0pXA{]2mY eȅ0S}aQsG5j7cC !^G*&x93E&ks3׭gA$am(\f*]pfϜ1Y9$vEo£!a[αLǍWU\8km /؄6InfMj!WB+ĺUt*+rt1nKr$5Q)=|^ tyh>3ɏ&?r8&bXw1ɬDa\b:9KW[R@v$#{:aq]TIJ++A;4--m*yCc3[méʕy  BxhDLA le£+oszón.-8r޲JA ,Y%8HN11~Pv'8omk'w cQSIL;@0`ERSՇы0{O$l7WhecnkC 8tZTbJgmn7uX;nK:::hݶM@ sxlXSdA9v\ YEHA@ _F.ȁoyQc6"ML#gcv=H먨hjoo>$"h2UA@#6X$ZE EaY$I&'gV5 ;UоsE*P7;w-tEA B>u k1ۣ $aZJ߄B<"\ de Eay;o߹d cH~"oٺ--s!Y/v˶4A@ %zRS-uCreA;jڗ^={>*I<h%^?L2yDZW hYt{.\D? 2 D6eVP:4!9pN p\Z8^ߕe+W9vC]-Mp!KX _H{#xp3KNNRov )|ܹC9i *̥JgH DTW_FLvtvvRO/ &9,1!>MB%^g"dެ莱@*ہD~ow {_ěٻC@h=^N^vN'tqZlNb /OiT_`Yd?m/(D(~)7/\|҆G 7g YN5kэ$Tg)A DE؆5k!1a?g&{M_|b5i< £pun `2G*iSPnNHWØ]*7B{TVVrjnnV8&i!!==߄7)2w<'*GF$b^bOJLJԔT`SSSyorX:6x4{XQN1w;6<xPe?sIu`m{؉Tl&  6o\lsXX%zDH\nx`oaCF$ ~MhwS]m=WTУJEx w q1瑴''&UFFM?ʕ+k؃oթ44[[qDH246rس&V OY]a5yX^A7nܤGU}Dk0|3y'-0^` 6[Cvڶru.zoVFL)ճUQ)^2;ϕf~*X7nݢ;SIQ!? 9]d;èDē'yXc*ٻíޞ z ƥAvcyL߂ Skkt`x5[.jjiFa}^bVr2b!;;!^ h==]jm}skGǿQ-rySFFʂ|EHJ +siGx`Bv 1b XqUeɃK(d#=::Trr97wPǮ4v(*))aC3]yI ;p4èj|Ɂk!9`E2WK0~lFX.Tkիp1kT5 W7oqa/9ŕϿlGH,vhXH뱆IG*>G5J@8?+=Ǐ:{!Ԑ 4gLYAA_SF??`CueB[`1kPx sYaC \M"b~ۚGii*$1OZ&6$x̸6#&/[Jd(|"=X`rsct5`n5Pͺv@RУ b$C5l}/_ m|@p4pT'}r`m}ѩ!KbJ7Iu9qo!E׍o! C().RϠ7O::vs(1d H77c &al9Day9R<d͆fHNJVF& = ́qIa%=@b9$*ٰľaTak͋e]0%%s,Y695Nq"ST #hX 01<##9 &qdU wQ F%OLHsf lfm?eeձi¸38ex@_hpZ7q֪V` ]|\,1ȕa8BcB^^PkݩH6^\c]bk `rll1kGwa,os͢>c [ܵw jNl' ~a#E۫I@"<ӸhCS6=c$冡02ِQG,H>7 7L4@í;Jc lџ0^dޜٔd2s,^3̾ /=4U^ =ٰv-xK^c;3aᄚ>iD nQbXƾ@=S!}G9{4..ί֢ nٶ]4- eKhfvkI_/__~_xZ2t*~#01I4xt`F&wwv)oPwmP*"F/Ȏ4w5c3 d^@ؽAކzU)% H8&6>=wC0V[˘'ũMEY.wPso+W.[J:l:Wҭ;ws\'EVA@p7nЂO9L B&o/ AjV 7<̞9D`sjgϗo)/ݷAEn^ =ukiْ%|n c$ qxbQhs(Հ6v*-QزurЂ K$P&@ LxQNpr1Dx~F7w%p8wɭw:8ٲCΧFAI'*iB|X}ٝgCoL2֬ZŃ\ۮ" 8Tk&@ Lx`+>"xskʤI̸%#4mT.O2s~J(3CzJ3vW$b¦e?sM|Rz. b7=ejR$9#/&tmzy|mGSA@'&  /ARҜa,sRvr{c)(MS}v:ݺu[7՝\4AniOyܼuKUS~(K`Bs.C1gcpP_{o }b#ѱ<}˥?QtY&MLCqcFSbR2E@.Wпa]0~B_~P11\16>}ܝL"Bx@Vq:Y:u,ݺ}ynlFZArukְ~رcΡ[iEǏ 8ehI02s4 MWxj & 0VAN!A@D ( ++3Fϐ{0ڨQHZ =&9 ް [tqZwQkB£QXxLKs9r8#fx}`̂|ZbQc-R&ZRU_ȹ\rsjLPJǶmnA!r!IA"O=O;|B.]Dّ~dSHMa3A`;w{K@cw$1GRkar\;TDѼ9C5$$$_~tRiŦ?޺-  )>.nsrP?ǐ59)1ZZ a쏬r~B\<-JǍSƭUo B*c[L啕tzTH3lbg9'3Cc555^ǺIIH4c}L]d8bтM'OvKK t1ȅ3lI7^(xKmƦ&JMI 2 D,x\v/b1 Mx 1tgl7\m@v4U駱fH Bt{{nwcÛ[[[jkꨦZZ[}%Ot5wu`6ui gy'30@kccGz0vQ,WщQs3(7'0}o8YpDڲUh?{467 1h6<EӦMQ;66"7)`Z ז?UCXﶝ ]XFB`y x9&M 9 '#%mุ F6<6ܻK7&:;U׆/`v*,`d^&+NC? 8i[Q}c#=Ϯ]xE`!$fTi Nw~sGz҆Fk>+C_`Hx\]q&Moҍ #x2/>&R"S^O?z 1UF8&p\ c0ѽk^:p=,ȡ+nu`{fM"ftiY;d51'`a A41uPEf3:)1f͘/Cq!XcFy?vw"=  ֔x&) E '=cE`qF/ֱcЄM~ U2a0ȭl?A4e9[)"{-&]޻nܼA.~X?X;cǎ1mX tu1e$¦fEױ`O>i ӦҘzK",D3u$+COIltϚ1Ӵ`\#lwM0RSX>KRZMy@'Gg3U7f 8F[Yh"#m,eXE ֆy` !~A@JA`$L#cnjRhCǔW0K 9)~dPA@ hޜ "`uZj; /0J2)##4}VVU1a6D!/d㲱G{ĺ2kl6=-Tc}Džܻ > E2Cv3H9Е._557Obrwĥ9h;l6P D, >!`_sZKi1]> 3]H-;T<$r80v0v,e .![ĺ2[OQ\D%pDU `J穘n [g,Ǩ! ,?2#t2mA@  $E^f&!}G >\ I'MD]]]>%ʺΟ7OҘm'owCfxy,7\9sn&+I;$s ]h&͞ùn>#$W}6X<~mdGھs?ȵ 墍|~t$C"`:ၑ6[\-|ȩ _f )3M촎1)0$/r "Z Ggg'릔&;1B]P4!lڰ߼&@o02~X̡n:mBx6Yp ׬v\d" `.pUl͉ ֚nXK"r1oC…B(I֞up|57*٠Btd^:;,yX*y6hsݷ|-c `MnܠWZӹ*L2ƍ3UrZ"Kl/n~}WWSkko|a'&rHӪ4sTտhT)SM'l©)6Z͸a0-Θ6q[AIlBSu5 dՔj/l ͻ_"%''@"_[F1잭c:jMd$/u.e&Xf,%ŅlpY68DL_0P9%* jXos<%B\`|㜯<=$%%Y9~9ta,C-4s4mӆ4{,EN3`9BX:1\@rT<*Wk kK`u Ǧl$=DAY_y "+3/#g>`FU yeA@'b6b?i#o\F~E4fhc?| ( j(>UV>Vu9 fC͡Pɉ_RRRcީ sKLLds9dgRjk먥,iF `p4*gr=g :d׮tJW4WZ0.!/|+rm a-DD{ ]r|\%vE̟3N9kWE.A@˦k_nhJoCPn߹C&NT!VL6R~n2J775S{[;9| .qjBJJ %sQmx- ?E >Y7h℉JM--Y"RG:{'$PrJ21nL|x7,o歛TkIިx#c}7ߐ=$cnjf_W,[$o25sR!Hb7 ׂɅ@`켛5}貾Ѕgh`ҟDwǸt:5+]\\+[MxvE!Hd' @`7ӟˢ6F f `yϿ2broPf1}hbcOR{YP|o $aV{&d`L <;a3%嵃5$^@%9/^@P=낝^x^}m{ӗA@$^by@M L,g!?rR!A@q A 2 (9EuYGh=te%ć]" M\QkQexv@29ف. "5cͤMbJX;A@3O˖Q0rhh'OюݻUQ08ׇ-d1t C(;psv`m4{"#-=^yE{ (RK5)$W޶sP "0u$BAi ![oR+dCv@x_]>gba֥Aӏ[ BUs}N%Ik{Դ4 vJBZ۶u[d y$/u*e@PbI5{fgP"`uuT[[G^dHGӨQG$םwν}755sH[y@Y:{o՗_pD#pvbڻvuxrk',#l5m6e@K_xRY8J|' ^1tS}}=ݺsWC9q]ccr,=^Ua-Pǖ dBZ]AjimX" D4 ϿFyS $ALA =&N/+MA^c bOi!ޢd}B ckni{(,@`򥔘耙A PBxӗta8^]Zh@czZwGhh(*,TmJkH fEwӨl*o5C@Z4S+@ ))VXn YDA@mϭ_KsgMMH b|C)1>[:gkiGЯAAbbU<{5\l &OHcnj, q  t&Á)8 (|bc]>c r,y9<\8$.ιRRR4y9 cZZhy7^LCP63C+66om;BA ͝C9+7 ղ2e27"#`>+7с诳!iΒ)!G?tB٬cZZB;F³m9+gfWHy!qra)ܳW!I`㺵0S  @rzK_Q\k EfMNӧNl6XΙB/͙@q~^9*yL)))/P_~rs|R.q y4Tتkj:.2 X)@ @"Gc՗=e FK E@iV4~8Zx᰹ڷh<g7!:n(Zl8}ȄA@iqz+R l.^D{!C[1f(Zt|iqǙ t68?feI9(BY b1Ū!_{%txx$%ӷuᄥ"+k[bɓSXl΄-!M>UC[^yel6Ȋ F ҂CGzZ:%%%KCpPjq7 ]α/zh҄}͝5q`PoEnj^v" eVh9N.;]XWJg6e 9#D &&Rl3Qw_uzF:{xrJ[ZͥܘQ*L{&\eSg=ze iӆum;,>:$<tTsi B I,]D@w'?mGx!۔F.W,YJJY!8=7;Ar X`$%R*D`~ޓ8j!,jFR6]A&8e2z;K 6_kj0557.786w/3VkI7噬ƜLၥ1fhobb՗^t$ӌiS8q-0̺ᛔBRC7#/'K``ޞn';L薕<_AO4IKh瞽ȞHh#A,$%Tp39TH?ɶe qgb۽չs~< lh?B:Yrgk%j'`Oi@6?vXTWj]&+ɣG7NvAq4m1?]/LI_֘m s6fp</o> @L*ƒ˓ a2tR(G,uʂwڜ3(+3sAF*S9_4A`֯)㐧Kr]r߼iLJ FWN7}nyt҇y&|Yc&ӓu'ayU]۶_]B`&NI&iVߋϧ`VseiMx@˖,bÌk!<`F4^!J~ww7}mi)ٳNXSRM[-|}{[ )3-dG)a-!!,z(&6Ft )pl '-%R.#Mo`J҉ ` ]{L#u~~ꌾ$}657gorTl'0+/䤤G@cLv CqZea8E$NѦGtĶwDEhܹ4~ء/3@@]tf-:q}joovR;rܧ:b*lP q3*gc1cFs%\Y$2SDiitcoFEO 'M(V܄PW^|A[4ˍ#P\THgΰ|زu)]bbn~VSps'?voXي%ǰzrӆun{l{Q$<Cx`9l~өDh~JVW1H Io QHh6=&I|N7uvw;ut~Vimm!>0&Ɔ yl 2{si8U1`*,O}|݉vєi" ~!YPGQCC_b(?=jjjǛDCwC%8ΞĥLpP&hQL"Mv+zw[`92D{mR$Ҽ u<Ƽ21^o mw'NbnETxȊN>uOy\ǻx-?ރ、GOqCڮ [[1a3HQ*ظ@0*7 P@ D5kcH bJ.ޏe1} <@w1Vx}7&sև9c:}p>os浗_x)옊+On 2oߵxՠEח~t?蠦= 1woVsF+qѽo/G@cjVLv^{U,1 ̣[w)ݟ{}. ox>qr Cv~[N=!ǭ ³`Y#ajÝ`'pOv.qQ,Wԉo)119.511Q%XC5~zHǰ^ i+>74}G.E %9V._F?ﭶ~]&z{kJ"%sڱ'KZOI7Z׭Y-E8N3٢aB>Exn>ol:)boދ ifg&@.%14tz0ȫk[TS[G-ۮ 00_5nvSyð 5[)+ qQBBv]5TTT$= o>{j `ƴi*t* {.`bw+9T x{y-=kc+7bݳC'@7q_4XN&L4qG("O.mgII/xxq4_BZŹQydsq|S~#o, )$ӟ[H3{o}þgU g0)9nyv f}ef$IJD Hy#NXz~^9{0 a@'Œ2pRGtcߑv#y)|0‘Eo&@o1Dd8i$5'Hty v 2؛};|bOrRދ9(αa-; a)F OR$-e2#sR$0ER=6}0Wj.j)L0>6\ ܥ"B~6gL&<|S'ӺU_ G ش~G{} mܛVЙx!g?%Lqw~տ,v9XHB@NN ?rDHǍl-G3ّh5l n;j4UELxVȬs$Cfփ]7?qA,a8E,d1H~&;"x/ҾGhRF&(o)ƍ70r9<F/pgTWxJ$!#>2X2GqB~Ps68tk \13LqcUՃ>_cl !vP#=$? wzX^NQhC8dѴ) 2,_D%wQ̭Z;RL~*y$4<ol >M0c2'OLYyx8Gf <#ğd97!4ORjR"yJIIpiϜ. ޻o |@XذnM?ﱰx> 'zyd(mn۾k"kjl#PzV!9C]C7,/Ӯ0ԀFy&;o=aW_ƨ< (:Ne_$}("VZᏼ%7rYΟ"*Rn* /_x\e/ \^ xfcޠ3qVN;H5nSCۿReU)}ڥ!<좉ȁi# d1Gy&?EWO9b)UYoOqǎ~D!B]/HX+b:\уL:oinQc9qcτOnOޕ 7IϿ a@a-6GO J|uZ?~Cぇ~/AUvrM6nb<"0{ gٵcM}#TUf6z?H,Xy`+..R&S]\a~@/@HX|%i?޺=(r^YAuh?+_?xؔ[NO~F_t@}֬Vl-g*]I3y&`3y0T" ŘK_lmT$ 9Uek;w0zkkkZGM]]w@@ӦLyF \zj%_{yBr.i@HX5-| 7.`aƀwݣҗ5xێ~^+|- {+75e oSlb<<233跿MrE{ʃdex0X;w6'{{Ztk"zU%+s|*)= \4iDf3$*5m9vӶٻ-sr{>Ozw'?WklwߧgN}mG_ijjl,;t΃vMdS'Ov]*]ɾ貴!Xd1o_Wiɔ6},(-.;POwנDAa pJMMfOH |cctC7mGq9CVϒظX}(mocU*L"mnқ0(+lt'ݿ>~HCUg)]Q s=/wG9Mg1LBLf-L(shƌPű3?xH/̲^m$iii|0#挳;t68 ~9<\UTL˖.:GS9EG2I۷¥K4xz |T?$%ȾaG@D]D,umk[kbmv}[Qڢ *[X@/ eΝsg~Ϙds!?g?!)}x֬bvv)(ȗ&M%;43Mq,PX8}ݹd́l-8uݫWxcD`>=`81kA.!xnl+aӅ3wv FY_TVH#sO}[}2wuCaUU=oOozdǎ^y<O6uNgv/0f_봖p9}p m \;wNՖ% Z '{﹅'ңkW)lӆQ&x/6n*t/L['zC[$!C$===>Z$.N95Xۯs\u%NM]Im@ r:>VL^~#ON; EEE NSe|ǭ:>߸Qʎq-Z1qFp>йM4AIk.e˥"̑;5W;ҳ-*%nW '{oO9z']qYݾ>fdCr=gIif9zA8ljȑ%mD? x'!+ZHo{F_[ 9wp\FwDN9 XZzdKƂby'sgSO* z ::tH6n̴8|tM7;mmȇNg)hRD~k}?h<kS/EFJ3;LMxо}mnؕ>Gxnş=C2OOx ph~Y}>q8ѭ97ngwmZZooٳgoXOKK[V$/vRsc<ϓ Ey|U>Wk9lIud3=)?N27Ϲ1}קttg-;L#S&G_ϔ~?8nx~+YͶFs/'M]+9Iv[Q#IJrb֮t %^<|=Zr57#'DAV+x8d< h2kw|ᇾ b;ƌj^ÒUƌ(<xh(-j }y&D\w@om;8}-EO!:QJvvL4-jm̨8ID)^C97f-ׯ _y`{&;7}Ңi)2sxi^@yشyޚ߸qc毅Wafu%q:}rdod!Nh40&7̻FF4:O>;wH2CB%]; JiS:J4]T41z0r%i-Z14P& &pHY`YYW'*_GO]홛+&M,0TwMZlg<_22R=;\:rY])//3eNM"iʹ I&%ȠH>\$Ijj̾H/V 2tJa^}}Zy4$صV}O2cy[Aj7u+G#gksΑ xO"Ͳ~7ozk+'O|L ts.Yn,:ьxoZe/tt8RRg<>Z*5- Y5C%%%d˞_wd=wIJJ'M}wfA)ح%BQ^+3tL#foiFy\4c'7oyԳ/!͛5+tI,`q&4f%Mejd~`viG>}=I)ir⮭u }ZWiroD@[}>3UԈjD[@GE u=ih<\Λ2IrZ0Qothaa%i!W^r'FQUU%o/ٳo/AZ{ +4mmHeϹOenR*+Oy.tgi'I;3bD8NѢysVyYp>S&NKg,?2Sf;@nOGόFL;i&2$E/hgٹYfݎS22qݥݻ'N?h=JJKdju6V9m`jjYb7KT?AeUu,!ueZ4pfϐ|3ΓMZQXGt铣DOe 5,89DCAC~IֹfH۷O^[J|5M^QۨĴoᴕSܩL24͛I6m\T@]Ł@" +j/}yYxgu>ၻgu,O2Co^0g/ngz^d[}'ڽeRsͽ++~:]ѧgOё~)թwlXIУFs>@J6wo+$Y`iTQ|a ׬8HHNbe7̐yffj/wO3 m[eɡC}w][{ym m対괉wM~rڠC! _>e$ѣ|Wwe]WyG9{9FEMycGh'E5˖HǨ1s'!Ymj̻J̖Ag /4;Bՙfa2̯NJW3];BQgEm^`ݾI2n_g`PCl(#KrsdU5)&i\[zTTT?^\GSGp3QO<4q ) )ۚOIŲOם :&ڢs\?jimˮ-: ftWO>v438",;C_~e~١D4}>9iTr$`q OkyqKVۑhfcH"=yF@@@ RG,OM7>9~}rd}#{dnA=hU,k7gv8C#ffdH.!GQ HF&Od6kl5bg x\xTߍk0H6|*_Z&7mr~fAӠRj6lpdjCdo[t4nt ۶7(3 q|h4'"B@D=b$k;m6&t ,ךOL7IVAN}NG!h?3Y%Y yJ͎fp񒥎>3sʐAdiAT;Y3~6\t b5Sa j{6eeecܿӀk42\sѸ@F cYF(=I% .ccL# ] O50g!Pf^ eeޚ5%޺'~a=C=_mNXn ;cnO^?k0//Gn^x9-LP0$;B)}{9^j@ 6;9$mZ5Щm۴_G2|S_',P4jd*@Ulk|Amݻ-7DM.>:,]g[~"0Jѣ,4 |s;^I#1zr'{'3$fiKf{wLfKr-7Qu +] ˇRO?#FEM?1l?$!# ztsـ}r@"_m xD02mD8u$GYg Kk*޼sON)n菚Z3KŦܹ͛OFsM[6;&jM7kJ>]M5j(6Ճ?nG-&!$PضhsϿ76#;u1S]CrDV iS}w߼߬wd= 6KCL# ѫGw+KɫTGA^?&Ӭêen˸zA nܸQŒX,omٺm-V5֯l߱]zm3c,UDm:eʄ2syn~NQ a@5lTg𡮦I}r/}2tlA? s37E(0Au"~/G_G8j^^zeNje՛o6ʡ#EGz=WвFph8b ֛K//ׯf~c0mkktvy'헗W'SҴ)d ) @5Z:ղ鷺[Ť F׏Y-9hެ<˖ސKt;ύ@dV>٭\'gAUww-fjѿoYtu?0#*iCo?ް,.MfKNNsiB4i"Yf4I6*sDGCPBGgٻwXJJ깁 GKȎw۝wMH9>ruz-y pMM:dйʊ}[QYg?ͬ_؏Y9~aPGMxM(ǎW_oi'ү}2v,v-켜;y8ٶ}ߛUl5 Qfعkع)YzZCG#=IS ibJYVi5RA5Ճ!տyOh>R^V.hI !9l>}ZD8tDRc[ϦWnNƝ:{oEJ=! \t?diDC̑~ `s8H3k?Ghm3N;V5%֑{7AG`H5 Rj"CyC8pЩ:CGYT  pFn0aϕh@k8i5/~\#AFp jzݵseNM~@RL xQǿ-bzKdޜ+OcXcGQfQÿ>yϚƋc]$V3eܫ@V #"Z NO=du篞ځ64[mr=pVVH@kz"РjءC=c8j>gZj;?џ\$Ο ]@I qExعK|_Ul94su%˖˟wOl)^ˡ~4PfL;OSWA@_غ-uO>yO&qj;͛2A{emFm \^`nj&5W] ;x&vme-tR)S&w|Xm(S46oLM277ZDE@Gyfjϰ ΐX,P4j$ۧ'ץx,u3C'wGУN,^@Rr:Dn[-'#s\`ܘQ򳧟9e$/ ;&//^śu;fr7mW__)+cG{s䢟0SU,vӦ1(D ڶݻ˚uq}'VdBÊf:߼WzoxOLȎC=,i `@^Vҷw/;X%xTLv+/XvmoȊoȻk:;U?׶geɠdfh  tA^]l;k_=ⵁ-W١>p r /Pnv>@֮|]/?c9XIvn]{WvQXL2K!OGSG;UJc>%H]Rcf 4Hr,=#CJK=-5|u;<%3@tmwZgo۶ezOv6rZ4|)0k)(.: Giܸ\Iğ}}mJþ6H>doɎ= z sf2??'3nDL5 ѼES~5raO=5;ƚ&Hd CLc(>x@JJJ̣Kͣd e5W]Jv  `JKd}ͽ̬Sy',:7ѝ;u'Z.Y<_ʚh>*؉Q ]e9һGZKurâ[B |z")5y@@cQR@FzUn7Jd%GoWkC ڳ{7y{ߑ,^*{eϾ!\&Pff-$m\.=CN,BW@-rhIVrYåwuek   @LxĄݎ(:f~c9dnx+++*\IH7ASs˳ xEר/^?U@:dʄr %L_ hҤ4jt"H.徻ns> &    mq 7n(k)1ټek*e]z5kY,\g~'~=Nh/'ҽ̛s3$4h3ihu0QHd@@@ . zfG?An)ULsq- "ZʥfDǤc%-5e~uIKIys3g]Y̶,   <,nh-7'GYs7RQ¨iK)9vUtҩ&ȈO/IC>B4St7ffKvvPr8ǏiOk3ˀ~}OI   8FGr*d̺|ۻWjnz'͘&W\2["vM2F@@"-H Q]~Rf۸겋/h ׮[' |Rǻ舖diRr 4Hf͘jFXoYY\`lٺU:t(K#zM2G@@!@#qv >'س[zqpj0k>eW𑒸Y:eaeyK ̷W#w LfbTG$E@@x)neҭsg9Q+meť/;:'מZ!M|6di=˔UǨp   x-@kQ#G彵kMMVˮ];Y* FrssZH~d!,BڨQØqa@@@ Qx$JK'X=)/kֽ/oZ2۾*I2 Uf>qbh`<קmetY.   [Q()-M7˻ʚ5MD?5~HRR+C!' Ѡi(I&mIb-sgJ^GrFaٽ&9!  D_G͹b=*۶6n&iٺs(> GKKTʏUƉj$-5Y33afd7m*򤰰7 %tqy@@@_^CrCJJJL8$Gd`G I o   `[!   @'%9 @@@@{x6 @W @@\ p G2@@@WmC@@@@p$C@@@{x6GOȡ IDAT @@@\ p G2@@@WmC@@@@p$C@@@{x6 @@@\ p G2@@@WmC@@@@p$C@@@{x6 @@@\ p G2@@@WmC@@@@p$C@@@{x6 @@@\ p G2@@@WmC@@@@p$C@@@{x6 @@@\ p G2@@@WmC@@@@p$C@@@{x6 @@@\ p G2@@@WmC@@@@p$C@@@{x6 @@@\ p G2@@@WmC@@@@p$C@@@{x6 @@@\ p G2@@@WmC@@@@p$C@@@{x6 @@@\ p G2@@@WmC@@@@p$C@@@{x6 @@@\ p G2@@@WmC@@@@p$C@@@{x6 @@@\ p G2@@@WmC@@@@p$C@@@{x6 @@@\ p G2@@@WmC@@@@p$C@@@{x6 @@@\ p G2@@@WmC@@@@p$C@@@{x6 @@@\ p G2@@@WmC@@@@p$C@@@{x6 @@@\ p G2@@@WmC@@@@p$C@@@{x6 @@@\ p G2@@@WmC@@@@p$C@@@{x6 @@@\ p G2@@@WmC@@@@p$C@@@{x6 @@@\ p G2@@@WmC@@@@p$C@@@{x6 @@@\ p G2@@@WmC@@@@p$C@@@{x6 @@@\ p G2@@@WmC@@@@p$C@@@{x6 @@@\ p G2@@@WmC@@@@p$C@@@{x6 @@@\ p G2@@@WmC@@@@p$C@@@{x6 @@@\ p G2@@@WmC@@@@p$C@@@{x6 @@@\ p G2@@@WmC@@@@p$C@@@{x6 @@@\ p G2@@@WkIENDB`PK?eu.s document.jsonPK-V/Ypages/88C443B8-BE51-4600-97CF-5D57601D608F.jsonPK◇ee3Simages/c183b4f7489cfb6c0e08bf1bf321776c3582b22c.pngPKQ& gluser.jsonPKaR$- 2mmeta.jsonPKNW\ R }npreviews/preview.pngPK ykong-0.8.1/kong_test.go000066400000000000000000001302671451020262500150100ustar00rootroot00000000000000package kong_test import ( "bytes" "errors" "fmt" "strings" "testing" "github.com/alecthomas/assert/v2" "github.com/alecthomas/repr" "github.com/alecthomas/kong" ) func mustNew(t *testing.T, cli interface{}, options ...kong.Option) *kong.Kong { t.Helper() options = append([]kong.Option{ kong.Name("test"), kong.Exit(func(int) { t.Helper() t.Fatalf("unexpected exit()") }), }, options...) parser, err := kong.New(cli, options...) assert.NoError(t, err) return parser } func TestPositionalArguments(t *testing.T) { var cli struct { User struct { Create struct { ID int `kong:"arg"` First string `kong:"arg"` Last string `kong:"arg"` } `kong:"cmd"` } `kong:"cmd"` } p := mustNew(t, &cli) ctx, err := p.Parse([]string{"user", "create", "10", "Alec", "Thomas"}) assert.NoError(t, err) assert.Equal(t, "user create ", ctx.Command()) t.Run("Missing", func(t *testing.T) { _, err := p.Parse([]string{"user", "create", "10"}) assert.Error(t, err) }) } func TestBranchingArgument(t *testing.T) { /* app user create app user delete app user rename */ var cli struct { User struct { Create struct { ID string `kong:"arg"` First string `kong:"arg"` Last string `kong:"arg"` } `kong:"cmd"` // Branching argument. ID struct { ID int `kong:"arg"` Flag int Delete struct{} `kong:"cmd"` Rename struct { To string } `kong:"cmd"` } `kong:"arg"` } `kong:"cmd,help='User management.'"` } p := mustNew(t, &cli) ctx, err := p.Parse([]string{"user", "10", "delete"}) assert.NoError(t, err) assert.Equal(t, 10, cli.User.ID.ID) assert.Equal(t, "user delete", ctx.Command()) t.Run("Missing", func(t *testing.T) { _, err = p.Parse([]string{"user"}) assert.Error(t, err) }) } func TestResetWithDefaults(t *testing.T) { var cli struct { Flag string FlagWithDefault string `kong:"default='default'"` } cli.Flag = "BLAH" cli.FlagWithDefault = "BLAH" parser := mustNew(t, &cli) _, err := parser.Parse([]string{}) assert.NoError(t, err) assert.Equal(t, "", cli.Flag) assert.Equal(t, "default", cli.FlagWithDefault) } func TestFlagSlice(t *testing.T) { var cli struct { Slice []int } parser := mustNew(t, &cli) _, err := parser.Parse([]string{"--slice=1,2,3"}) assert.NoError(t, err) assert.Equal(t, []int{1, 2, 3}, cli.Slice) } func TestFlagSliceWithSeparator(t *testing.T) { var cli struct { Slice []string } parser := mustNew(t, &cli) _, err := parser.Parse([]string{`--slice=a\,b,c`}) assert.NoError(t, err) assert.Equal(t, []string{"a,b", "c"}, cli.Slice) } func TestArgSlice(t *testing.T) { var cli struct { Slice []int `arg` Flag bool } parser := mustNew(t, &cli) _, err := parser.Parse([]string{"1", "2", "3", "--flag"}) assert.NoError(t, err) assert.Equal(t, []int{1, 2, 3}, cli.Slice) assert.Equal(t, true, cli.Flag) } func TestArgSliceWithSeparator(t *testing.T) { var cli struct { Slice []string `arg` Flag bool } parser := mustNew(t, &cli) _, err := parser.Parse([]string{"a,b", "c", "--flag"}) assert.NoError(t, err) assert.Equal(t, []string{"a,b", "c"}, cli.Slice) assert.Equal(t, true, cli.Flag) } func TestUnsupportedFieldErrors(t *testing.T) { var cli struct { Keys struct{} } _, err := kong.New(&cli) assert.Error(t, err) } func TestMatchingArgField(t *testing.T) { var cli struct { ID struct { NotID int `kong:"arg"` } `kong:"arg"` } _, err := kong.New(&cli) assert.Error(t, err) } func TestCantMixPositionalAndBranches(t *testing.T) { var cli struct { Arg string `kong:"arg"` Command struct { } `kong:"cmd"` } _, err := kong.New(&cli) assert.Error(t, err) } func TestPropagatedFlags(t *testing.T) { var cli struct { Flag1 string Command1 struct { Flag2 bool Command2 struct{} `kong:"cmd"` } `kong:"cmd"` } parser := mustNew(t, &cli) _, err := parser.Parse([]string{"command-1", "command-2", "--flag-2", "--flag-1=moo"}) assert.NoError(t, err) assert.Equal(t, "moo", cli.Flag1) assert.Equal(t, true, cli.Command1.Flag2) } func TestRequiredFlag(t *testing.T) { var cli struct { Flag string `kong:"required"` } parser := mustNew(t, &cli) _, err := parser.Parse([]string{}) assert.Error(t, err) } func TestOptionalArg(t *testing.T) { var cli struct { Arg string `kong:"arg,optional"` } parser := mustNew(t, &cli) _, err := parser.Parse([]string{}) assert.NoError(t, err) } func TestOptionalArgWithDefault(t *testing.T) { var cli struct { Arg string `kong:"arg,optional,default='moo'"` } parser := mustNew(t, &cli) _, err := parser.Parse([]string{}) assert.NoError(t, err) assert.Equal(t, "moo", cli.Arg) } func TestArgWithDefaultIsOptional(t *testing.T) { var cli struct { Arg string `kong:"arg,default='moo'"` } parser := mustNew(t, &cli) _, err := parser.Parse([]string{}) assert.NoError(t, err) assert.Equal(t, "moo", cli.Arg) } func TestRequiredArg(t *testing.T) { var cli struct { Arg string `kong:"arg"` } parser := mustNew(t, &cli) _, err := parser.Parse([]string{}) assert.Error(t, err) } func TestInvalidRequiredAfterOptional(t *testing.T) { var cli struct { ID int `kong:"arg,optional"` Name string `kong:"arg"` } _, err := kong.New(&cli) assert.Error(t, err) } func TestOptionalStructArg(t *testing.T) { var cli struct { Name struct { Name string `kong:"arg,optional"` Enabled bool } `kong:"arg,optional"` } parser := mustNew(t, &cli) t.Run("WithFlag", func(t *testing.T) { _, err := parser.Parse([]string{"gak", "--enabled"}) assert.NoError(t, err) assert.Equal(t, "gak", cli.Name.Name) assert.Equal(t, true, cli.Name.Enabled) }) t.Run("WithoutFlag", func(t *testing.T) { _, err := parser.Parse([]string{"gak"}) assert.NoError(t, err) assert.Equal(t, "gak", cli.Name.Name) }) t.Run("WithNothing", func(t *testing.T) { _, err := parser.Parse([]string{}) assert.NoError(t, err) }) } func TestMixedRequiredArgs(t *testing.T) { var cli struct { Name string `kong:"arg"` ID int `kong:"arg,optional"` } parser := mustNew(t, &cli) t.Run("SingleRequired", func(t *testing.T) { _, err := parser.Parse([]string{"gak", "5"}) assert.NoError(t, err) assert.Equal(t, "gak", cli.Name) assert.Equal(t, 5, cli.ID) }) t.Run("ExtraOptional", func(t *testing.T) { _, err := parser.Parse([]string{"gak"}) assert.NoError(t, err) assert.Equal(t, "gak", cli.Name) }) } func TestInvalidDefaultErrors(t *testing.T) { var cli struct { Flag int `kong:"default='foo'"` } p := mustNew(t, &cli) _, err := p.Parse(nil) assert.Error(t, err) } func TestCommandMissingTagIsInvalid(t *testing.T) { var cli struct { One struct{} } _, err := kong.New(&cli) assert.Error(t, err) } func TestDuplicateFlag(t *testing.T) { var cli struct { Flag bool Cmd struct { Flag bool } `kong:"cmd"` } _, err := kong.New(&cli) assert.Error(t, err) } func TestDuplicateFlagOnPeerCommandIsOkay(t *testing.T) { var cli struct { Cmd1 struct { Flag bool } `kong:"cmd"` Cmd2 struct { Flag bool } `kong:"cmd"` } _, err := kong.New(&cli) assert.NoError(t, err) } func TestTraceErrorPartiallySucceeds(t *testing.T) { var cli struct { One struct { Two struct { } `kong:"cmd"` } `kong:"cmd"` } p := mustNew(t, &cli) ctx, err := kong.Trace(p, []string{"one", "bad"}) assert.NoError(t, err) assert.Error(t, ctx.Error) assert.Equal(t, "one", ctx.Command()) } type commandWithNegatableFlag struct { Flag bool `kong:"default='true',negatable"` ran bool } func (c *commandWithNegatableFlag) Run() error { c.ran = true return nil } func TestNegatableFlag(t *testing.T) { tests := []struct { name string args []string expected bool }{ { name: "no flag", args: []string{"cmd"}, expected: true, }, { name: "boolean flag", args: []string{"cmd", "--flag"}, expected: true, }, { name: "inverted boolean flag", args: []string{"cmd", "--flag=false"}, expected: false, }, { name: "negated boolean flag", args: []string{"cmd", "--no-flag"}, expected: false, }, { name: "inverted negated boolean flag", args: []string{"cmd", "--no-flag=false"}, expected: true, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { var cli struct { Cmd commandWithNegatableFlag `kong:"cmd"` } p := mustNew(t, &cli) kctx, err := p.Parse(tt.args) assert.NoError(t, err) assert.Equal(t, tt.expected, cli.Cmd.Flag) err = kctx.Run() assert.NoError(t, err) assert.Equal(t, tt.expected, cli.Cmd.Flag) assert.True(t, cli.Cmd.ran) }) } } func TestExistingNoFlag(t *testing.T) { var cli struct { Cmd struct { Flag bool `kong:"default='true'"` NoFlag string } `kong:"cmd"` } p := mustNew(t, &cli) _, err := p.Parse([]string{"cmd", "--no-flag=none"}) assert.NoError(t, err) assert.Equal(t, true, cli.Cmd.Flag) assert.Equal(t, "none", cli.Cmd.NoFlag) } func TestInvalidNegatedNonBool(t *testing.T) { var cli struct { Cmd struct { Flag string `kong:"negatable"` } `kong:"cmd"` } _, err := kong.New(&cli) assert.Error(t, err) } type hookContext struct { cmd bool values []string } type hookValue string func (h *hookValue) BeforeApply(ctx *hookContext) error { ctx.values = append(ctx.values, "before:"+string(*h)) return nil } func (h *hookValue) AfterApply(ctx *hookContext) error { ctx.values = append(ctx.values, "after:"+string(*h)) return nil } type hookCmd struct { Two hookValue `kong:"arg,optional"` Three hookValue } func (h *hookCmd) BeforeApply(ctx *hookContext) error { ctx.cmd = true return nil } func (h *hookCmd) AfterApply(ctx *hookContext) error { ctx.cmd = true return nil } func TestHooks(t *testing.T) { var tests = []struct { name string input string values hookContext }{ {"Command", "one", hookContext{true, nil}}, {"Arg", "one two", hookContext{true, []string{"before:", "after:two"}}}, {"Flag", "one --three=THREE", hookContext{true, []string{"before:", "after:THREE"}}}, {"ArgAndFlag", "one two --three=THREE", hookContext{true, []string{"before:", "before:", "after:two", "after:THREE"}}}, } var cli struct { One hookCmd `cmd:""` } ctx := &hookContext{} p := mustNew(t, &cli, kong.Bind(ctx)) for _, test := range tests { *ctx = hookContext{} cli.One = hookCmd{} t.Run(test.name, func(t *testing.T) { _, err := p.Parse(strings.Split(test.input, " ")) assert.NoError(t, err) assert.Equal(t, &test.values, ctx) }) } } func TestShort(t *testing.T) { var cli struct { Bool bool `short:"b"` String string `short:"s"` } app := mustNew(t, &cli) _, err := app.Parse([]string{"-b", "-shello"}) assert.NoError(t, err) assert.True(t, cli.Bool) assert.Equal(t, "hello", cli.String) } func TestDuplicateFlagChoosesLast(t *testing.T) { var cli struct { Flag int } _, err := mustNew(t, &cli).Parse([]string{"--flag=1", "--flag=2"}) assert.NoError(t, err) assert.Equal(t, 2, cli.Flag) } func TestDuplicateSliceAccumulates(t *testing.T) { var cli struct { Flag []int } args := []string{"--flag=1,2", "--flag=3,4"} _, err := mustNew(t, &cli).Parse(args) assert.NoError(t, err) assert.Equal(t, []int{1, 2, 3, 4}, cli.Flag) } func TestMapFlag(t *testing.T) { var cli struct { Set map[string]int } _, err := mustNew(t, &cli).Parse([]string{"--set", "a=10", "--set", "b=20"}) assert.NoError(t, err) assert.Equal(t, map[string]int{"a": 10, "b": 20}, cli.Set) } func TestMapFlagWithSliceValue(t *testing.T) { var cli struct { Set map[string][]int } _, err := mustNew(t, &cli).Parse([]string{"--set", "a=1,2", "--set", "b=3"}) assert.NoError(t, err) assert.Equal(t, map[string][]int{"a": {1, 2}, "b": {3}}, cli.Set) } type embeddedFlags struct { Embedded string } func TestEmbeddedStruct(t *testing.T) { var cli struct { embeddedFlags NotEmbedded string } _, err := mustNew(t, &cli).Parse([]string{"--embedded=moo", "--not-embedded=foo"}) assert.NoError(t, err) assert.Equal(t, "moo", cli.Embedded) assert.Equal(t, "foo", cli.NotEmbedded) } func TestSliceWithDisabledSeparator(t *testing.T) { var cli struct { Flag []string `sep:"none"` } _, err := mustNew(t, &cli).Parse([]string{"--flag=a,b", "--flag=b,c"}) assert.NoError(t, err) assert.Equal(t, []string{"a,b", "b,c"}, cli.Flag) } func TestMultilineMessage(t *testing.T) { w := &bytes.Buffer{} var cli struct{} p := mustNew(t, &cli, kong.Writers(w, w)) p.Printf("hello\nworld") assert.Equal(t, "test: hello\n world\n", w.String()) } type cmdWithRun struct { Arg string `arg:""` } func (c *cmdWithRun) Run(key string) error { c.Arg += key if key == "ERROR" { return fmt.Errorf("ERROR") } return nil } type parentCmdWithRun struct { Flag string SubCommand struct { Arg string `arg:""` } `cmd:""` } func (p *parentCmdWithRun) Run(key string) error { p.SubCommand.Arg += key return nil } type grammarWithRun struct { One cmdWithRun `cmd:""` Two cmdWithRun `cmd:""` Three parentCmdWithRun `cmd:""` } func TestRun(t *testing.T) { cli := &grammarWithRun{} p := mustNew(t, cli) ctx, err := p.Parse([]string{"one", "two"}) assert.NoError(t, err) err = ctx.Run("hello") assert.NoError(t, err) assert.Equal(t, "twohello", cli.One.Arg) ctx, err = p.Parse([]string{"two", "three"}) assert.NoError(t, err) err = ctx.Run("ERROR") assert.Error(t, err) ctx, err = p.Parse([]string{"three", "sub-command", "arg"}) assert.NoError(t, err) err = ctx.Run("ping") assert.NoError(t, err) assert.Equal(t, "argping", cli.Three.SubCommand.Arg) } type failCmd struct{} func (f failCmd) Run() error { return errors.New("this command failed") } func TestPassesThroughOriginalCommandError(t *testing.T) { var cli struct { Fail failCmd `kong:"cmd"` } p := mustNew(t, &cli) ctx, _ := p.Parse([]string{"fail"}) err := ctx.Run() assert.Error(t, err) assert.Equal(t, err.Error(), "this command failed") } func TestInterpolationIntoModel(t *testing.T) { var cli struct { Flag string `default:"${default_value}" help:"Help, I need ${somebody}" enum:"${enum}"` EnumRef string `enum:"a,b" required:"" help:"One of ${enum}"` EnvRef string `env:"${env}" help:"God ${env}"` } _, err := kong.New(&cli) assert.Error(t, err) p, err := kong.New(&cli, kong.Vars{ "default_value": "Some default value.", "somebody": "chickens!", "enum": "a,b,c,d", "env": "SAVE_THE_QUEEN", }) assert.NoError(t, err) assert.Equal(t, 4, len(p.Model.Flags)) flag := p.Model.Flags[1] flag2 := p.Model.Flags[2] flag3 := p.Model.Flags[3] assert.Equal(t, "Some default value.", flag.Default) assert.Equal(t, "Help, I need chickens!", flag.Help) assert.Equal(t, map[string]bool{"a": true, "b": true, "c": true, "d": true}, flag.EnumMap()) assert.Equal(t, []string{"a", "b", "c", "d"}, flag.EnumSlice()) assert.Equal(t, "One of a,b", flag2.Help) assert.Equal(t, []string{"SAVE_THE_QUEEN"}, flag3.Envs) assert.Equal(t, "God SAVE_THE_QUEEN", flag3.Help) } func TestIssue244(t *testing.T) { type Config struct { Project string `short:"p" env:"CI_PROJECT_ID" help:"Environment variable: ${env}"` } w := &strings.Builder{} k := mustNew(t, &Config{}, kong.Exit(func(int) {}), kong.Writers(w, w)) _, err := k.Parse([]string{"--help"}) assert.NoError(t, err) assert.Contains(t, w.String(), `Environment variable: CI_PROJECT_ID`) } func TestErrorMissingArgs(t *testing.T) { var cli struct { One string `arg:""` Two string `arg:""` } p := mustNew(t, &cli) _, err := p.Parse(nil) assert.Error(t, err) assert.Equal(t, "expected \" \"", err.Error()) } func TestBoolOverride(t *testing.T) { var cli struct { Flag bool `default:"true"` } p := mustNew(t, &cli) _, err := p.Parse([]string{"--flag=false"}) assert.NoError(t, err) _, err = p.Parse([]string{"--flag", "false"}) assert.Error(t, err) } func TestAnonymousPrefix(t *testing.T) { type Anonymous struct { Flag string } var cli struct { Anonymous `prefix:"anon-"` } p := mustNew(t, &cli) _, err := p.Parse([]string{"--anon-flag=moo"}) assert.NoError(t, err) assert.Equal(t, "moo", cli.Flag) } type TestInterface interface { SomeMethod() } type TestImpl struct { Flag string } func (t *TestImpl) SomeMethod() {} func TestEmbedInterface(t *testing.T) { type CLI struct { SomeFlag string TestInterface } cli := &CLI{TestInterface: &TestImpl{}} p := mustNew(t, cli) _, err := p.Parse([]string{"--some-flag=foo", "--flag=yes"}) assert.NoError(t, err) assert.Equal(t, "foo", cli.SomeFlag) assert.Equal(t, "yes", cli.TestInterface.(*TestImpl).Flag) // nolint } func TestExcludedField(t *testing.T) { var cli struct { Flag string Excluded string `kong:"-"` } p := mustNew(t, &cli) _, err := p.Parse([]string{"--flag=foo"}) assert.NoError(t, err) _, err = p.Parse([]string{"--excluded=foo"}) assert.Error(t, err) } func TestUnnamedFieldEmbeds(t *testing.T) { type Embed struct { Flag string } var cli struct { One Embed `prefix:"one-" embed:""` Two Embed `prefix:"two-" embed:""` } buf := &strings.Builder{} p := mustNew(t, &cli, kong.Writers(buf, buf), kong.Exit(func(int) {})) _, err := p.Parse([]string{"--help"}) assert.NoError(t, err) assert.Contains(t, buf.String(), `--one-flag=STRING`) assert.Contains(t, buf.String(), `--two-flag=STRING`) } func TestHooksCalledForDefault(t *testing.T) { var cli struct { Flag hookValue `default:"default"` } ctx := &hookContext{} _, err := mustNew(t, &cli, kong.Bind(ctx)).Parse(nil) assert.NoError(t, err) assert.Equal(t, "default", string(cli.Flag)) assert.Equal(t, []string{"before:default", "after:default"}, ctx.values) } func TestEnum(t *testing.T) { var cli struct { Flag string `enum:"a,b,c" required:""` } _, err := mustNew(t, &cli).Parse([]string{"--flag", "d"}) assert.EqualError(t, err, "--flag must be one of \"a\",\"b\",\"c\" but got \"d\"") } func TestEnumMeaningfulOrder(t *testing.T) { var cli struct { Flag string `enum:"first,second,third,fourth,fifth" required:""` } _, err := mustNew(t, &cli).Parse([]string{"--flag", "sixth"}) assert.EqualError(t, err, "--flag must be one of \"first\",\"second\",\"third\",\"fourth\",\"fifth\" but got \"sixth\"") } type commandWithHook struct { value string } func (c *commandWithHook) AfterApply(cli *cliWithHook) error { c.value = cli.Flag return nil } type cliWithHook struct { Flag string Command commandWithHook `cmd:""` } func (c *cliWithHook) AfterApply(ctx *kong.Context) error { ctx.Bind(c) return nil } func TestParentBindings(t *testing.T) { cli := &cliWithHook{} _, err := mustNew(t, cli).Parse([]string{"command", "--flag=foo"}) assert.NoError(t, err) assert.Equal(t, "foo", cli.Command.value) } func TestNumericParamErrors(t *testing.T) { var cli struct { Name string } parser := mustNew(t, &cli) _, err := parser.Parse([]string{"--name", "-10"}) assert.EqualError(t, err, `--name: expected string value but got "-10" (short flag); perhaps try --name="-10"?`) } func TestDefaultValueIsHyphen(t *testing.T) { var cli struct { Flag string `default:"-"` } p := mustNew(t, &cli) _, err := p.Parse(nil) assert.NoError(t, err) assert.Equal(t, "-", cli.Flag) } func TestDefaultEnumValidated(t *testing.T) { var cli struct { Flag string `default:"invalid" enum:"valid"` } p := mustNew(t, &cli) _, err := p.Parse(nil) assert.EqualError(t, err, "--flag must be one of \"valid\" but got \"invalid\"") } func TestEnvarEnumValidated(t *testing.T) { restore := tempEnv(map[string]string{ "FLAG": "invalid", }) defer restore() var cli struct { Flag string `env:"FLAG" required:"" enum:"valid"` } p := mustNew(t, &cli) _, err := p.Parse(nil) assert.EqualError(t, err, "--flag must be one of \"valid\" but got \"invalid\"") } func TestXor(t *testing.T) { var cli struct { Hello bool `xor:"another"` One bool `xor:"group"` Two string `xor:"group"` } p := mustNew(t, &cli) _, err := p.Parse([]string{"--hello", "--one", "--two=hi"}) assert.EqualError(t, err, "--one and --two can't be used together") p = mustNew(t, &cli) _, err = p.Parse([]string{"--one", "--hello"}) assert.NoError(t, err) } func TestXorChild(t *testing.T) { var cli struct { One bool `xor:"group"` Cmd struct { Two string `xor:"group"` Three string `xor:"group"` } `cmd` } p := mustNew(t, &cli) _, err := p.Parse([]string{"--one", "cmd", "--two=hi"}) assert.NoError(t, err) p = mustNew(t, &cli) _, err = p.Parse([]string{"--two=hi", "cmd", "--three"}) assert.Error(t, err, "--two and --three can't be used together") } func TestMultiXor(t *testing.T) { var cli struct { Hello bool `xor:"one,two"` One bool `xor:"one"` Two string `xor:"two"` } p := mustNew(t, &cli) _, err := p.Parse([]string{"--hello", "--one"}) assert.EqualError(t, err, "--hello and --one can't be used together") p = mustNew(t, &cli) _, err = p.Parse([]string{"--hello", "--two=foo"}) assert.EqualError(t, err, "--hello and --two can't be used together") } func TestXorRequired(t *testing.T) { var cli struct { One bool `xor:"one,two" required:""` Two bool `xor:"one" required:""` Three bool `xor:"two" required:""` Four bool `required:""` } p := mustNew(t, &cli) _, err := p.Parse([]string{"--one"}) assert.EqualError(t, err, "missing flags: --four") p = mustNew(t, &cli) _, err = p.Parse([]string{"--two"}) assert.EqualError(t, err, "missing flags: --four, --one or --three") p = mustNew(t, &cli) _, err = p.Parse([]string{}) assert.EqualError(t, err, "missing flags: --four, --one or --three, --one or --two") } func TestXorRequiredMany(t *testing.T) { var cli struct { One bool `xor:"one" required:""` Two bool `xor:"one" required:""` Three bool `xor:"one" required:""` } p := mustNew(t, &cli) _, err := p.Parse([]string{"--one"}) assert.NoError(t, err) p = mustNew(t, &cli) _, err = p.Parse([]string{"--three"}) assert.NoError(t, err) p = mustNew(t, &cli) _, err = p.Parse([]string{}) assert.EqualError(t, err, "missing flags: --one or --two or --three") } func TestEnumSequence(t *testing.T) { var cli struct { State []string `enum:"a,b,c" default:"a"` } p := mustNew(t, &cli) _, err := p.Parse(nil) assert.NoError(t, err) assert.Equal(t, []string{"a"}, cli.State) } func TestIssue40EnumAcrossCommands(t *testing.T) { var cli struct { One struct { OneArg string `arg:"" required:""` } `cmd:""` Two struct { TwoArg string `arg:"" enum:"a,b,c" required:"" env:"FOO"` } `cmd:""` Three struct { ThreeArg string `arg:"" optional:"" default:"a" enum:"a,b,c"` } `cmd:""` } p := mustNew(t, &cli) _, err := p.Parse([]string{"one", "two"}) assert.NoError(t, err) _, err = p.Parse([]string{"two", "d"}) assert.Error(t, err) _, err = p.Parse([]string{"three", "d"}) assert.Error(t, err) _, err = p.Parse([]string{"three", "c"}) assert.NoError(t, err) } func TestIssue179(t *testing.T) { type A struct { Enum string `required:"" enum:"1,2"` } type B struct{} var root struct { A A `cmd` B B `cmd` } p := mustNew(t, &root) _, err := p.Parse([]string{"b"}) assert.NoError(t, err) } func TestIssue153(t *testing.T) { type LsCmd struct { Paths []string `arg required name:"path" help:"Paths to list." env:"CMD_PATHS"` } var cli struct { Debug bool `help:"Enable debug mode."` Ls LsCmd `cmd help:"List paths."` } p, revert := newEnvParser(t, &cli, envMap{ "CMD_PATHS": "hello", }) defer revert() _, err := p.Parse([]string{"ls"}) assert.NoError(t, err) assert.Equal(t, []string{"hello"}, cli.Ls.Paths) } func TestEnumArg(t *testing.T) { var cli struct { Nested struct { One string `arg:"" enum:"a,b,c" required:""` Two string `arg:""` } `cmd:""` } p := mustNew(t, &cli) _, err := p.Parse([]string{"nested", "a", "b"}) assert.NoError(t, err) assert.Equal(t, "a", cli.Nested.One) assert.Equal(t, "b", cli.Nested.Two) } func TestDefaultCommand(t *testing.T) { var cli struct { One struct{} `cmd:"" default:"1"` Two struct{} `cmd:""` } p := mustNew(t, &cli) ctx, err := p.Parse([]string{}) assert.NoError(t, err) assert.Equal(t, "one", ctx.Command()) } func TestMultipleDefaultCommands(t *testing.T) { var cli struct { One struct{} `cmd:"" default:"1"` Two struct{} `cmd:"" default:"1"` } _, err := kong.New(&cli) assert.EqualError(t, err, ".Two: can't have more than one default command under ") } func TestDefaultCommandWithSubCommand(t *testing.T) { var cli struct { One struct { Two struct{} `cmd:""` } `cmd:"" default:"1"` } _, err := kong.New(&cli) assert.EqualError(t, err, ".One: default command one must not have subcommands or arguments") } func TestDefaultCommandWithAllowedSubCommand(t *testing.T) { var cli struct { One struct { Two struct{} `cmd:""` } `cmd:"" default:"withargs"` } p := mustNew(t, &cli) ctx, err := p.Parse([]string{"two"}) assert.NoError(t, err) assert.Equal(t, "one two", ctx.Command()) } func TestDefaultCommandWithArgument(t *testing.T) { var cli struct { One struct { Arg string `arg:""` } `cmd:"" default:"1"` } _, err := kong.New(&cli) assert.EqualError(t, err, ".One: default command one must not have subcommands or arguments") } func TestDefaultCommandWithAllowedArgument(t *testing.T) { var cli struct { One struct { Arg string `arg:""` Flag string } `cmd:"" default:"withargs"` } p := mustNew(t, &cli) _, err := p.Parse([]string{"arg", "--flag=value"}) assert.NoError(t, err) assert.Equal(t, "arg", cli.One.Arg) assert.Equal(t, "value", cli.One.Flag) } func TestDefaultCommandWithBranchingArgument(t *testing.T) { var cli struct { One struct { Two struct { Two string `arg:""` } `arg:""` } `cmd:"" default:"1"` } _, err := kong.New(&cli) assert.EqualError(t, err, ".One: default command one must not have subcommands or arguments") } func TestDefaultCommandWithAllowedBranchingArgument(t *testing.T) { var cli struct { One struct { Two struct { Two string `arg:""` Flag string } `arg:""` } `cmd:"" default:"withargs"` } p := mustNew(t, &cli) _, err := p.Parse([]string{"arg", "--flag=value"}) assert.NoError(t, err) assert.Equal(t, "arg", cli.One.Two.Two) assert.Equal(t, "value", cli.One.Two.Flag) } func TestDefaultCommandPrecedence(t *testing.T) { var cli struct { Two struct { Arg string `arg:""` Flag bool } `cmd:"" default:"withargs"` One struct{} `cmd:""` } p := mustNew(t, &cli) // A named command should take precedence over a default command with arg ctx, err := p.Parse([]string{"one"}) assert.NoError(t, err) assert.Equal(t, "one", ctx.Command()) // An explicitly named command with arg should parse, even if labeled default:"witharg" ctx, err = p.Parse([]string{"two", "arg"}) assert.NoError(t, err) assert.Equal(t, "two ", ctx.Command()) // An arg to a default command that does not match another command should select the default ctx, err = p.Parse([]string{"arg"}) assert.NoError(t, err) assert.Equal(t, "two ", ctx.Command()) // A flag on a default command should not be valid on a sibling command _, err = p.Parse([]string{"one", "--flag"}) assert.EqualError(t, err, "unknown flag --flag") } func TestLoneHpyhen(t *testing.T) { var cli struct { Flag string Arg string `arg:"" optional:""` } p := mustNew(t, &cli) _, err := p.Parse([]string{"-"}) assert.NoError(t, err) assert.Equal(t, "-", cli.Arg) _, err = p.Parse([]string{"--flag", "-"}) assert.NoError(t, err) assert.Equal(t, "-", cli.Flag) } func TestPlugins(t *testing.T) { var pluginOne struct { One string } var pluginTwo struct { Two string } var cli struct { Base string kong.Plugins } cli.Plugins = kong.Plugins{&pluginOne, &pluginTwo} p := mustNew(t, &cli) _, err := p.Parse([]string{"--base=base", "--one=one", "--two=two"}) assert.NoError(t, err) assert.Equal(t, "base", cli.Base) assert.Equal(t, "one", pluginOne.One) assert.Equal(t, "two", pluginTwo.Two) } type validateCmd struct{} func (v *validateCmd) Validate() error { return errors.New("cmd error") } type validateCli struct { Cmd validateCmd `cmd:""` } func (v *validateCli) Validate() error { return errors.New("app error") } type validateFlag string func (v *validateFlag) Validate() error { return errors.New("flag error") } func TestValidateApp(t *testing.T) { cli := validateCli{} p := mustNew(t, &cli) _, err := p.Parse([]string{}) assert.EqualError(t, err, "app error") } func TestValidateCmd(t *testing.T) { cli := struct { Cmd validateCmd `cmd:""` }{} p := mustNew(t, &cli) _, err := p.Parse([]string{"cmd"}) assert.EqualError(t, err, "cmd: cmd error") } func TestValidateFlag(t *testing.T) { cli := struct { Flag validateFlag }{} p := mustNew(t, &cli) _, err := p.Parse([]string{"--flag=one"}) assert.EqualError(t, err, "--flag: flag error") } func TestValidateArg(t *testing.T) { cli := struct { Arg validateFlag `arg:""` }{} p := mustNew(t, &cli) _, err := p.Parse([]string{"one"}) assert.EqualError(t, err, ": flag error") } func TestPointers(t *testing.T) { cli := struct { Mapped *mappedValue JSON *jsonUnmarshalerValue }{} p := mustNew(t, &cli) _, err := p.Parse([]string{"--mapped=mapped", "--json=\"foo\""}) assert.NoError(t, err) assert.NotZero(t, cli.Mapped) assert.Equal(t, "mapped", cli.Mapped.decoded) assert.NotZero(t, cli.JSON) assert.Equal(t, "FOO", string(*cli.JSON)) } type dynamicCommand struct { Flag string ran bool } func (d *dynamicCommand) Run() error { d.ran = true return nil } func TestDynamicCommands(t *testing.T) { cli := struct { One struct{} `cmd:"one"` }{} help := &strings.Builder{} two := &dynamicCommand{} three := &dynamicCommand{} p := mustNew(t, &cli, kong.DynamicCommand("two", "", "", &two), kong.DynamicCommand("three", "", "", three, "hidden"), kong.Writers(help, help), kong.Exit(func(int) {})) kctx, err := p.Parse([]string{"two", "--flag=flag"}) assert.NoError(t, err) assert.Equal(t, "flag", two.Flag) assert.False(t, two.ran) err = kctx.Run() assert.NoError(t, err) assert.True(t, two.ran) _, err = p.Parse([]string{"--help"}) assert.EqualError(t, err, `expected one of "one", "two"`) assert.NotContains(t, help.String(), "three", help.String()) } func TestDuplicateShortflags(t *testing.T) { cli := struct { Flag1 bool `short:"t"` Flag2 bool `short:"t"` }{} _, err := kong.New(&cli) assert.EqualError(t, err, ".Flag2: duplicate short flag -t") } func TestDuplicateNestedShortFlags(t *testing.T) { cli := struct { Flag1 bool `short:"t"` Cmd struct { Flag2 bool `short:"t"` } `cmd:""` }{} _, err := kong.New(&cli) assert.EqualError(t, err, ".Flag2: duplicate short flag -t") } func TestHydratePointerCommandsAndEmbeds(t *testing.T) { type cmd struct { Flag bool } type embed struct { Embed bool } var cli struct { Cmd *cmd `cmd:""` Embed *embed `embed:""` } k := mustNew(t, &cli) _, err := k.Parse([]string{"--embed", "cmd", "--flag"}) assert.NoError(t, err) assert.Equal(t, &cmd{Flag: true}, cli.Cmd) assert.Equal(t, &embed{Embed: true}, cli.Embed) } // nolint type testIgnoreFields struct { Foo struct { Bar bool Sub struct { SubFlag1 bool `kong:"name=subflag1"` XXX_SubFlag2 bool `kong:"name=subflag2"` } `kong:"cmd"` } `kong:"cmd"` XXX_Baz struct { Boo bool } `kong:"cmd,name=baz"` } func TestIgnoreRegex(t *testing.T) { cli := testIgnoreFields{} k, err := kong.New(&cli, kong.IgnoreFields(`.*\.XXX_.+`)) assert.NoError(t, err) _, err = k.Parse([]string{"foo", "sub"}) assert.NoError(t, err) _, err = k.Parse([]string{"foo", "sub", "--subflag1"}) assert.NoError(t, err) _, err = k.Parse([]string{"foo", "sub", "--subflag2"}) assert.Error(t, err) assert.Contains(t, err.Error(), "unknown flag --subflag2") _, err = k.Parse([]string{"baz"}) assert.Error(t, err) assert.Contains(t, err.Error(), "unexpected argument baz") } // Verify that passing a nil regex will work func TestIgnoreRegexEmpty(t *testing.T) { cli := testIgnoreFields{} _, err := kong.New(&cli, kong.IgnoreFields("")) assert.Error(t, err) assert.Contains(t, "regex input cannot be empty", err.Error()) } type optionWithErr struct{} func (o *optionWithErr) Apply(k *kong.Kong) error { return errors.New("option returned err") } func TestOptionReturnsErr(t *testing.T) { cli := struct { Test bool }{} optWithError := &optionWithErr{} _, err := kong.New(cli, optWithError) assert.Error(t, err) assert.Equal(t, "option returned err", err.Error()) } func TestEnumValidation(t *testing.T) { 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, }, { "EnumWithEmptyDefault", &struct { Flag string `enum:"one,two," default:""` }{}, false, }, } for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { _, err := kong.New(test.cli) if test.fail { assert.Error(t, err, repr.String(test.cli)) } else { assert.NoError(t, err, repr.String(test.cli)) } }) } } func TestPassthroughCmd(t *testing.T) { tests := []struct { name string args []string flag string cmdArgs []string }{ { "Simple", []string{"--flag", "foobar", "command", "something"}, "foobar", []string{"something"}, }, { "DashDash", []string{"--flag", "foobar", "command", "--", "something"}, "foobar", []string{"--", "something"}, }, { "Flag", []string{"command", "--flag", "foobar"}, "", []string{"--flag", "foobar"}, }, { "FlagAndFlag", []string{"--flag", "foobar", "command", "--flag", "foobar"}, "foobar", []string{"--flag", "foobar"}, }, { "NoArgs", []string{"--flag", "foobar", "command"}, "foobar", []string(nil), }, } for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { var cli struct { Flag string Command struct { Args []string `arg:"" optional:""` } `cmd:"" passthrough:""` } p := mustNew(t, &cli) _, err := p.Parse(test.args) assert.NoError(t, err) assert.Equal(t, test.flag, cli.Flag) assert.Equal(t, test.cmdArgs, cli.Command.Args) }) } } func TestPassthroughCmdOnlyArgs(t *testing.T) { var cli struct { Command struct { Flag string Args []string `arg:"" optional:""` } `cmd:"" passthrough:""` } _, err := kong.New(&cli) assert.EqualError(t, err, ".Command: passthrough command command [ ...] must not have subcommands or flags") } func TestPassthroughCmdOnlyStringArgs(t *testing.T) { var cli struct { Command struct { Args []int `arg:"" optional:""` } `cmd:"" passthrough:""` } _, err := kong.New(&cli) assert.EqualError(t, err, ".Command: passthrough command command [ ...] must contain exactly one positional argument of []string type") } func TestHelpShouldStillWork(t *testing.T) { type CLI struct { Dir string `type:"existingdir" default:"missing-dir"` File string `type:"existingfile" default:"testdata/missing.txt"` } var cli CLI w := &strings.Builder{} k := mustNew(t, &cli, kong.Writers(w, w)) rc := -1 // init nonzero to help assert help hook was called k.Exit = func(i int) { rc = i } _, err := k.Parse([]string{"--help"}) t.Log(w.String()) // checking return code validates the help hook was called assert.Zero(t, rc) // allow for error propagation from other validation (only for the // sake of this test, due to the exit function not actually exiting the // program; errors will not propagate in the real world). assert.Error(t, err) } func TestVersionFlagShouldStillWork(t *testing.T) { type CLI struct { Dir string `type:"existingdir" default:"missing-dir"` File string `type:"existingfile" default:"testdata/missing.txt"` Version kong.VersionFlag } var cli CLI w := &strings.Builder{} k := mustNew(t, &cli, kong.Writers(w, w)) rc := -1 // init nonzero to help assert help hook was called k.Exit = func(i int) { rc = i } _, err := k.Parse([]string{"--version"}) t.Log(w.String()) // checking return code validates the help hook was called assert.Zero(t, rc) // allow for error propagation from other validation (only for the // sake of this test, due to the exit function not actually exiting the // program; errors will not propagate in the real world). assert.Error(t, err) } func TestSliceDecoderHelpfulErrorMsg(t *testing.T) { tests := []struct { name string cli interface{} args []string err string }{ { "DefaultRune", &struct { Stuff []string }{}, []string{"--stuff"}, `--stuff: missing value, expecting ",..."`, }, { "SpecifiedRune", &struct { Stuff []string `sep:","` }{}, []string{"--stuff"}, `--stuff: missing value, expecting ",..."`, }, { "SpaceRune", &struct { Stuff []string `sep:" "` }{}, []string{"--stuff"}, `--stuff: missing value, expecting " ..."`, }, { "OtherRune", &struct { Stuff []string `sep:"_"` }{}, []string{"--stuff"}, `--stuff: missing value, expecting "_..."`, }, } for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { p := mustNew(t, test.cli) _, err := p.Parse(test.args) assert.EqualError(t, err, test.err) }) } } func TestMapDecoderHelpfulErrorMsg(t *testing.T) { tests := []struct { name string cli interface{} args []string expected string }{ { "DefaultRune", &struct { Stuff map[string]int }{}, []string{"--stuff"}, `--stuff: missing value, expecting "=;..."`, }, { "SpecifiedRune", &struct { Stuff map[string]int `mapsep:";"` }{}, []string{"--stuff"}, `--stuff: missing value, expecting "=;..."`, }, { "SpaceRune", &struct { Stuff map[string]int `mapsep:" "` }{}, []string{"--stuff"}, `--stuff: missing value, expecting "= ..."`, }, { "OtherRune", &struct { Stuff map[string]int `mapsep:","` }{}, []string{"--stuff"}, `--stuff: missing value, expecting "=,..."`, }, } for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { p := mustNew(t, test.cli) _, err := p.Parse(test.args) assert.EqualError(t, err, test.expected) }) } } func TestDuplicateName(t *testing.T) { var cli struct { DupA struct{} `cmd:"" name:"duplicate"` DupB struct{} `cmd:"" name:"duplicate"` } _, err := kong.New(&cli) assert.Error(t, err) } func TestDuplicateChildName(t *testing.T) { var cli struct { A struct { DupA struct{} `cmd:"" name:"duplicate"` DupB struct{} `cmd:"" name:"duplicate"` } `cmd:""` B struct{} `cmd:""` } _, err := kong.New(&cli) assert.Error(t, err) } func TestChildNameCanBeDuplicated(t *testing.T) { var cli struct { A struct { A struct{} `cmd:"" name:"duplicateA"` B struct{} `cmd:"" name:"duplicateB"` } `cmd:"" name:"duplicateA"` B struct{} `cmd:"" name:"duplicateB"` } mustNew(t, &cli) } func TestCumulativeArgumentLast(t *testing.T) { var cli struct { Arg1 string `arg:""` Arg2 []string `arg:""` } _, err := kong.New(&cli) assert.NoError(t, err) } func TestCumulativeArgumentNotLast(t *testing.T) { var cli struct { Arg2 []string `arg:""` Arg1 string `arg:""` } _, err := kong.New(&cli) assert.Error(t, err) } func TestStringPointer(t *testing.T) { var cli struct { Foo *string } k, err := kong.New(&cli) assert.NoError(t, err) assert.NotZero(t, k) ctx, err := k.Parse([]string{"--foo", "wtf"}) assert.NoError(t, err) assert.NotZero(t, ctx) assert.NotZero(t, cli.Foo) assert.Equal(t, "wtf", *cli.Foo) } func TestStringPointerNoValue(t *testing.T) { var cli struct { Foo *string } k, err := kong.New(&cli) assert.NoError(t, err) assert.NotZero(t, k) ctx, err := k.Parse([]string{}) assert.NoError(t, err) assert.NotZero(t, ctx) assert.Zero(t, cli.Foo) } func TestStringPointerDefault(t *testing.T) { var cli struct { Foo *string `default:"stuff"` } k, err := kong.New(&cli) assert.NoError(t, err) assert.NotZero(t, k) ctx, err := k.Parse([]string{}) assert.NoError(t, err) assert.NotZero(t, ctx) assert.NotZero(t, cli.Foo) assert.Equal(t, "stuff", *cli.Foo) } func TestStringPointerAliasNoValue(t *testing.T) { type Foo string var cli struct { F *Foo } k, err := kong.New(&cli) assert.NoError(t, err) assert.NotZero(t, k) ctx, err := k.Parse([]string{}) assert.NoError(t, err) assert.NotZero(t, ctx) assert.Zero(t, cli.F) } func TestStringPointerAlias(t *testing.T) { type Foo string var cli struct { F *Foo } k, err := kong.New(&cli) assert.NoError(t, err) assert.NotZero(t, k) ctx, err := k.Parse([]string{"--f=value"}) assert.NoError(t, err) assert.NotZero(t, ctx) assert.NotZero(t, cli.F) assert.Equal(t, Foo("value"), *cli.F) } func TestStringPointerEmptyValue(t *testing.T) { var cli struct { F *string G *string } k, err := kong.New(&cli) assert.NoError(t, err) assert.NotZero(t, k) ctx, err := k.Parse([]string{"--f", "", "--g="}) assert.NoError(t, err) assert.NotZero(t, ctx) assert.NotZero(t, cli.F) assert.NotZero(t, cli.G) assert.Equal(t, "", *cli.F) assert.Equal(t, "", *cli.G) } func TestIntPtr(t *testing.T) { var cli struct { F *int G *int } k, err := kong.New(&cli) assert.NoError(t, err) assert.NotZero(t, k) ctx, err := k.Parse([]string{"--f=6"}) assert.NoError(t, err) assert.NotZero(t, ctx) assert.NotZero(t, cli.F) assert.Zero(t, cli.G) assert.Equal(t, 6, *cli.F) } func TestBoolPtr(t *testing.T) { var cli struct { X *bool } k, err := kong.New(&cli) assert.NoError(t, err) assert.NotZero(t, k) ctx, err := k.Parse([]string{"--x"}) assert.NoError(t, err) assert.NotZero(t, ctx) assert.NotZero(t, cli.X) assert.Equal(t, true, *cli.X) } func TestBoolPtrFalse(t *testing.T) { var cli struct { X *bool } k, err := kong.New(&cli) assert.NoError(t, err) assert.NotZero(t, k) ctx, err := k.Parse([]string{"--x=false"}) assert.NoError(t, err) assert.NotZero(t, ctx) assert.NotZero(t, cli.X) assert.Equal(t, false, *cli.X) } func TestBoolPtrNegated(t *testing.T) { var cli struct { X *bool `negatable:""` } k, err := kong.New(&cli) assert.NoError(t, err) assert.NotZero(t, k) ctx, err := k.Parse([]string{"--no-x"}) assert.NoError(t, err) assert.NotZero(t, ctx) assert.NotZero(t, cli.X) assert.Equal(t, false, *cli.X) } func TestNilNegatableBoolPtr(t *testing.T) { var cli struct { X *bool `negatable:""` } k, err := kong.New(&cli) assert.NoError(t, err) assert.NotZero(t, k) ctx, err := k.Parse([]string{}) assert.NoError(t, err) assert.NotZero(t, ctx) assert.Zero(t, cli.X) } func TestBoolPtrNil(t *testing.T) { var cli struct { X *bool } k, err := kong.New(&cli) assert.NoError(t, err) assert.NotZero(t, k) ctx, err := k.Parse([]string{}) assert.NoError(t, err) assert.NotZero(t, ctx) assert.Zero(t, cli.X) } func TestUnsupportedPtr(t *testing.T) { type Foo struct { x int // nolint y int // nolint } var cli struct { F *Foo } k, err := kong.New(&cli) assert.NoError(t, err) assert.NotZero(t, k) ctx, err := k.Parse([]string{"--f=whatever"}) assert.Zero(t, ctx) assert.Error(t, err) assert.Equal(t, "--f: cannot find mapper for kong_test.Foo", err.Error()) } func TestEnumPtr(t *testing.T) { var cli struct { X *string `enum:"A,B,C" default:"C"` } k, err := kong.New(&cli) assert.NoError(t, err) assert.NotZero(t, k) ctx, err := k.Parse([]string{"--x=A"}) assert.NoError(t, err) assert.NotZero(t, ctx) assert.NotZero(t, cli.X) assert.Equal(t, "A", *cli.X) } func TestEnumPtrOmitted(t *testing.T) { var cli struct { X *string `enum:"A,B,C" default:"C"` } k, err := kong.New(&cli) assert.NoError(t, err) assert.NotZero(t, k) ctx, err := k.Parse([]string{}) assert.NoError(t, err) assert.NotZero(t, ctx) assert.NotZero(t, cli.X) assert.Equal(t, "C", *cli.X) } func TestEnumPtrOmittedNoDefault(t *testing.T) { var cli struct { X *string `enum:"A,B,C"` } k, err := kong.New(&cli) assert.NoError(t, err) assert.NotZero(t, k) ctx, err := k.Parse([]string{}) assert.NoError(t, err) assert.NotZero(t, ctx) assert.Zero(t, cli.X) } kong-0.8.1/levenshtein.go000066400000000000000000000014121451020262500153240ustar00rootroot00000000000000package kong import "unicode/utf8" // https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#Go // License: https://creativecommons.org/licenses/by-sa/3.0/ func levenshtein(a, b string) int { f := make([]int, utf8.RuneCountInString(b)+1) for j := range f { f[j] = j } for _, ca := range a { j := 1 fj1 := f[0] // fj1 is the value of f[j - 1] in last iteration f[0]++ for _, cb := range b { mn := min(f[j]+1, f[j-1]+1) // delete & insert if cb != ca { mn = min(mn, fj1+1) // change } else { mn = min(mn, fj1) // matched } fj1, f[j] = f[j], mn // save f[j] to fj1(j is about to increase), update f[j] to mn j++ } } return f[len(f)-1] } func min(a, b int) int { if a <= b { return a } return b } kong-0.8.1/mapper.go000066400000000000000000000617431451020262500143010ustar00rootroot00000000000000package kong import ( "encoding" "encoding/json" "errors" "fmt" "io/ioutil" "math/bits" "net/url" "os" "reflect" "strconv" "strings" "time" ) var ( mapperValueType = reflect.TypeOf((*MapperValue)(nil)).Elem() boolMapperValueType = reflect.TypeOf((*BoolMapperValue)(nil)).Elem() jsonUnmarshalerType = reflect.TypeOf((*json.Unmarshaler)(nil)).Elem() textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() binaryUnmarshalerType = reflect.TypeOf((*encoding.BinaryUnmarshaler)(nil)).Elem() ) // DecodeContext is passed to a Mapper's Decode(). // // It contains the Value being decoded into and the Scanner to parse from. type DecodeContext struct { // Value being decoded into. Value *Value // Scan contains the input to scan into Target. Scan *Scanner } // WithScanner creates a clone of this context with a new Scanner. func (r *DecodeContext) WithScanner(scan *Scanner) *DecodeContext { return &DecodeContext{ Value: r.Value, Scan: scan, } } // MapperValue may be implemented by fields in order to provide custom mapping. // Mappers may additionally implement PlaceHolderProvider to provide custom placeholder text. type MapperValue interface { Decode(ctx *DecodeContext) error } // BoolMapperValue may be implemented by fields in order to provide custom mappings for boolean values. type BoolMapperValue interface { MapperValue IsBool() bool } type mapperValueAdapter struct { isBool bool } func (m *mapperValueAdapter) Decode(ctx *DecodeContext, target reflect.Value) error { if target.Type().Implements(mapperValueType) { return target.Interface().(MapperValue).Decode(ctx) // nolint } return target.Addr().Interface().(MapperValue).Decode(ctx) // nolint } func (m *mapperValueAdapter) IsBool() bool { return m.isBool } type textUnmarshalerAdapter struct{} func (m *textUnmarshalerAdapter) Decode(ctx *DecodeContext, target reflect.Value) error { var value string err := ctx.Scan.PopValueInto("value", &value) if err != nil { return err } if target.Type().Implements(textUnmarshalerType) { return target.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(value)) // nolint } return target.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(value)) // nolint } type binaryUnmarshalerAdapter struct{} func (m *binaryUnmarshalerAdapter) Decode(ctx *DecodeContext, target reflect.Value) error { var value string err := ctx.Scan.PopValueInto("value", &value) if err != nil { return err } if target.Type().Implements(binaryUnmarshalerType) { return target.Interface().(encoding.BinaryUnmarshaler).UnmarshalBinary([]byte(value)) // nolint } return target.Addr().Interface().(encoding.BinaryUnmarshaler).UnmarshalBinary([]byte(value)) // nolint } type jsonUnmarshalerAdapter struct{} func (j *jsonUnmarshalerAdapter) Decode(ctx *DecodeContext, target reflect.Value) error { var value string err := ctx.Scan.PopValueInto("value", &value) if err != nil { return err } if target.Type().Implements(jsonUnmarshalerType) { return target.Interface().(json.Unmarshaler).UnmarshalJSON([]byte(value)) // nolint } return target.Addr().Interface().(json.Unmarshaler).UnmarshalJSON([]byte(value)) // nolint } // A Mapper represents how a field is mapped from command-line values to Go. // // Mappers can be associated with concrete fields via pointer, reflect.Type, reflect.Kind, or via a "type" tag. // // Additionally, if a type implements the MapperValue interface, it will be used. type Mapper interface { // Decode ctx.Value with ctx.Scanner into target. Decode(ctx *DecodeContext, target reflect.Value) error } // VarsContributor can be implemented by a Mapper to contribute Vars during interpolation. type VarsContributor interface { Vars(ctx *Value) Vars } // A BoolMapper is a Mapper to a value that is a boolean. // // This is used solely for formatting help. type BoolMapper interface { Mapper IsBool() bool } // BoolMapperExt allows a Mapper to dynamically determine if a value is a boolean. type BoolMapperExt interface { Mapper IsBoolFromValue(v reflect.Value) bool } // A MapperFunc is a single function that complies with the Mapper interface. type MapperFunc func(ctx *DecodeContext, target reflect.Value) error func (m MapperFunc) Decode(ctx *DecodeContext, target reflect.Value) error { // nolint: revive return m(ctx, target) } // A Registry contains a set of mappers and supporting lookup methods. type Registry struct { names map[string]Mapper types map[reflect.Type]Mapper kinds map[reflect.Kind]Mapper values map[reflect.Value]Mapper } // NewRegistry creates a new (empty) Registry. func NewRegistry() *Registry { return &Registry{ names: map[string]Mapper{}, types: map[reflect.Type]Mapper{}, kinds: map[reflect.Kind]Mapper{}, values: map[reflect.Value]Mapper{}, } } // ForNamedValue finds a mapper for a value with a user-specified name. // // Will return nil if a mapper can not be determined. func (r *Registry) ForNamedValue(name string, value reflect.Value) Mapper { if mapper, ok := r.names[name]; ok { return mapper } return r.ForValue(value) } // ForValue looks up the Mapper for a reflect.Value. func (r *Registry) ForValue(value reflect.Value) Mapper { if mapper, ok := r.values[value]; ok { return mapper } return r.ForType(value.Type()) } // ForNamedType finds a mapper for a type with a user-specified name. // // Will return nil if a mapper can not be determined. func (r *Registry) ForNamedType(name string, typ reflect.Type) Mapper { if mapper, ok := r.names[name]; ok { return mapper } return r.ForType(typ) } // ForType finds a mapper from a type, by type, then kind. // // Will return nil if a mapper can not be determined. func (r *Registry) ForType(typ reflect.Type) Mapper { // Check if the type implements MapperValue. for _, impl := range []reflect.Type{typ, reflect.PtrTo(typ)} { if impl.Implements(mapperValueType) { // FIXME: This should pass in the bool mapper. return &mapperValueAdapter{impl.Implements(boolMapperValueType)} } } // Next, try explicitly registered types. var mapper Mapper var ok bool if mapper, ok = r.types[typ]; ok { return mapper } // Next try stdlib unmarshaler interfaces. for _, impl := range []reflect.Type{typ, reflect.PtrTo(typ)} { switch { case impl.Implements(textUnmarshalerType): return &textUnmarshalerAdapter{} case impl.Implements(binaryUnmarshalerType): return &binaryUnmarshalerAdapter{} case impl.Implements(jsonUnmarshalerType): return &jsonUnmarshalerAdapter{} } } // Finally try registered kinds. if mapper, ok = r.kinds[typ.Kind()]; ok { return mapper } return nil } // RegisterKind registers a Mapper for a reflect.Kind. func (r *Registry) RegisterKind(kind reflect.Kind, mapper Mapper) *Registry { r.kinds[kind] = mapper return r } // RegisterName registers a mapper to be used if the value mapper has a "type" tag matching name. // // eg. // // Mapper string `kong:"type='colour'` // registry.RegisterName("colour", ...) func (r *Registry) RegisterName(name string, mapper Mapper) *Registry { r.names[name] = mapper return r } // RegisterType registers a Mapper for a reflect.Type. func (r *Registry) RegisterType(typ reflect.Type, mapper Mapper) *Registry { r.types[typ] = mapper return r } // RegisterValue registers a Mapper by pointer to the field value. func (r *Registry) RegisterValue(ptr interface{}, mapper Mapper) *Registry { key := reflect.ValueOf(ptr) if key.Kind() != reflect.Ptr { panic("expected a pointer") } key = key.Elem() r.values[key] = mapper return r } // RegisterDefaults registers Mappers for all builtin supported Go types and some common stdlib types. func (r *Registry) RegisterDefaults() *Registry { return r.RegisterKind(reflect.Int, intDecoder(bits.UintSize)). RegisterKind(reflect.Int8, intDecoder(8)). RegisterKind(reflect.Int16, intDecoder(16)). RegisterKind(reflect.Int32, intDecoder(32)). RegisterKind(reflect.Int64, intDecoder(64)). RegisterKind(reflect.Uint, uintDecoder(bits.UintSize)). RegisterKind(reflect.Uint8, uintDecoder(8)). RegisterKind(reflect.Uint16, uintDecoder(16)). RegisterKind(reflect.Uint32, uintDecoder(32)). RegisterKind(reflect.Uint64, uintDecoder(64)). RegisterKind(reflect.Float32, floatDecoder(32)). RegisterKind(reflect.Float64, floatDecoder(64)). RegisterKind(reflect.String, MapperFunc(func(ctx *DecodeContext, target reflect.Value) error { return ctx.Scan.PopValueInto("string", target.Addr().Interface()) })). RegisterKind(reflect.Bool, boolMapper{}). RegisterKind(reflect.Slice, sliceDecoder(r)). RegisterKind(reflect.Map, mapDecoder(r)). RegisterType(reflect.TypeOf(time.Time{}), timeDecoder()). RegisterType(reflect.TypeOf(time.Duration(0)), durationDecoder()). RegisterType(reflect.TypeOf(&url.URL{}), urlMapper()). RegisterType(reflect.TypeOf(&os.File{}), fileMapper(r)). RegisterName("path", pathMapper(r)). RegisterName("existingfile", existingFileMapper(r)). RegisterName("existingdir", existingDirMapper(r)). RegisterName("counter", counterMapper()). RegisterName("filecontent", fileContentMapper(r)). RegisterKind(reflect.Ptr, ptrMapper{r}) } type boolMapper struct{} func (boolMapper) Decode(ctx *DecodeContext, target reflect.Value) error { if ctx.Scan.Peek().Type == FlagValueToken { token := ctx.Scan.Pop() switch v := token.Value.(type) { case string: v = strings.ToLower(v) switch v { case "true", "1", "yes": target.SetBool(true) case "false", "0", "no": target.SetBool(false) default: return fmt.Errorf("bool value must be true, 1, yes, false, 0 or no but got %q", v) } case bool: target.SetBool(v) default: return fmt.Errorf("expected bool but got %q (%T)", token.Value, token.Value) } } else { target.SetBool(true) } return nil } func (boolMapper) IsBool() bool { return true } func durationDecoder() MapperFunc { return func(ctx *DecodeContext, target reflect.Value) error { t, err := ctx.Scan.PopValue("duration") if err != nil { return err } var d time.Duration switch v := t.Value.(type) { case string: d, err = time.ParseDuration(v) if err != nil { return fmt.Errorf("expected duration but got %q: %v", v, err) } case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64: d = reflect.ValueOf(v).Convert(reflect.TypeOf(time.Duration(0))).Interface().(time.Duration) // nolint: forcetypeassert default: return fmt.Errorf("expected duration but got %q", v) } target.Set(reflect.ValueOf(d)) return nil } } func timeDecoder() MapperFunc { return func(ctx *DecodeContext, target reflect.Value) error { format := time.RFC3339 if ctx.Value.Format != "" { format = ctx.Value.Format } var value string if err := ctx.Scan.PopValueInto("time", &value); err != nil { return err } t, err := time.Parse(format, value) if err != nil { return err } target.Set(reflect.ValueOf(t)) return nil } } func intDecoder(bits int) MapperFunc { // nolint: dupl return func(ctx *DecodeContext, target reflect.Value) error { t, err := ctx.Scan.PopValue("int") if err != nil { return err } var sv string switch v := t.Value.(type) { case string: sv = v case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: sv = fmt.Sprintf("%v", v) case float32, float64: sv = fmt.Sprintf("%0.f", v) default: return fmt.Errorf("expected an int but got %q (%T)", t, t.Value) } n, err := strconv.ParseInt(sv, 10, bits) if err != nil { return fmt.Errorf("expected a valid %d bit int but got %q", bits, sv) } target.SetInt(n) return nil } } func uintDecoder(bits int) MapperFunc { // nolint: dupl return func(ctx *DecodeContext, target reflect.Value) error { t, err := ctx.Scan.PopValue("uint") if err != nil { return err } var sv string switch v := t.Value.(type) { case string: sv = v case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: sv = fmt.Sprintf("%v", v) case float32, float64: sv = fmt.Sprintf("%0.f", v) default: return fmt.Errorf("expected an int but got %q (%T)", t, t.Value) } n, err := strconv.ParseUint(sv, 10, bits) if err != nil { return fmt.Errorf("expected a valid %d bit uint but got %q", bits, sv) } target.SetUint(n) return nil } } func floatDecoder(bits int) MapperFunc { return func(ctx *DecodeContext, target reflect.Value) error { t, err := ctx.Scan.PopValue("float") if err != nil { return err } switch v := t.Value.(type) { case string: n, err := strconv.ParseFloat(v, bits) if err != nil { return fmt.Errorf("expected a float but got %q (%T)", t, t.Value) } target.SetFloat(n) case float32: target.SetFloat(float64(v)) case float64: target.SetFloat(v) case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: target.Set(reflect.ValueOf(v)) default: return fmt.Errorf("expected an int but got %q (%T)", t, t.Value) } return nil } } func mapDecoder(r *Registry) MapperFunc { return func(ctx *DecodeContext, target reflect.Value) error { if target.IsNil() { target.Set(reflect.MakeMap(target.Type())) } el := target.Type() mapsep := ctx.Value.Tag.MapSep var childScanner *Scanner if ctx.Value.Flag != nil { t := ctx.Scan.Pop() // If decoding a flag, we need an value. if t.IsEOL() { return fmt.Errorf("missing value, expecting \"=%c...\"", mapsep) } switch v := t.Value.(type) { case string: childScanner = ScanAsType(t.Type, SplitEscaped(v, mapsep)...) case []map[string]interface{}: for _, m := range v { err := jsonTranscode(m, target.Addr().Interface()) if err != nil { return err } } return nil case map[string]interface{}: return jsonTranscode(v, target.Addr().Interface()) default: return fmt.Errorf("invalid map value %q (of type %T)", t, t.Value) } } else { tokens := ctx.Scan.PopWhile(func(t Token) bool { return t.IsValue() }) childScanner = ScanFromTokens(tokens...) } for !childScanner.Peek().IsEOL() { var token string err := childScanner.PopValueInto("map", &token) if err != nil { return err } parts := strings.SplitN(token, "=", 2) if len(parts) != 2 { return fmt.Errorf("expected \"=\" but got %q", token) } key, value := parts[0], parts[1] keyTypeName, valueTypeName := "", "" if typ := ctx.Value.Tag.Type; typ != "" { parts := strings.Split(typ, ":") if len(parts) != 2 { return errors.New("type:\"\" on map field must be in the form \"[]:[]\"") } keyTypeName, valueTypeName = parts[0], parts[1] } keyScanner := ScanAsType(FlagValueToken, key) keyDecoder := r.ForNamedType(keyTypeName, el.Key()) keyValue := reflect.New(el.Key()).Elem() if err := keyDecoder.Decode(ctx.WithScanner(keyScanner), keyValue); err != nil { return fmt.Errorf("invalid map key %q", key) } valueScanner := ScanAsType(FlagValueToken, value) valueDecoder := r.ForNamedType(valueTypeName, el.Elem()) valueValue := reflect.New(el.Elem()).Elem() if err := valueDecoder.Decode(ctx.WithScanner(valueScanner), valueValue); err != nil { return fmt.Errorf("invalid map value %q", value) } target.SetMapIndex(keyValue, valueValue) } return nil } } func sliceDecoder(r *Registry) MapperFunc { return func(ctx *DecodeContext, target reflect.Value) error { el := target.Type().Elem() sep := ctx.Value.Tag.Sep var childScanner *Scanner if ctx.Value.Flag != nil { t := ctx.Scan.Pop() // If decoding a flag, we need an value. if t.IsEOL() { return fmt.Errorf("missing value, expecting \"%c...\"", sep) } switch v := t.Value.(type) { case string: childScanner = ScanAsType(t.Type, SplitEscaped(v, sep)...) case []interface{}: return jsonTranscode(v, target.Addr().Interface()) default: v = []interface{}{v} return jsonTranscode(v, target.Addr().Interface()) } } else { tokens := ctx.Scan.PopWhile(func(t Token) bool { return t.IsValue() }) childScanner = ScanFromTokens(tokens...) } childDecoder := r.ForNamedType(ctx.Value.Tag.Type, el) if childDecoder == nil { return fmt.Errorf("no mapper for element type of %s", target.Type()) } for !childScanner.Peek().IsEOL() { childValue := reflect.New(el).Elem() err := childDecoder.Decode(ctx.WithScanner(childScanner), childValue) if err != nil { return err } target.Set(reflect.Append(target, childValue)) } return nil } } func pathMapper(r *Registry) MapperFunc { return func(ctx *DecodeContext, target reflect.Value) error { if target.Kind() == reflect.Slice { return sliceDecoder(r)(ctx, target) } if target.Kind() != reflect.String { return fmt.Errorf("\"path\" type must be applied to a string not %s", target.Type()) } var path string err := ctx.Scan.PopValueInto("file", &path) if err != nil { return err } if path != "-" { path = ExpandPath(path) } target.SetString(path) return nil } } func fileMapper(r *Registry) MapperFunc { return func(ctx *DecodeContext, target reflect.Value) error { if target.Kind() == reflect.Slice { return sliceDecoder(r)(ctx, target) } var path string err := ctx.Scan.PopValueInto("file", &path) if err != nil { return err } var file *os.File if path == "-" { file = os.Stdin } else { path = ExpandPath(path) file, err = os.Open(path) // nolint: gosec if err != nil { return err } } target.Set(reflect.ValueOf(file)) return nil } } func existingFileMapper(r *Registry) MapperFunc { return func(ctx *DecodeContext, target reflect.Value) error { if target.Kind() == reflect.Slice { return sliceDecoder(r)(ctx, target) } if target.Kind() != reflect.String { return fmt.Errorf("\"existingfile\" type must be applied to a string not %s", target.Type()) } var path string err := ctx.Scan.PopValueInto("file", &path) if err != nil { return err } if !ctx.Value.Active || ctx.Value.Set { // early return to avoid checking extra files that may not exist; // this hack only works because the value provided on the cli is // checked before the default value is checked (if default is set). return nil } if path != "-" { path = ExpandPath(path) stat, err := os.Stat(path) if err != nil { return err } if stat.IsDir() { return fmt.Errorf("%q exists but is a directory", path) } } target.SetString(path) return nil } } func existingDirMapper(r *Registry) MapperFunc { return func(ctx *DecodeContext, target reflect.Value) error { if target.Kind() == reflect.Slice { return sliceDecoder(r)(ctx, target) } if target.Kind() != reflect.String { return fmt.Errorf("\"existingdir\" must be applied to a string not %s", target.Type()) } var path string err := ctx.Scan.PopValueInto("file", &path) if err != nil { return err } if !ctx.Value.Active || ctx.Value.Set { // early return to avoid checking extra dirs that may not exist; // this hack only works because the value provided on the cli is // checked before the default value is checked (if default is set). return nil } path = ExpandPath(path) stat, err := os.Stat(path) if err != nil { return err } if !stat.IsDir() { return fmt.Errorf("%q exists but is not a directory", path) } target.SetString(path) return nil } } func fileContentMapper(r *Registry) MapperFunc { return func(ctx *DecodeContext, target reflect.Value) error { if target.Kind() != reflect.Slice && target.Elem().Kind() != reflect.Uint8 { return fmt.Errorf("\"filecontent\" must be applied to []byte not %s", target.Type()) } var path string err := ctx.Scan.PopValueInto("file", &path) if err != nil { return err } if !ctx.Value.Active || ctx.Value.Set { // early return to avoid checking extra dirs that may not exist; // this hack only works because the value provided on the cli is // checked before the default value is checked (if default is set). return nil } var data []byte if path != "-" { path = ExpandPath(path) data, err = ioutil.ReadFile(path) //nolint:gosec } else { data, err = ioutil.ReadAll(os.Stdin) } if err != nil { return err } target.SetBytes(data) return nil } } type ptrMapper struct { r *Registry } var _ BoolMapperExt = (*ptrMapper)(nil) // IsBoolFromValue implements BoolMapperExt func (p ptrMapper) IsBoolFromValue(target reflect.Value) bool { elem := reflect.New(target.Type().Elem()).Elem() nestedMapper := p.r.ForValue(elem) if nestedMapper == nil { return false } if bm, ok := nestedMapper.(BoolMapper); ok && bm.IsBool() { return true } if bm, ok := nestedMapper.(BoolMapperExt); ok && bm.IsBoolFromValue(target) { return true } return target.Kind() == reflect.Ptr && target.Type().Elem().Kind() == reflect.Bool } func (p ptrMapper) Decode(ctx *DecodeContext, target reflect.Value) error { elem := reflect.New(target.Type().Elem()).Elem() nestedMapper := p.r.ForValue(elem) if nestedMapper == nil { return fmt.Errorf("cannot find mapper for %v", target.Type().Elem().String()) } err := nestedMapper.Decode(ctx, elem) if err != nil { return err } target.Set(elem.Addr()) return nil } func counterMapper() MapperFunc { return func(ctx *DecodeContext, target reflect.Value) error { if ctx.Scan.Peek().Type == FlagValueToken { t, err := ctx.Scan.PopValue("counter") if err != nil { return err } switch v := t.Value.(type) { case string: n, err := strconv.ParseInt(v, 10, 64) if err != nil { return fmt.Errorf("expected a counter but got %q (%T)", t, t.Value) } target.SetInt(n) case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: target.Set(reflect.ValueOf(v)) default: return fmt.Errorf("expected a counter but got %q (%T)", t, t.Value) } return nil } switch target.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: target.SetInt(target.Int() + 1) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: target.SetUint(target.Uint() + 1) case reflect.Float32, reflect.Float64: target.SetFloat(target.Float() + 1) default: return fmt.Errorf("type:\"counter\" must be used with a numeric field") } return nil } } func urlMapper() MapperFunc { return func(ctx *DecodeContext, target reflect.Value) error { var urlStr string err := ctx.Scan.PopValueInto("url", &urlStr) if err != nil { return err } url, err := url.Parse(urlStr) if err != nil { return err } target.Set(reflect.ValueOf(url)) return nil } } // SplitEscaped splits a string on a separator. // // It differs from strings.Split() in that the separator can exist in a field by escaping it with a \. eg. // // SplitEscaped(`hello\,there,bob`, ',') == []string{"hello,there", "bob"} func SplitEscaped(s string, sep rune) (out []string) { if sep == -1 { return []string{s} } escaped := false token := "" for i, ch := range s { switch { case escaped: if ch != sep { token += `\` } token += string(ch) escaped = false case ch == '\\' && i < len(s)-1: escaped = true case ch == sep && !escaped: out = append(out, token) token = "" escaped = false default: token += string(ch) } } if token != "" { out = append(out, token) } return } // JoinEscaped joins a slice of strings on sep, but also escapes any instances of sep in the fields with \. eg. // // JoinEscaped([]string{"hello,there", "bob"}, ',') == `hello\,there,bob` func JoinEscaped(s []string, sep rune) string { escaped := []string{} for _, e := range s { escaped = append(escaped, strings.ReplaceAll(e, string(sep), `\`+string(sep))) } return strings.Join(escaped, string(sep)) } // NamedFileContentFlag is a flag value that loads a file's contents and filename into its value. type NamedFileContentFlag struct { Filename string Contents []byte } func (f *NamedFileContentFlag) Decode(ctx *DecodeContext) error { // nolint: revive var filename string err := ctx.Scan.PopValueInto("filename", &filename) if err != nil { return err } // This allows unsetting of file content flags. if filename == "" { *f = NamedFileContentFlag{} return nil } filename = ExpandPath(filename) data, err := ioutil.ReadFile(filename) // nolint: gosec if err != nil { return fmt.Errorf("failed to open %q: %v", filename, err) } f.Contents = data f.Filename = filename return nil } // FileContentFlag is a flag value that loads a file's contents into its value. type FileContentFlag []byte func (f *FileContentFlag) Decode(ctx *DecodeContext) error { // nolint: revive var filename string err := ctx.Scan.PopValueInto("filename", &filename) if err != nil { return err } // This allows unsetting of file content flags. if filename == "" { *f = nil return nil } filename = ExpandPath(filename) data, err := ioutil.ReadFile(filename) // nolint: gosec if err != nil { return fmt.Errorf("failed to open %q: %v", filename, err) } *f = data return nil } func jsonTranscode(in, out interface{}) error { data, err := json.Marshal(in) if err != nil { return err } if err = json.Unmarshal(data, out); err != nil { return fmt.Errorf("%#v -> %T: %v", in, out, err) } return nil } kong-0.8.1/mapper_linux_test.go000066400000000000000000000007001451020262500165410ustar00rootroot00000000000000//go:build !windows // +build !windows package kong_test import ( "testing" "github.com/alecthomas/assert/v2" ) func TestPathMapper(t *testing.T) { var cli struct { Path string `arg:"" type:"path"` } p := mustNew(t, &cli) _, err := p.Parse([]string{"/an/absolute/path"}) assert.NoError(t, err) assert.Equal(t, "/an/absolute/path", cli.Path) _, err = p.Parse([]string{"-"}) assert.NoError(t, err) assert.Equal(t, "-", cli.Path) } kong-0.8.1/mapper_test.go000066400000000000000000000442411451020262500153320ustar00rootroot00000000000000package kong_test import ( "bytes" "encoding/json" "fmt" "io/ioutil" "math" "net/url" "os" "reflect" "runtime" "strings" "testing" "time" "github.com/alecthomas/assert/v2" "github.com/alecthomas/kong" ) func TestValueMapper(t *testing.T) { var cli struct { Flag string } k := mustNew(t, &cli, kong.ValueMapper(&cli.Flag, testMooMapper{})) _, err := k.Parse(nil) assert.NoError(t, err) assert.Equal(t, "", cli.Flag) _, err = k.Parse([]string{"--flag"}) assert.NoError(t, err) assert.Equal(t, "MOO", cli.Flag) } type textUnmarshalerValue int func (m *textUnmarshalerValue) UnmarshalText(text []byte) error { s := string(text) if s == "hello" { *m = 10 } else { return fmt.Errorf("expected \"hello\"") } return nil } func TestTextUnmarshaler(t *testing.T) { var cli struct { Value textUnmarshalerValue } p := mustNew(t, &cli) _, err := p.Parse([]string{"--value=hello"}) assert.NoError(t, err) assert.Equal(t, 10, int(cli.Value)) _, err = p.Parse([]string{"--value=other"}) assert.Error(t, err) } type jsonUnmarshalerValue string func (j *jsonUnmarshalerValue) UnmarshalJSON(text []byte) error { var v string err := json.Unmarshal(text, &v) if err != nil { return err } *j = jsonUnmarshalerValue(strings.ToUpper(v)) return nil } func TestJSONUnmarshaler(t *testing.T) { var cli struct { Value jsonUnmarshalerValue } p := mustNew(t, &cli) _, err := p.Parse([]string{"--value=\"hello\""}) assert.NoError(t, err) assert.Equal(t, "HELLO", string(cli.Value)) } func TestNamedMapper(t *testing.T) { var cli struct { Flag string `type:"moo"` } k := mustNew(t, &cli, kong.NamedMapper("moo", testMooMapper{})) _, err := k.Parse(nil) assert.NoError(t, err) assert.Equal(t, "", cli.Flag) _, err = k.Parse([]string{"--flag"}) assert.NoError(t, err) assert.Equal(t, "MOO", cli.Flag) } type testMooMapper struct { text string } func (t testMooMapper) Decode(ctx *kong.DecodeContext, target reflect.Value) error { if t.text == "" { target.SetString("MOO") } else { target.SetString(t.text) } return nil } func (testMooMapper) IsBool() bool { return true } func TestTimeMapper(t *testing.T) { var cli struct { Flag time.Time `format:"2006"` } k := mustNew(t, &cli) _, err := k.Parse([]string{"--flag=2008"}) assert.NoError(t, err) expected, err := time.Parse("2006", "2008") assert.NoError(t, err) assert.Equal(t, 2008, expected.Year()) assert.Equal(t, expected, cli.Flag) } func TestDurationMapper(t *testing.T) { var cli struct { Flag time.Duration } k := mustNew(t, &cli) _, err := k.Parse([]string{"--flag=5s"}) assert.NoError(t, err) assert.Equal(t, time.Second*5, cli.Flag) } func TestDurationMapperJSONResolver(t *testing.T) { var cli struct { Flag time.Duration } resolver, err := kong.JSON(strings.NewReader(`{"flag": 5000000000}`)) assert.NoError(t, err) k := mustNew(t, &cli, kong.Resolvers(resolver)) _, err = k.Parse(nil) assert.NoError(t, err) assert.Equal(t, time.Second*5, cli.Flag) } func TestSplitEscaped(t *testing.T) { assert.Equal(t, []string{"a", "b"}, kong.SplitEscaped("a,b", ',')) assert.Equal(t, []string{"a,b", "c"}, kong.SplitEscaped(`a\,b,c`, ',')) assert.Equal(t, []string{"a,b,c"}, kong.SplitEscaped(`a,b,c`, -1)) } func TestJoinEscaped(t *testing.T) { assert.Equal(t, `a,b`, kong.JoinEscaped([]string{"a", "b"}, ',')) assert.Equal(t, `a\,b,c`, kong.JoinEscaped([]string{`a,b`, `c`}, ',')) assert.Equal(t, kong.JoinEscaped(kong.SplitEscaped(`a\,b,c`, ','), ','), `a\,b,c`) } func TestMapWithNamedTypes(t *testing.T) { var cli struct { TypedValue map[string]string `type:":moo"` TypedKey map[string]string `type:"upper:"` } k := mustNew(t, &cli, kong.NamedMapper("moo", testMooMapper{}), kong.NamedMapper("upper", testUppercaseMapper{})) _, err := k.Parse([]string{"--typed-value", "first=5s", "--typed-value", "second=10s"}) assert.NoError(t, err) assert.Equal(t, map[string]string{"first": "MOO", "second": "MOO"}, cli.TypedValue) _, err = k.Parse([]string{"--typed-key", "first=5s", "--typed-key", "second=10s"}) assert.NoError(t, err) assert.Equal(t, map[string]string{"FIRST": "5s", "SECOND": "10s"}, cli.TypedKey) } func TestMapWithMultipleValues(t *testing.T) { var cli struct { Value map[string]string } k := mustNew(t, &cli) _, err := k.Parse([]string{"--value=a=b;c=d"}) assert.NoError(t, err) assert.Equal(t, map[string]string{"a": "b", "c": "d"}, cli.Value) } func TestMapWithDifferentSeparator(t *testing.T) { var cli struct { Value map[string]string `mapsep:","` } k := mustNew(t, &cli) _, err := k.Parse([]string{"--value=a=b,c=d"}) assert.NoError(t, err) assert.Equal(t, map[string]string{"a": "b", "c": "d"}, cli.Value) } func TestMapWithNoSeparator(t *testing.T) { var cli struct { Slice []string `sep:"none"` Value map[string]string `mapsep:"none"` } k := mustNew(t, &cli) _, err := k.Parse([]string{"--slice=a,n,c", "--value=a=b;n=d"}) assert.NoError(t, err) assert.Equal(t, []string{"a,n,c"}, cli.Slice) assert.Equal(t, map[string]string{"a": "b;n=d"}, cli.Value) } func TestURLMapper(t *testing.T) { var cli struct { URL *url.URL `arg:""` } p := mustNew(t, &cli) _, err := p.Parse([]string{"http://w3.org"}) assert.NoError(t, err) assert.Equal(t, "http://w3.org", cli.URL.String()) _, err = p.Parse([]string{":foo"}) assert.Error(t, err) } func TestSliceConsumesRemainingPositionalArgs(t *testing.T) { var cli struct { Remainder []string `arg:""` } p := mustNew(t, &cli) _, err := p.Parse([]string{"--", "ls", "-lart"}) assert.NoError(t, err) assert.Equal(t, []string{"ls", "-lart"}, cli.Remainder) } func TestPassthroughStopsParsing(t *testing.T) { type cli struct { Interactive bool `short:"i"` Image string `arg:""` Argv []string `arg:"" optional:"" passthrough:""` } var actual cli p := mustNew(t, &actual) _, err := p.Parse([]string{"alpine", "sudo", "-i", "true"}) assert.NoError(t, err) assert.Equal(t, cli{ Interactive: false, Image: "alpine", Argv: []string{"sudo", "-i", "true"}, }, actual) _, err = p.Parse([]string{"alpine", "-i", "sudo", "-i", "true"}) assert.NoError(t, err) assert.Equal(t, cli{ Interactive: true, Image: "alpine", Argv: []string{"sudo", "-i", "true"}, }, actual) } type mappedValue struct { decoded string } func (m *mappedValue) Decode(ctx *kong.DecodeContext) error { err := ctx.Scan.PopValueInto("mapped", &m.decoded) return err } func TestMapperValue(t *testing.T) { var cli struct { Value mappedValue `arg:""` } p := mustNew(t, &cli) _, err := p.Parse([]string{"foo"}) assert.NoError(t, err) assert.Equal(t, "foo", cli.Value.decoded) } func TestFileContentFlag(t *testing.T) { var cli struct { File kong.FileContentFlag } f, err := ioutil.TempFile("", "") assert.NoError(t, err) defer os.Remove(f.Name()) fmt.Fprint(f, "hello world") f.Close() _, err = mustNew(t, &cli).Parse([]string{"--file", f.Name()}) assert.NoError(t, err) assert.Equal(t, []byte("hello world"), []byte(cli.File)) } func TestNamedFileContentFlag(t *testing.T) { var cli struct { File kong.NamedFileContentFlag } f, err := ioutil.TempFile("", "") assert.NoError(t, err) defer os.Remove(f.Name()) fmt.Fprint(f, "hello world") f.Close() _, err = mustNew(t, &cli).Parse([]string{"--file", f.Name()}) assert.NoError(t, err) assert.Equal(t, []byte("hello world"), cli.File.Contents) assert.Equal(t, f.Name(), cli.File.Filename) } func TestNamedSliceTypesDontHaveEllipsis(t *testing.T) { var cli struct { File kong.FileContentFlag } b := bytes.NewBuffer(nil) parser := mustNew(t, &cli, kong.Writers(b, b), kong.Exit(func(int) { panic("exit") })) // Ensure that --help assert.Panics(t, func() { _, err := parser.Parse([]string{"--help"}) assert.NoError(t, err) }) assert.NotContains(t, b.String(), `--file=FILE-CONTENT-FLAG,...`) } func TestCounter(t *testing.T) { var cli struct { Int int `type:"counter" short:"i"` Uint uint `type:"counter" short:"u"` Float float64 `type:"counter" short:"f"` } p := mustNew(t, &cli) _, err := p.Parse([]string{"--int", "--int", "--int"}) assert.NoError(t, err) assert.Equal(t, 3, cli.Int) _, err = p.Parse([]string{"--int=5"}) assert.NoError(t, err) assert.Equal(t, 5, cli.Int) _, err = p.Parse([]string{"-iii"}) assert.NoError(t, err) assert.Equal(t, 3, cli.Int) _, err = p.Parse([]string{"-uuu"}) assert.NoError(t, err) assert.Equal(t, uint(3), cli.Uint) _, err = p.Parse([]string{"-fff"}) assert.NoError(t, err) assert.Equal(t, 3., cli.Float) } func TestNumbers(t *testing.T) { type CLI struct { F32 float32 F64 float64 I8 int8 I16 int16 I32 int32 I64 int64 U8 uint8 U16 uint16 U32 uint32 U64 uint64 } var cli CLI p := mustNew(t, &cli) t.Run("Max", func(t *testing.T) { _, err := p.Parse([]string{ "--f-32", fmt.Sprintf("%v", math.MaxFloat32), "--f-64", fmt.Sprintf("%v", math.MaxFloat64), "--i-8", fmt.Sprintf("%v", int8(math.MaxInt8)), "--i-16", fmt.Sprintf("%v", int16(math.MaxInt16)), "--i-32", fmt.Sprintf("%v", int32(math.MaxInt32)), "--i-64", fmt.Sprintf("%v", int64(math.MaxInt64)), "--u-8", fmt.Sprintf("%v", uint8(math.MaxUint8)), "--u-16", fmt.Sprintf("%v", uint16(math.MaxUint16)), "--u-32", fmt.Sprintf("%v", uint32(math.MaxUint32)), "--u-64", fmt.Sprintf("%v", uint64(math.MaxUint64)), }) assert.NoError(t, err) assert.Equal(t, CLI{ F32: math.MaxFloat32, F64: math.MaxFloat64, I8: math.MaxInt8, I16: math.MaxInt16, I32: math.MaxInt32, I64: math.MaxInt64, U8: math.MaxUint8, U16: math.MaxUint16, U32: math.MaxUint32, U64: math.MaxUint64, }, cli) }) t.Run("Min", func(t *testing.T) { _, err := p.Parse([]string{ fmt.Sprintf("--i-8=%v", int8(math.MinInt8)), fmt.Sprintf("--i-16=%v", int16(math.MinInt16)), fmt.Sprintf("--i-32=%v", int32(math.MinInt32)), fmt.Sprintf("--i-64=%v", int64(math.MinInt64)), fmt.Sprintf("--u-8=%v", 0), fmt.Sprintf("--u-16=%v", 0), fmt.Sprintf("--u-32=%v", 0), fmt.Sprintf("--u-64=%v", 0), }) assert.NoError(t, err) assert.Equal(t, CLI{ I8: math.MinInt8, I16: math.MinInt16, I32: math.MinInt32, I64: math.MinInt64, }, cli) }) } func TestJSONLargeNumber(t *testing.T) { // Make sure that large numbers are not internally converted to // scientific notation when the mapper parses the values. // (Scientific notation is e.g. `1e+06` instead of `1000000`.) // Large signed integers: { var cli struct { N int64 } json := `{"n": 1000000}` r, err := kong.JSON(strings.NewReader(json)) assert.NoError(t, err) parser := mustNew(t, &cli, kong.Resolvers(r)) _, err = parser.Parse([]string{}) assert.NoError(t, err) assert.Equal(t, int64(1000000), cli.N) } // Large unsigned integers: { var cli struct { N uint64 } json := `{"n": 1000000}` r, err := kong.JSON(strings.NewReader(json)) assert.NoError(t, err) parser := mustNew(t, &cli, kong.Resolvers(r)) _, err = parser.Parse([]string{}) assert.NoError(t, err) assert.Equal(t, uint64(1000000), cli.N) } // Large floats: { var cli struct { N float64 } json := `{"n": 1000000.1}` r, err := kong.JSON(strings.NewReader(json)) assert.NoError(t, err) parser := mustNew(t, &cli, kong.Resolvers(r)) _, err = parser.Parse([]string{}) assert.NoError(t, err) assert.Equal(t, float64(1000000.1), cli.N) } } func TestFileMapper(t *testing.T) { type CLI struct { File *os.File `arg:""` } var cli CLI p := mustNew(t, &cli) _, err := p.Parse([]string{"testdata/file.txt"}) assert.NoError(t, err) assert.NotZero(t, cli.File) _ = cli.File.Close() _, err = p.Parse([]string{"testdata/missing.txt"}) assert.Error(t, err) if runtime.GOOS == "windows" { assert.Contains(t, err.Error(), "missing.txt: The system cannot find the file specified.") } else { assert.Contains(t, err.Error(), "missing.txt: no such file or directory") } _, err = p.Parse([]string{"-"}) assert.NoError(t, err) assert.Equal(t, os.Stdin, cli.File) } func TestFileContentMapper(t *testing.T) { type CLI struct { File []byte `type:"filecontent"` } var cli CLI p := mustNew(t, &cli) _, err := p.Parse([]string{"--file", "testdata/file.txt"}) assert.NoError(t, err) assert.Equal(t, []byte(`Hello world.`), cli.File) p = mustNew(t, &cli) _, err = p.Parse([]string{"--file", "testdata/missing.txt"}) assert.Error(t, err) assert.Contains(t, err.Error(), "missing.txt: no such file or directory") p = mustNew(t, &cli) _, err = p.Parse([]string{"--file", "testdata/"}) assert.Error(t, err) assert.Contains(t, err.Error(), "is a directory") } //nolint:dupl func TestExistingFileMapper(t *testing.T) { type CLI struct { File string `type:"existingfile"` } var cli CLI p := mustNew(t, &cli) _, err := p.Parse([]string{"--file", "testdata/file.txt"}) assert.NoError(t, err) assert.NotZero(t, cli.File) p = mustNew(t, &cli) _, err = p.Parse([]string{"--file", "testdata/missing.txt"}) assert.Error(t, err) assert.Contains(t, err.Error(), "missing.txt: no such file or directory") p = mustNew(t, &cli) _, err = p.Parse([]string{"--file", "testdata/"}) assert.Error(t, err) assert.Contains(t, err.Error(), "exists but is a directory") } func TestExistingFileMapperDefaultMissing(t *testing.T) { type CLI struct { File string `type:"existingfile" default:"testdata/missing.txt"` } var cli CLI p := mustNew(t, &cli) file := "testdata/file.txt" _, err := p.Parse([]string{"--file", file}) assert.NoError(t, err) assert.NotZero(t, cli.File) assert.Contains(t, cli.File, file) p = mustNew(t, &cli) _, err = p.Parse([]string{}) assert.Error(t, err) assert.Contains(t, err.Error(), "missing.txt: no such file or directory") } func TestExistingFileMapperDefaultMissingCmds(t *testing.T) { type CLI struct { CmdA struct { FileA string `type:"existingfile" default:"testdata/aaa-missing.txt"` FileB string `type:"existingfile" default:"testdata/bbb-missing.txt"` } `cmd:""` CmdC struct { FileC string `type:"existingfile" default:"testdata/ccc-missing.txt"` } `cmd:""` } var cli CLI file := "testdata/file.txt" p := mustNew(t, &cli) _, err := p.Parse([]string{"cmd-a", "--file-a", file, "--file-b", file}) assert.NoError(t, err) assert.NotZero(t, cli.CmdA.FileA) assert.Contains(t, cli.CmdA.FileA, file) assert.NotZero(t, cli.CmdA.FileB) assert.Contains(t, cli.CmdA.FileB, file) p = mustNew(t, &cli) _, err = p.Parse([]string{"cmd-a", "--file-a", file}) assert.Error(t, err) assert.Contains(t, err.Error(), "bbb-missing.txt: no such file or directory") } //nolint:dupl func TestExistingDirMapper(t *testing.T) { type CLI struct { Dir string `type:"existingdir"` } var cli CLI p := mustNew(t, &cli) _, err := p.Parse([]string{"--dir", "testdata/"}) assert.NoError(t, err) assert.NotZero(t, cli.Dir) p = mustNew(t, &cli) _, err = p.Parse([]string{"--dir", "missingdata/"}) assert.Error(t, err) assert.Contains(t, err.Error(), "missingdata: no such file or directory") p = mustNew(t, &cli) _, err = p.Parse([]string{"--dir", "testdata/file.txt"}) assert.Error(t, err) assert.Contains(t, err.Error(), "exists but is not a directory") } func TestExistingDirMapperDefaultMissing(t *testing.T) { type CLI struct { Dir string `type:"existingdir" default:"missing-dir"` } var cli CLI p := mustNew(t, &cli) dir := "testdata" _, err := p.Parse([]string{"--dir", dir}) assert.NoError(t, err) assert.NotZero(t, cli.Dir) assert.Contains(t, cli.Dir, dir) p = mustNew(t, &cli) _, err = p.Parse([]string{}) assert.Error(t, err) assert.Contains(t, err.Error(), "missing-dir: no such file or directory") } func TestExistingDirMapperDefaultMissingCmds(t *testing.T) { type CLI struct { CmdA struct { DirA string `type:"existingdir" default:"aaa-missing-dir"` DirB string `type:"existingdir" default:"bbb-missing-dir"` } `cmd:""` CmdC struct { DirC string `type:"existingdir" default:"ccc-missing-dir"` } `cmd:""` } var cli CLI dir := "testdata" p := mustNew(t, &cli) _, err := p.Parse([]string{"cmd-a", "--dir-a", dir, "--dir-b", dir}) assert.NoError(t, err) assert.NotZero(t, cli.CmdA.DirA) assert.NotZero(t, cli.CmdA.DirB) assert.Contains(t, cli.CmdA.DirA, dir) assert.Contains(t, cli.CmdA.DirB, dir) p = mustNew(t, &cli) _, err = p.Parse([]string{"cmd-a", "--dir-a", dir}) assert.Error(t, err) assert.Contains(t, err.Error(), "bbb-missing-dir: no such file or directory") } func TestMapperPlaceHolder(t *testing.T) { var cli struct { Flag string } b := bytes.NewBuffer(nil) k := mustNew( t, &cli, kong.Writers(b, b), kong.ValueMapper(&cli.Flag, testMapperWithPlaceHolder{}), kong.Exit(func(int) { panic("exit") }), ) // Ensure that --help assert.Panics(t, func() { _, err := k.Parse([]string{"--help"}) assert.NoError(t, err) }) assert.Contains(t, b.String(), "--flag=/a/b/c") } type testMapperWithPlaceHolder struct { } func (t testMapperWithPlaceHolder) Decode(ctx *kong.DecodeContext, target reflect.Value) error { target.SetString("hi") return nil } func (t testMapperWithPlaceHolder) PlaceHolder(flag *kong.Flag) string { return "/a/b/c" } func TestMapperVarsContributor(t *testing.T) { var cli struct { Flag string `help:"Some help with ${avar}"` } b := bytes.NewBuffer(nil) k := mustNew( t, &cli, kong.Writers(b, b), kong.ValueMapper(&cli.Flag, testMapperVarsContributor{}), kong.Exit(func(int) { panic("exit") }), ) // Ensure that --help assert.Panics(t, func() { _, err := k.Parse([]string{"--help"}) assert.NoError(t, err) }) assert.Contains(t, b.String(), "--flag=STRING") assert.Contains(t, b.String(), "Some help with a var", b.String()) } type testMapperVarsContributor struct{} func (t testMapperVarsContributor) Vars(value *kong.Value) kong.Vars { return kong.Vars{"avar": "a var"} } func (t testMapperVarsContributor) Decode(ctx *kong.DecodeContext, target reflect.Value) error { target.SetString("hi") return nil } func TestValuesThatLookLikeFlags(t *testing.T) { var cli struct { Slice []string Map map[string]string } k := mustNew(t, &cli) _, err := k.Parse([]string{"--slice", "-foo"}) assert.Error(t, err) _, err = k.Parse([]string{"--map", "-foo=-bar"}) assert.Error(t, err) _, err = k.Parse([]string{"--slice=-foo", "--slice=-bar"}) assert.NoError(t, err) assert.Equal(t, []string{"-foo", "-bar"}, cli.Slice) _, err = k.Parse([]string{"--map=-foo=-bar"}) assert.NoError(t, err) assert.Equal(t, map[string]string{"-foo": "-bar"}, cli.Map) } kong-0.8.1/mapper_windows_test.go000066400000000000000000000022071451020262500171000ustar00rootroot00000000000000//go:build windows // +build windows package kong_test import ( "os" "testing" "github.com/alecthomas/assert/v2" ) func TestWindowsPathMapper(t *testing.T) { var cli struct { Path string `short:"p" type:"path"` Files []string `short:"f" type:"path"` } wd, err := os.Getwd() assert.NoError(t, err, "Getwd failed") p := mustNew(t, &cli) _, err = p.Parse([]string{`-p`, `c:\an\absolute\path`, `-f`, `c:\second\absolute\path\`, `-f`, `relative\path\file`}) assert.NoError(t, err) assert.Equal(t, `c:\an\absolute\path`, cli.Path) assert.Equal(t, []string{`c:\second\absolute\path\`, wd + `\relative\path\file`}, cli.Files) } func TestWindowsFileMapper(t *testing.T) { type CLI struct { File *os.File `arg:""` } var cli CLI p := mustNew(t, &cli) _, err := p.Parse([]string{"testdata\\file.txt"}) assert.NoError(t, err) assert.NotNil(t, cli.File) _ = cli.File.Close() _, err = p.Parse([]string{"testdata\\missing.txt"}) assert.Error(t, err) assert.Contains(t, err.Error(), "missing.txt: The system cannot find the file specified.") _, err = p.Parse([]string{"-"}) assert.NoError(t, err) assert.Equal(t, os.Stdin, cli.File) } kong-0.8.1/model.go000066400000000000000000000304371451020262500141110ustar00rootroot00000000000000package kong import ( "fmt" "math" "os" "reflect" "strconv" "strings" ) // A Visitable component in the model. type Visitable interface { node() } // Application is the root of the Kong model. type Application struct { *Node // Help flag, if the NoDefaultHelp() option is not specified. HelpFlag *Flag } // Argument represents a branching positional argument. type Argument = Node // Command represents a command in the CLI. type Command = Node // NodeType is an enum representing the type of a Node. type NodeType int // Node type enumerations. const ( ApplicationNode NodeType = iota CommandNode ArgumentNode ) // Node is a branch in the CLI. ie. a command or positional argument. type Node struct { Type NodeType Parent *Node Name string Help string // Short help displayed in summaries. Detail string // Detailed help displayed when describing command/arg alone. Group *Group Hidden bool Flags []*Flag Positional []*Positional Children []*Node DefaultCmd *Node Target reflect.Value // Pointer to the value in the grammar that this Node is associated with. Tag *Tag Aliases []string Passthrough bool // Set to true to stop flag parsing when encountered. Active bool // Denotes the node is part of an active branch in the CLI. Argument *Value // Populated when Type is ArgumentNode. } func (*Node) node() {} // Leaf returns true if this Node is a leaf node. func (n *Node) Leaf() bool { return len(n.Children) == 0 } // Find a command/argument/flag by pointer to its field. // // Returns nil if not found. Panics if ptr is not a pointer. func (n *Node) Find(ptr interface{}) *Node { key := reflect.ValueOf(ptr) if key.Kind() != reflect.Ptr { panic("expected a pointer") } return n.findNode(key) } func (n *Node) findNode(key reflect.Value) *Node { if n.Target == key { return n } for _, child := range n.Children { if found := child.findNode(key); found != nil { return found } } return nil } // AllFlags returns flags from all ancestor branches encountered. // // If "hide" is true hidden flags will be omitted. func (n *Node) AllFlags(hide bool) (out [][]*Flag) { if n.Parent != nil { out = append(out, n.Parent.AllFlags(hide)...) } group := []*Flag{} for _, flag := range n.Flags { if !hide || !flag.Hidden { flag.Active = true group = append(group, flag) } } if len(group) > 0 { out = append(out, group) } return } // Leaves returns the leaf commands/arguments under Node. // // If "hidden" is true hidden leaves will be omitted. func (n *Node) Leaves(hide bool) (out []*Node) { _ = Visit(n, func(nd Visitable, next Next) error { if nd == n { return next(nil) } if node, ok := nd.(*Node); ok { if hide && node.Hidden { return nil } if len(node.Children) == 0 && node.Type != ApplicationNode { out = append(out, node) } } return next(nil) }) return } // Depth of the command from the application root. func (n *Node) Depth() int { depth := 0 p := n.Parent for p != nil && p.Type != ApplicationNode { depth++ p = p.Parent } return depth } // Summary help string for the node (not including application name). func (n *Node) Summary() string { summary := n.Path() if flags := n.FlagSummary(true); flags != "" { summary += " " + flags } args := []string{} optional := 0 for _, arg := range n.Positional { argSummary := arg.Summary() if arg.Tag.Optional { optional++ argSummary = strings.TrimRight(argSummary, "]") } args = append(args, argSummary) } if len(args) != 0 { summary += " " + strings.Join(args, " ") + strings.Repeat("]", optional) } else if len(n.Children) > 0 { summary += " " } return summary } // FlagSummary for the node. func (n *Node) FlagSummary(hide bool) string { required := []string{} count := 0 for _, group := range n.AllFlags(hide) { for _, flag := range group { count++ if flag.Required { required = append(required, flag.Summary()) } } } return strings.Join(required, " ") } // FullPath is like Path() but includes the Application root node. func (n *Node) FullPath() string { root := n for root.Parent != nil { root = root.Parent } return strings.TrimSpace(root.Name + " " + n.Path()) } // Vars returns the combined Vars defined by all ancestors of this Node. func (n *Node) Vars() Vars { if n == nil { return Vars{} } return n.Parent.Vars().CloneWith(n.Tag.Vars) } // Path through ancestors to this Node. func (n *Node) Path() (out string) { if n.Parent != nil { out += " " + n.Parent.Path() } switch n.Type { case CommandNode: out += " " + n.Name if len(n.Aliases) > 0 { out += fmt.Sprintf(" (%s)", strings.Join(n.Aliases, ",")) } case ArgumentNode: out += " " + "<" + n.Name + ">" default: } return strings.TrimSpace(out) } // ClosestGroup finds the first non-nil group in this node and its ancestors. func (n *Node) ClosestGroup() *Group { switch { case n.Group != nil: return n.Group case n.Parent != nil: return n.Parent.ClosestGroup() default: return nil } } // A Value is either a flag or a variable positional argument. type Value struct { Flag *Flag // Nil if positional argument. Name string Help string OrigHelp string // Original help string, without interpolated variables. HasDefault bool Default string DefaultValue reflect.Value Enum string Mapper Mapper Tag *Tag Target reflect.Value Required bool Set bool // Set to true when this value is set through some mechanism. Format string // Formatting directive, if applicable. Position int // Position (for positional arguments). Passthrough bool // Set to true to stop flag parsing when encountered. Active bool // Denotes the value is part of an active branch in the CLI. } // EnumMap returns a map of the enums in this value. func (v *Value) EnumMap() map[string]bool { parts := strings.Split(v.Enum, ",") out := make(map[string]bool, len(parts)) for _, part := range parts { out[strings.TrimSpace(part)] = true } return out } // EnumSlice returns a slice of the enums in this value. func (v *Value) EnumSlice() []string { parts := strings.Split(v.Enum, ",") out := make([]string, len(parts)) for i, part := range parts { out[i] = strings.TrimSpace(part) } return out } // ShortSummary returns a human-readable summary of the value, not including any placeholders/defaults. func (v *Value) ShortSummary() string { if v.Flag != nil { return fmt.Sprintf("--%s", v.Name) } argText := "<" + v.Name + ">" if v.IsCumulative() { argText += " ..." } if !v.Required { argText = "[" + argText + "]" } return argText } // Summary returns a human-readable summary of the value. func (v *Value) Summary() string { if v.Flag != nil { if v.IsBool() { return fmt.Sprintf("--%s", v.Name) } return fmt.Sprintf("--%s=%s", v.Name, v.Flag.FormatPlaceHolder()) } argText := "<" + v.Name + ">" if v.IsCumulative() { argText += " ..." } if !v.Required { argText = "[" + argText + "]" } return argText } // IsCumulative returns true if the type can be accumulated into. func (v *Value) IsCumulative() bool { return v.IsSlice() || v.IsMap() } // IsSlice returns true if the value is a slice. func (v *Value) IsSlice() bool { return v.Target.Type().Name() == "" && v.Target.Kind() == reflect.Slice } // IsMap returns true if the value is a map. func (v *Value) IsMap() bool { return v.Target.Kind() == reflect.Map } // IsBool returns true if the underlying value is a boolean. func (v *Value) IsBool() bool { if m, ok := v.Mapper.(BoolMapperExt); ok && m.IsBoolFromValue(v.Target) { return true } if m, ok := v.Mapper.(BoolMapper); ok && m.IsBool() { return true } return v.Target.Kind() == reflect.Bool } // IsCounter returns true if the value is a counter. func (v *Value) IsCounter() bool { return v.Tag.Type == "counter" } // Parse tokens into value, parse, and validate, but do not write to the field. func (v *Value) Parse(scan *Scanner, target reflect.Value) (err error) { if target.Kind() == reflect.Ptr && target.IsNil() { target.Set(reflect.New(target.Type().Elem())) } err = v.Mapper.Decode(&DecodeContext{Value: v, Scan: scan}, target) if err != nil { return fmt.Errorf("%s: %w", v.ShortSummary(), err) } v.Set = true return nil } // Apply value to field. func (v *Value) Apply(value reflect.Value) { v.Target.Set(value) v.Set = true } // ApplyDefault value to field if it is not already set. func (v *Value) ApplyDefault() error { if reflectValueIsZero(v.Target) { return v.Reset() } v.Set = true return nil } // Reset this value to its default, either the zero value or the parsed result of its envar, // or its "default" tag. // // Does not include resolvers. func (v *Value) Reset() error { v.Target.Set(reflect.Zero(v.Target.Type())) if len(v.Tag.Envs) != 0 { for _, env := range v.Tag.Envs { envar := os.Getenv(env) // Parse the first non-empty ENV in the list if envar != "" { err := v.Parse(ScanFromTokens(Token{Type: FlagValueToken, Value: envar}), v.Target) if err != nil { return fmt.Errorf("%s (from envar %s=%q)", err, env, envar) } return nil } } } if v.HasDefault { return v.Parse(ScanFromTokens(Token{Type: FlagValueToken, Value: v.Default}), v.Target) } return nil } func (*Value) node() {} // A Positional represents a non-branching command-line positional argument. type Positional = Value // A Flag represents a command-line flag. type Flag struct { *Value Group *Group // Logical grouping when displaying. May also be used by configuration loaders to group options logically. Xor []string PlaceHolder string Envs []string Short rune Hidden bool Negated bool } func (f *Flag) String() string { out := "--" + f.Name if f.Short != 0 { out = fmt.Sprintf("-%c, %s", f.Short, out) } if !f.IsBool() && !f.IsCounter() { out += "=" + f.FormatPlaceHolder() } return out } // FormatPlaceHolder formats the placeholder string for a Flag. func (f *Flag) FormatPlaceHolder() string { placeholderHelper, ok := f.Value.Mapper.(PlaceHolderProvider) if ok { return placeholderHelper.PlaceHolder(f) } tail := "" if f.Value.IsSlice() && f.Value.Tag.Sep != -1 { tail += string(f.Value.Tag.Sep) + "..." } if f.PlaceHolder != "" { return f.PlaceHolder + tail } if f.HasDefault { if f.Value.Target.Kind() == reflect.String { return strconv.Quote(f.Default) + tail } return f.Default + tail } if f.Value.IsMap() { if f.Value.Tag.MapSep != -1 { tail = string(f.Value.Tag.MapSep) + "..." } return "KEY=VALUE" + tail } if f.Tag != nil && f.Tag.TypeName != "" { return strings.ToUpper(dashedString(f.Tag.TypeName)) + tail } return strings.ToUpper(f.Name) + tail } // Group holds metadata about a command or flag group used when printing help. type Group struct { // Key is the `group` field tag value used to identify this group. Key string // Title is displayed above the grouped items. Title string // Description is optional and displayed under the Title when non empty. // It can be used to introduce the group's purpose to the user. Description string } // This is directly from the Go 1.13 source code. func reflectValueIsZero(v reflect.Value) bool { switch v.Kind() { case reflect.Bool: return !v.Bool() case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return v.Int() == 0 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return v.Uint() == 0 case reflect.Float32, reflect.Float64: return math.Float64bits(v.Float()) == 0 case reflect.Complex64, reflect.Complex128: c := v.Complex() return math.Float64bits(real(c)) == 0 && math.Float64bits(imag(c)) == 0 case reflect.Array: for i := 0; i < v.Len(); i++ { if !reflectValueIsZero(v.Index(i)) { return false } } return true case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer: return v.IsNil() case reflect.String: return v.Len() == 0 case reflect.Struct: for i := 0; i < v.NumField(); i++ { if !reflectValueIsZero(v.Field(i)) { return false } } return true default: // This should never happens, but will act as a safeguard for // later, as a default value doesn't makes sense here. panic(&reflect.ValueError{"reflect.Value.IsZero", v.Kind()}) } } kong-0.8.1/model_test.go000066400000000000000000000047711451020262500151520ustar00rootroot00000000000000package kong_test import ( "testing" "github.com/alecthomas/assert/v2" ) func TestModelApplicationCommands(t *testing.T) { var cli struct { One struct { Two struct { } `kong:"cmd"` Three struct { Four struct { Four string `kong:"arg"` } `kong:"arg"` } `kong:"cmd"` } `kong:"cmd"` } p := mustNew(t, &cli) actual := []string{} for _, cmd := range p.Model.Leaves(false) { actual = append(actual, cmd.Path()) } assert.Equal(t, []string{"one two", "one three "}, actual) } func TestFlagString(t *testing.T) { var cli struct { String string DefaultInt int `default:"42"` DefaultStr string `default:"hello"` Placeholder string `placeholder:"world"` DefaultPlaceholder string `default:"hello" placeholder:"world"` SliceSep []string SliceNoSep []string `sep:"none"` SliceDefault []string `default:"hello"` SlicePlaceholder []string `placeholder:"world"` SliceDefaultPlaceholder []string `default:"hello" placeholder:"world"` MapSep map[string]string MapNoSep map[string]string `mapsep:"none"` MapDefault map[string]string `mapsep:"none" default:"hello"` MapPlaceholder map[string]string `mapsep:"none" placeholder:"world"` Counter int `type:"counter"` } tests := map[string]string{ "help": "-h, --help", "string": "--string=STRING", "default-int": "--default-int=42", "default-str": `--default-str="hello"`, "placeholder": "--placeholder=world", "default-placeholder": "--default-placeholder=world", "slice-sep": "--slice-sep=SLICE-SEP,...", "slice-no-sep": "--slice-no-sep=SLICE-NO-SEP", "slice-default": "--slice-default=hello,...", "slice-placeholder": "--slice-placeholder=world,...", "slice-default-placeholder": "--slice-default-placeholder=world,...", "map-sep": "--map-sep=KEY=VALUE;...", "map-no-sep": "--map-no-sep=KEY=VALUE", "map-default": "--map-default=hello", "map-placeholder": "--map-placeholder=world", "counter": "--counter", } p := mustNew(t, &cli) for _, flag := range p.Model.Flags { want, ok := tests[flag.Name] assert.True(t, ok, "unknown flag name: %s", flag.Name) assert.Equal(t, want, flag.String()) } } kong-0.8.1/options.go000066400000000000000000000311361451020262500145010ustar00rootroot00000000000000package kong import ( "errors" "fmt" "io" "os" "os/user" "path/filepath" "reflect" "regexp" "strings" ) // An Option applies optional changes to the Kong application. type Option interface { Apply(k *Kong) error } // OptionFunc is function that adheres to the Option interface. type OptionFunc func(k *Kong) error func (o OptionFunc) Apply(k *Kong) error { return o(k) } // nolint: revive // Vars sets the variables to use for interpolation into help strings and default values. // // See README for details. type Vars map[string]string // Apply lets Vars act as an Option. func (v Vars) Apply(k *Kong) error { for key, value := range v { k.vars[key] = value } return nil } // CloneWith clones the current Vars and merges "vars" onto the clone. func (v Vars) CloneWith(vars Vars) Vars { out := make(Vars, len(v)+len(vars)) for key, value := range v { out[key] = value } for key, value := range vars { out[key] = value } return out } // Exit overrides the function used to terminate. This is useful for testing or interactive use. func Exit(exit func(int)) Option { return OptionFunc(func(k *Kong) error { k.Exit = exit return nil }) } type embedded struct { strct any tags []string } // Embed a struct into the root of the CLI. // // "strct" must be a pointer to a structure. func Embed(strct any, tags ...string) Option { t := reflect.TypeOf(strct) if t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct { panic("kong: Embed() must be called with a pointer to a struct") } return OptionFunc(func(k *Kong) error { k.embedded = append(k.embedded, embedded{strct, tags}) return nil }) } type dynamicCommand struct { name string help string group string tags []string cmd interface{} } // DynamicCommand registers a dynamically constructed command with the root of the CLI. // // This is useful for command-line structures that are extensible via user-provided plugins. // // "tags" is a list of extra tag strings to parse, in the form :"". func DynamicCommand(name, help, group string, cmd interface{}, tags ...string) Option { return OptionFunc(func(k *Kong) error { k.dynamicCommands = append(k.dynamicCommands, &dynamicCommand{ name: name, help: help, group: group, cmd: cmd, tags: tags, }) return nil }) } // NoDefaultHelp disables the default help flags. func NoDefaultHelp() Option { return OptionFunc(func(k *Kong) error { k.noDefaultHelp = true return nil }) } // PostBuild provides read/write access to kong.Kong after initial construction of the model is complete but before // parsing occurs. // // This is useful for, e.g., adding short options to flags, updating help, etc. func PostBuild(fn func(*Kong) error) Option { return OptionFunc(func(k *Kong) error { k.postBuildOptions = append(k.postBuildOptions, OptionFunc(fn)) return nil }) } // Name overrides the application name. func Name(name string) Option { return PostBuild(func(k *Kong) error { k.Model.Name = name return nil }) } // Description sets the application description. func Description(description string) Option { return PostBuild(func(k *Kong) error { k.Model.Help = description return nil }) } // TypeMapper registers a mapper to a type. func TypeMapper(typ reflect.Type, mapper Mapper) Option { return OptionFunc(func(k *Kong) error { k.registry.RegisterType(typ, mapper) return nil }) } // KindMapper registers a mapper to a kind. func KindMapper(kind reflect.Kind, mapper Mapper) Option { return OptionFunc(func(k *Kong) error { k.registry.RegisterKind(kind, mapper) return nil }) } // ValueMapper registers a mapper to a field value. func ValueMapper(ptr interface{}, mapper Mapper) Option { return OptionFunc(func(k *Kong) error { k.registry.RegisterValue(ptr, mapper) return nil }) } // NamedMapper registers a mapper to a name. func NamedMapper(name string, mapper Mapper) Option { return OptionFunc(func(k *Kong) error { k.registry.RegisterName(name, mapper) return nil }) } // Writers overrides the default writers. Useful for testing or interactive use. func Writers(stdout, stderr io.Writer) Option { return OptionFunc(func(k *Kong) error { k.Stdout = stdout k.Stderr = stderr return nil }) } // Bind binds values for hooks and Run() function arguments. // // Any arguments passed will be available to the receiving hook functions, but may be omitted. Additionally, *Kong and // the current *Context will also be made available. // // There are two hook points: // // BeforeApply(...) error // AfterApply(...) error // // Called before validation/assignment, and immediately after validation/assignment, respectively. func Bind(args ...interface{}) Option { return OptionFunc(func(k *Kong) error { k.bindings.add(args...) return nil }) } // BindTo allows binding of implementations to interfaces. // // BindTo(impl, (*iface)(nil)) func BindTo(impl, iface interface{}) Option { return OptionFunc(func(k *Kong) error { k.bindings.addTo(impl, iface) return nil }) } // BindToProvider allows binding of provider functions. // // This is useful when the Run() function of different commands require different values that may // not all be initialisable from the main() function. func BindToProvider(provider interface{}) Option { return OptionFunc(func(k *Kong) error { return k.bindings.addProvider(provider) }) } // Help printer to use. func Help(help HelpPrinter) Option { return OptionFunc(func(k *Kong) error { k.help = help return nil }) } // ShortHelp configures the short usage message. // // It should be used together with kong.ShortUsageOnError() to display a // custom short usage message on errors. func ShortHelp(shortHelp HelpPrinter) Option { return OptionFunc(func(k *Kong) error { k.shortHelp = shortHelp return nil }) } // HelpFormatter configures how the help text is formatted. // // Deprecated: Use ValueFormatter() instead. func HelpFormatter(helpFormatter HelpValueFormatter) Option { return OptionFunc(func(k *Kong) error { k.helpFormatter = helpFormatter return nil }) } // ValueFormatter configures how the help text is formatted. func ValueFormatter(helpFormatter HelpValueFormatter) Option { return OptionFunc(func(k *Kong) error { k.helpFormatter = helpFormatter return nil }) } // ConfigureHelp sets the HelpOptions to use for printing help. func ConfigureHelp(options HelpOptions) Option { return OptionFunc(func(k *Kong) error { k.helpOptions = options return nil }) } // AutoGroup automatically assigns groups to flags. func AutoGroup(format func(parent Visitable, flag *Flag) *Group) Option { return PostBuild(func(kong *Kong) error { parents := []Visitable{kong.Model} return Visit(kong.Model, func(node Visitable, next Next) error { if flag, ok := node.(*Flag); ok && flag.Group == nil { flag.Group = format(parents[len(parents)-1], flag) } parents = append(parents, node) defer func() { parents = parents[:len(parents)-1] }() return next(nil) }) }) } // Groups associates `group` field tags with group metadata. // // This option is used to simplify Kong tags while providing // rich group information such as title and optional description. // // Each key in the "groups" map corresponds to the value of a // `group` Kong tag, while the first line of the value will be // the title, and subsequent lines if any will be the description of // the group. // // See also ExplicitGroups for a more structured alternative. type Groups map[string]string func (g Groups) Apply(k *Kong) error { // nolint: revive for key, info := range g { lines := strings.Split(info, "\n") title := strings.TrimSpace(lines[0]) description := "" if len(lines) > 1 { description = strings.TrimSpace(strings.Join(lines[1:], "\n")) } k.groups = append(k.groups, Group{ Key: key, Title: title, Description: description, }) } return nil } // ExplicitGroups associates `group` field tags with their metadata. // // It can be used to provide a title or header to a command or flag group. func ExplicitGroups(groups []Group) Option { return OptionFunc(func(k *Kong) error { k.groups = groups return nil }) } // UsageOnError configures Kong to display context-sensitive usage if FatalIfErrorf is called with an error. func UsageOnError() Option { return OptionFunc(func(k *Kong) error { k.usageOnError = fullUsage return nil }) } // ShortUsageOnError configures Kong to display context-sensitive short // usage if FatalIfErrorf is called with an error. The default short // usage message can be overridden with kong.ShortHelp(...). func ShortUsageOnError() Option { return OptionFunc(func(k *Kong) error { k.usageOnError = shortUsage return nil }) } // ClearResolvers clears all existing resolvers. func ClearResolvers() Option { return OptionFunc(func(k *Kong) error { k.resolvers = nil return nil }) } // Resolvers registers flag resolvers. func Resolvers(resolvers ...Resolver) Option { return OptionFunc(func(k *Kong) error { k.resolvers = append(k.resolvers, resolvers...) return nil }) } // IgnoreFields will cause kong.New() to skip field names that match any // of the provided regex patterns. This is useful if you are not able to add a // kong="-" struct tag to a struct/element before the call to New. // // Example: When referencing protoc generated structs, you will likely want to // ignore/skip XXX_* fields. func IgnoreFields(regexes ...string) Option { return OptionFunc(func(k *Kong) error { for _, r := range regexes { if r == "" { return errors.New("regex input cannot be empty") } re, err := regexp.Compile(r) if err != nil { return fmt.Errorf("unable to compile regex: %v", err) } k.ignoreFields = append(k.ignoreFields, re) } return nil }) } // ConfigurationLoader is a function that builds a resolver from a file. type ConfigurationLoader func(r io.Reader) (Resolver, error) // Configuration provides Kong with support for loading defaults from a set of configuration files. // // Paths will be opened in order, and "loader" will be used to provide a Resolver which is registered with Kong. // // Note: The JSON function is a ConfigurationLoader. // // ~ and variable expansion will occur on the provided paths. func Configuration(loader ConfigurationLoader, paths ...string) Option { return OptionFunc(func(k *Kong) error { k.loader = loader for _, path := range paths { f, err := os.Open(ExpandPath(path)) if err != nil { if os.IsNotExist(err) || os.IsPermission(err) { continue } return err } f.Close() resolver, err := k.LoadConfig(path) if err != nil { return fmt.Errorf("%s: %v", path, err) } if resolver != nil { k.resolvers = append(k.resolvers, resolver) } } return nil }) } // ExpandPath is a helper function to expand a relative or home-relative path to an absolute path. // // eg. ~/.someconf -> /home/alec/.someconf func ExpandPath(path string) string { if filepath.IsAbs(path) { return path } if strings.HasPrefix(path, "~/") { user, err := user.Current() if err != nil { return path } return filepath.Join(user.HomeDir, path[2:]) } abspath, err := filepath.Abs(path) if err != nil { return path } return abspath } func siftStrings(ss []string, filter func(s string) bool) []string { i := 0 ss = append([]string(nil), ss...) for _, s := range ss { if filter(s) { ss[i] = s i++ } } return ss[0:i] } // DefaultEnvars option inits environment names for flags. // The name will not generate if tag "env" is "-". // Predefined environment variables are skipped. // // For example: // // --some.value -> PREFIX_SOME_VALUE func DefaultEnvars(prefix string) Option { processFlag := func(flag *Flag) { switch env := flag.Envs; { case flag.Name == "help": return case len(env) == 1 && env[0] == "-": flag.Envs = nil return case len(env) > 0: return } replacer := strings.NewReplacer("-", "_", ".", "_") names := append([]string{prefix}, camelCase(replacer.Replace(flag.Name))...) names = siftStrings(names, func(s string) bool { return !(s == "_" || strings.TrimSpace(s) == "") }) name := strings.ToUpper(strings.Join(names, "_")) flag.Envs = append(flag.Envs, name) flag.Value.Tag.Envs = append(flag.Value.Tag.Envs, name) } var processNode func(node *Node) processNode = func(node *Node) { for _, flag := range node.Flags { processFlag(flag) } for _, node := range node.Children { processNode(node) } } return PostBuild(func(k *Kong) error { processNode(k.Model.Node) return nil }) } // FlagNamer allows you to override the default kebab-case automated flag name generation. func FlagNamer(namer func(fieldName string) string) Option { return OptionFunc(func(k *Kong) error { k.flagNamer = namer return nil }) } kong-0.8.1/options_test.go000066400000000000000000000047421451020262500155430ustar00rootroot00000000000000package kong import ( "reflect" "strings" "testing" "github.com/alecthomas/assert/v2" ) func TestOptions(t *testing.T) { var cli struct{} p, err := New(&cli, Name("name"), Description("description"), Writers(nil, nil), Exit(nil)) assert.NoError(t, err) assert.Equal(t, "name", p.Model.Name) assert.Equal(t, "description", p.Model.Help) assert.Zero(t, p.Stdout) assert.Zero(t, p.Stderr) assert.Zero(t, p.Exit) } type impl string func (impl) Method() {} func TestBindTo(t *testing.T) { type iface interface { Method() } saw := "" method := func(i iface) error { saw = string(i.(impl)) // nolint return nil } var cli struct{} p, err := New(&cli, BindTo(impl("foo"), (*iface)(nil))) assert.NoError(t, err) err = callFunction(reflect.ValueOf(method), p.bindings) assert.NoError(t, err) assert.Equal(t, "foo", saw) } func TestInvalidCallback(t *testing.T) { type iface interface { Method() } saw := "" method := func(i iface) string { saw = string(i.(impl)) // nolint return saw } var cli struct{} p, err := New(&cli, BindTo(impl("foo"), (*iface)(nil))) assert.NoError(t, err) err = callFunction(reflect.ValueOf(method), p.bindings) assert.EqualError(t, err, `return value of func(kong.iface) string must implement "error"`) } type zrror struct{} func (*zrror) Error() string { return "error" } func TestCallbackCustomError(t *testing.T) { type iface interface { Method() } saw := "" method := func(i iface) *zrror { saw = string(i.(impl)) // nolint return nil } var cli struct{} p, err := New(&cli, BindTo(impl("foo"), (*iface)(nil))) assert.NoError(t, err) err = callFunction(reflect.ValueOf(method), p.bindings) assert.NoError(t, err) assert.Equal(t, "foo", saw) } type bindToProviderCLI struct { Called bool Cmd bindToProviderCmd `cmd:""` } type boundThing struct { } type bindToProviderCmd struct{} func (*bindToProviderCmd) Run(cli *bindToProviderCLI, b *boundThing) error { cli.Called = true return nil } func TestBindToProvider(t *testing.T) { var cli bindToProviderCLI app, err := New(&cli, BindToProvider(func() (*boundThing, error) { return &boundThing{}, nil })) assert.NoError(t, err) ctx, err := app.Parse([]string{"cmd"}) assert.NoError(t, err) err = ctx.Run() assert.NoError(t, err) assert.True(t, cli.Called) } func TestFlagNamer(t *testing.T) { var cli struct { SomeFlag string } app, err := New(&cli, FlagNamer(strings.ToUpper)) assert.NoError(t, err) assert.Equal(t, "SOMEFLAG", app.Model.Flags[1].Name) } kong-0.8.1/resolver.go000066400000000000000000000036411451020262500146470ustar00rootroot00000000000000package kong import ( "encoding/json" "io" "strings" ) // A Resolver resolves a Flag value from an external source. type Resolver interface { // Validate configuration against Application. // // This can be used to validate that all provided configuration is valid within this application. Validate(app *Application) error // Resolve the value for a Flag. Resolve(context *Context, parent *Path, flag *Flag) (interface{}, error) } // ResolverFunc is a convenience type for non-validating Resolvers. type ResolverFunc func(context *Context, parent *Path, flag *Flag) (interface{}, error) var _ Resolver = ResolverFunc(nil) func (r ResolverFunc) Resolve(context *Context, parent *Path, flag *Flag) (interface{}, error) { // nolint: revive return r(context, parent, flag) } func (r ResolverFunc) Validate(app *Application) error { return nil } // nolint: revive // JSON returns a Resolver that retrieves values from a JSON source. // // Flag names are used as JSON keys indirectly, by tring snake_case and camelCase variants. func JSON(r io.Reader) (Resolver, error) { values := map[string]interface{}{} err := json.NewDecoder(r).Decode(&values) if err != nil { return nil, err } var f ResolverFunc = func(context *Context, parent *Path, flag *Flag) (interface{}, error) { name := strings.ReplaceAll(flag.Name, "-", "_") snakeCaseName := snakeCase(flag.Name) raw, ok := values[name] if ok { return raw, nil } else if raw, ok = values[snakeCaseName]; ok { return raw, nil } raw = values for _, part := range strings.Split(name, ".") { if values, ok := raw.(map[string]interface{}); ok { raw, ok = values[part] if !ok { return nil, nil } } else { return nil, nil } } return raw, nil } return f, nil } func snakeCase(name string) string { name = strings.Join(strings.Split(strings.Title(name), "-"), "") //nolint: staticcheck return strings.ToLower(name[:1]) + name[1:] } kong-0.8.1/resolver_test.go000066400000000000000000000216701451020262500157100ustar00rootroot00000000000000package kong_test import ( "errors" "os" "reflect" "strings" "testing" "github.com/alecthomas/assert/v2" "github.com/alecthomas/kong" ) type envMap map[string]string func tempEnv(env envMap) func() { for k, v := range env { os.Setenv(k, v) } return func() { for k := range env { os.Unsetenv(k) } } } func newEnvParser(t *testing.T, cli interface{}, env envMap, options ...kong.Option) (*kong.Kong, func()) { t.Helper() restoreEnv := tempEnv(env) parser := mustNew(t, cli, options...) return parser, restoreEnv } func TestEnvarsFlagBasic(t *testing.T) { var cli struct { String string `env:"KONG_STRING"` Slice []int `env:"KONG_SLICE"` Interp string `env:"${kongInterp}"` } kongInterpEnv := "KONG_INTERP" parser, unsetEnvs := newEnvParser(t, &cli, envMap{ "KONG_STRING": "bye", "KONG_SLICE": "5,2,9", "KONG_INTERP": "foo", }, kong.Vars{ "kongInterp": kongInterpEnv, }, ) defer unsetEnvs() _, err := parser.Parse([]string{}) assert.NoError(t, err) assert.Equal(t, "bye", cli.String) assert.Equal(t, []int{5, 2, 9}, cli.Slice) assert.Equal(t, "foo", cli.Interp) } func TestEnvarsFlagMultiple(t *testing.T) { var cli struct { FirstENVPresent string `env:"KONG_TEST1_1,KONG_TEST1_2"` SecondENVPresent string `env:"KONG_TEST2_1,KONG_TEST2_2"` } parser, unsetEnvs := newEnvParser(t, &cli, envMap{ "KONG_TEST1_1": "value1.1", "KONG_TEST1_2": "value1.2", "KONG_TEST2_2": "value2.2", }, ) defer unsetEnvs() _, err := parser.Parse([]string{}) assert.NoError(t, err) assert.Equal(t, "value1.1", cli.FirstENVPresent) assert.Equal(t, "value2.2", cli.SecondENVPresent) } func TestEnvarsFlagOverride(t *testing.T) { var cli struct { Flag string `env:"KONG_FLAG"` } parser, restoreEnv := newEnvParser(t, &cli, envMap{"KONG_FLAG": "bye"}) defer restoreEnv() _, err := parser.Parse([]string{"--flag=hello"}) assert.NoError(t, err) assert.Equal(t, "hello", cli.Flag) } func TestEnvarsTag(t *testing.T) { var cli struct { Slice []int `env:"KONG_NUMBERS"` } parser, restoreEnv := newEnvParser(t, &cli, envMap{"KONG_NUMBERS": "5,2,9"}) defer restoreEnv() _, err := parser.Parse([]string{}) assert.NoError(t, err) assert.Equal(t, []int{5, 2, 9}, cli.Slice) } func TestEnvarsEnvPrefix(t *testing.T) { type Anonymous struct { Slice []int `env:"NUMBERS"` } var cli struct { Anonymous `envprefix:"KONG_"` } parser, restoreEnv := newEnvParser(t, &cli, envMap{"KONG_NUMBERS": "1,2,3"}) defer restoreEnv() _, err := parser.Parse([]string{}) assert.NoError(t, err) assert.Equal(t, []int{1, 2, 3}, cli.Slice) } func TestEnvarsEnvPrefixMultiple(t *testing.T) { type Anonymous struct { Slice1 []int `env:"NUMBERS1_1,NUMBERS1_2"` Slice2 []int `env:"NUMBERS2_1,NUMBERS2_2"` } var cli struct { Anonymous `envprefix:"KONG_"` } parser, restoreEnv := newEnvParser(t, &cli, envMap{"KONG_NUMBERS1_1": "1,2,3", "KONG_NUMBERS2_2": "5,6,7"}) defer restoreEnv() _, err := parser.Parse([]string{}) assert.NoError(t, err) assert.Equal(t, []int{1, 2, 3}, cli.Slice1) assert.Equal(t, []int{5, 6, 7}, cli.Slice2) } func TestEnvarsNestedEnvPrefix(t *testing.T) { type NestedAnonymous struct { String string `env:"STRING"` } type Anonymous struct { NestedAnonymous `envprefix:"ANON_"` } var cli struct { Anonymous `envprefix:"KONG_"` } parser, restoreEnv := newEnvParser(t, &cli, envMap{"KONG_ANON_STRING": "abc"}) defer restoreEnv() _, err := parser.Parse([]string{}) assert.NoError(t, err) assert.Equal(t, "abc", cli.String) } func TestEnvarsWithDefault(t *testing.T) { var cli struct { Flag string `env:"KONG_FLAG" default:"default"` } parser, restoreEnv := newEnvParser(t, &cli, envMap{}) defer restoreEnv() _, err := parser.Parse(nil) assert.NoError(t, err) assert.Equal(t, "default", cli.Flag) parser, restoreEnv = newEnvParser(t, &cli, envMap{"KONG_FLAG": "moo"}) defer restoreEnv() _, err = parser.Parse(nil) assert.NoError(t, err) assert.Equal(t, "moo", cli.Flag) } func TestEnv(t *testing.T) { type Embed struct { Flag string } type Cli struct { One Embed `prefix:"one-" embed:""` Two Embed `prefix:"two." embed:""` Three Embed `prefix:"three_" embed:""` Four Embed `prefix:"four_" embed:""` Five bool Six bool `env:"-"` } var cli Cli expected := Cli{ One: Embed{Flag: "one"}, Two: Embed{Flag: "two"}, Three: Embed{Flag: "three"}, Four: Embed{Flag: "four"}, Five: true, } // With the prefix parser, unsetEnvs := newEnvParser(t, &cli, envMap{ "KONG_ONE_FLAG": "one", "KONG_TWO_FLAG": "two", "KONG_THREE_FLAG": "three", "KONG_FOUR_FLAG": "four", "KONG_FIVE": "true", "KONG_SIX": "true", }, kong.DefaultEnvars("KONG")) defer unsetEnvs() _, err := parser.Parse(nil) assert.NoError(t, err) assert.Equal(t, expected, cli) // Without the prefix parser, unsetEnvs = newEnvParser(t, &cli, envMap{ "ONE_FLAG": "one", "TWO_FLAG": "two", "THREE_FLAG": "three", "FOUR_FLAG": "four", "FIVE": "true", "SIX": "true", }, kong.DefaultEnvars("")) defer unsetEnvs() _, err = parser.Parse(nil) assert.NoError(t, err) assert.Equal(t, expected, cli) } func TestJSONBasic(t *testing.T) { type Embed struct { String string } var cli struct { String string Slice []int SliceWithCommas []string Bool bool One Embed `prefix:"one." embed:""` Two Embed `prefix:"two." embed:""` } json := `{ "string": "🍕", "slice": [5, 8], "bool": true, "sliceWithCommas": ["a,b", "c"], "one":{ "string": "one value" }, "two.string": "two value" }` r, err := kong.JSON(strings.NewReader(json)) assert.NoError(t, err) parser := mustNew(t, &cli, kong.Resolvers(r)) _, err = parser.Parse([]string{}) assert.NoError(t, err) assert.Equal(t, "🍕", cli.String) assert.Equal(t, []int{5, 8}, cli.Slice) assert.Equal(t, []string{"a,b", "c"}, cli.SliceWithCommas) assert.Equal(t, "one value", cli.One.String) assert.Equal(t, "two value", cli.Two.String) assert.True(t, cli.Bool) } type testUppercaseMapper struct{} func (testUppercaseMapper) Decode(ctx *kong.DecodeContext, target reflect.Value) error { var value string err := ctx.Scan.PopValueInto("lowercase", &value) if err != nil { return err } target.SetString(strings.ToUpper(value)) return nil } func TestResolversWithMappers(t *testing.T) { var cli struct { Flag string `env:"KONG_MOO" type:"upper"` } restoreEnv := tempEnv(envMap{"KONG_MOO": "meow"}) defer restoreEnv() parser := mustNew(t, &cli, kong.NamedMapper("upper", testUppercaseMapper{}), ) _, err := parser.Parse([]string{}) assert.NoError(t, err) assert.Equal(t, "MEOW", cli.Flag) } func TestResolverWithBool(t *testing.T) { var cli struct { Bool bool } var resolver kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) { if flag.Name == "bool" { return true, nil } return nil, nil } p := mustNew(t, &cli, kong.Resolvers(resolver)) _, err := p.Parse(nil) assert.NoError(t, err) assert.True(t, cli.Bool) } func TestLastResolverWins(t *testing.T) { var cli struct { Int []int } var first kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) { if flag.Name == "int" { return 1, nil } return nil, nil } var second kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) { if flag.Name == "int" { return 2, nil } return nil, nil } p := mustNew(t, &cli, kong.Resolvers(first, second)) _, err := p.Parse(nil) assert.NoError(t, err) assert.Equal(t, []int{2}, cli.Int) } func TestResolverSatisfiesRequired(t *testing.T) { var cli struct { Int int `required` } var resolver kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) { if flag.Name == "int" { return 1, nil } return nil, nil } _, err := mustNew(t, &cli, kong.Resolvers(resolver)).Parse(nil) assert.NoError(t, err) assert.Equal(t, 1, cli.Int) } func TestResolverTriggersHooks(t *testing.T) { ctx := &hookContext{} var cli struct { Flag hookValue } var first kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) { if flag.Name == "flag" { return "one", nil } return nil, nil } _, err := mustNew(t, &cli, kong.Bind(ctx), kong.Resolvers(first)).Parse(nil) assert.NoError(t, err) assert.Equal(t, "one", string(cli.Flag)) assert.Equal(t, []string{"before:", "after:one"}, ctx.values) } type validatingResolver struct { err error } func (v *validatingResolver) Validate(app *kong.Application) error { return v.err } func (v *validatingResolver) Resolve(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) { return nil, nil } func TestValidatingResolverErrors(t *testing.T) { resolver := &validatingResolver{err: errors.New("invalid")} var cli struct{} _, err := mustNew(t, &cli, kong.Resolvers(resolver)).Parse(nil) assert.EqualError(t, err, "invalid") } kong-0.8.1/scanner.go000066400000000000000000000124561451020262500144430ustar00rootroot00000000000000package kong import ( "fmt" "strings" ) // TokenType is the type of a token. type TokenType int // Token types. const ( UntypedToken TokenType = iota EOLToken FlagToken // -- FlagValueToken // = ShortFlagToken // -[ PositionalArgumentToken // ) func (t TokenType) String() string { switch t { case UntypedToken: return "untyped" case EOLToken: return "" case FlagToken: // -- return "long flag" case FlagValueToken: // = return "flag value" case ShortFlagToken: // -[ return "short flag remainder" case PositionalArgumentToken: // return "positional argument" } panic("unsupported type") } // Token created by Scanner. type Token struct { Value interface{} Type TokenType } func (t Token) String() string { switch t.Type { case FlagToken: return fmt.Sprintf("--%v", t.Value) case ShortFlagToken: return fmt.Sprintf("-%v", t.Value) case EOLToken: return "EOL" default: return fmt.Sprintf("%v", t.Value) } } // IsEOL returns true if this Token is past the end of the line. func (t Token) IsEOL() bool { return t.Type == EOLToken } // IsAny returns true if the token's type is any of those provided. func (t TokenType) IsAny(types ...TokenType) bool { for _, typ := range types { if t == typ { return true } } return false } // InferredType tries to infer the type of a token. func (t Token) InferredType() TokenType { if t.Type != UntypedToken { return t.Type } if v, ok := t.Value.(string); ok { if strings.HasPrefix(v, "--") { // nolint: gocritic return FlagToken } else if v == "-" { return PositionalArgumentToken } else if strings.HasPrefix(v, "-") { return ShortFlagToken } } return t.Type } // IsValue returns true if token is usable as a parseable value. // // A parseable value is either a value typed token, or an untyped token NOT starting with a hyphen. func (t Token) IsValue() bool { tt := t.InferredType() return tt.IsAny(FlagValueToken, ShortFlagTailToken, PositionalArgumentToken) || (tt == UntypedToken && !strings.HasPrefix(t.String(), "-")) } // Scanner is a stack-based scanner over command-line tokens. // // Initially all tokens are untyped. As the parser consumes tokens it assigns types, splits tokens, and pushes them back // onto the stream. // // For example, the token "--foo=bar" will be split into the following by the parser: // // [{FlagToken, "foo"}, {FlagValueToken, "bar"}] type Scanner struct { args []Token } // ScanAsType creates a new Scanner from args with the given type. func ScanAsType(ttype TokenType, args ...string) *Scanner { s := &Scanner{} for _, arg := range args { s.args = append(s.args, Token{Value: arg, Type: ttype}) } return s } // Scan creates a new Scanner from args with untyped tokens. func Scan(args ...string) *Scanner { return ScanAsType(UntypedToken, args...) } // ScanFromTokens creates a new Scanner from a slice of tokens. func ScanFromTokens(tokens ...Token) *Scanner { return &Scanner{args: tokens} } // Len returns the number of input arguments. func (s *Scanner) Len() int { return len(s.args) } // Pop the front token off the Scanner. func (s *Scanner) Pop() Token { if len(s.args) == 0 { return Token{Type: EOLToken} } arg := s.args[0] s.args = s.args[1:] return arg } type expectedError struct { context string token Token } func (e *expectedError) Error() string { return fmt.Sprintf("expected %s value but got %q (%s)", e.context, e.token, e.token.InferredType()) } // PopValue pops a value token, or returns an error. // // "context" is used to assist the user if the value can not be popped, eg. "expected value but got " func (s *Scanner) PopValue(context string) (Token, error) { t := s.Pop() if !t.IsValue() { return t, &expectedError{context, t} } return t, nil } // PopValueInto pops a value token into target or returns an error. // // "context" is used to assist the user if the value can not be popped, eg. "expected value but got " func (s *Scanner) PopValueInto(context string, target interface{}) error { t, err := s.PopValue(context) if err != nil { return err } return jsonTranscode(t.Value, target) } // PopWhile predicate returns true. func (s *Scanner) PopWhile(predicate func(Token) bool) (values []Token) { for predicate(s.Peek()) { values = append(values, s.Pop()) } return } // PopUntil predicate returns true. func (s *Scanner) PopUntil(predicate func(Token) bool) (values []Token) { for !predicate(s.Peek()) { values = append(values, s.Pop()) } return } // Peek at the next Token or return an EOLToken. func (s *Scanner) Peek() Token { if len(s.args) == 0 { return Token{Type: EOLToken} } return s.args[0] } // Push an untyped Token onto the front of the Scanner. func (s *Scanner) Push(arg interface{}) *Scanner { s.PushToken(Token{Value: arg}) return s } // PushTyped pushes a typed token onto the front of the Scanner. func (s *Scanner) PushTyped(arg interface{}, typ TokenType) *Scanner { s.PushToken(Token{Value: arg, Type: typ}) return s } // PushToken pushes a preconstructed Token onto the front of the Scanner. func (s *Scanner) PushToken(token Token) *Scanner { s.args = append([]Token{token}, s.args...) return s } kong-0.8.1/scanner_test.go000066400000000000000000000013101451020262500154650ustar00rootroot00000000000000package kong import ( "testing" "github.com/alecthomas/assert/v2" ) func TestScannerTake(t *testing.T) { s := Scan("a", "b", "c", "-") assert.Equal(t, "a", s.Pop().Value) assert.Equal(t, "b", s.Pop().Value) assert.Equal(t, "c", s.Pop().Value) hyphen := s.Pop() assert.Equal(t, PositionalArgumentToken, hyphen.InferredType()) assert.Equal(t, EOLToken, s.Pop().Type) } func TestScannerPeek(t *testing.T) { s := Scan("a", "b", "c") assert.Equal(t, s.Peek().Value, "a") assert.Equal(t, s.Pop().Value, "a") assert.Equal(t, s.Peek().Value, "b") assert.Equal(t, s.Pop().Value, "b") assert.Equal(t, s.Peek().Value, "c") assert.Equal(t, s.Pop().Value, "c") assert.Equal(t, s.Peek().Type, EOLToken) } kong-0.8.1/tag.go000066400000000000000000000207321451020262500135610ustar00rootroot00000000000000package kong import ( "errors" "fmt" "reflect" "strconv" "strings" "unicode/utf8" ) // Tag represents the parsed state of Kong tags in a struct field tag. type Tag struct { Ignored bool // Field is ignored by Kong. ie. kong:"-" Cmd bool Arg bool Required bool Optional bool Name string Help string Type string TypeName string HasDefault bool Default string Format string PlaceHolder string Envs []string Short rune Hidden bool Sep rune MapSep rune Enum string Group string Xor []string Vars Vars Prefix string // Optional prefix on anonymous structs. All sub-flags will have this prefix. EnvPrefix string Embed bool Aliases []string Negatable bool Passthrough bool // Storage for all tag keys for arbitrary lookups. items map[string][]string } func (t *Tag) String() string { out := []string{} for key, list := range t.items { for _, value := range list { out = append(out, fmt.Sprintf("%s:%q", key, value)) } } return strings.Join(out, " ") } type tagChars struct { sep, quote, assign rune needsUnquote bool } var kongChars = tagChars{sep: ',', quote: '\'', assign: '=', needsUnquote: false} var bareChars = tagChars{sep: ' ', quote: '"', assign: ':', needsUnquote: true} // nolint:gocyclo func parseTagItems(tagString string, chr tagChars) (map[string][]string, error) { d := map[string][]string{} key := []rune{} value := []rune{} quotes := false inKey := true add := func() error { // Bare tags are quoted, therefore we need to unquote them in the same fashion reflect.Lookup() (implicitly) // unquotes "kong tags". s := string(value) if chr.needsUnquote && s != "" { if unquoted, err := strconv.Unquote(fmt.Sprintf(`"%s"`, s)); err == nil { s = unquoted } else { return fmt.Errorf("unquoting tag value `%s`: %w", s, err) } } d[string(key)] = append(d[string(key)], s) key = []rune{} value = []rune{} inKey = true return nil } runes := []rune(tagString) for idx := 0; idx < len(runes); idx++ { r := runes[idx] next := rune(0) eof := false if idx < len(runes)-1 { next = runes[idx+1] } else { eof = true } if !quotes && r == chr.sep { if err := add(); err != nil { return nil, err } continue } if r == chr.assign && inKey { inKey = false continue } if r == '\\' { if next == chr.quote { idx++ // We need to keep the backslashes, otherwise subsequent unquoting cannot work if chr.needsUnquote { value = append(value, r) } r = chr.quote } } else if r == chr.quote { if quotes { quotes = false if next == chr.sep || eof { continue } return nil, fmt.Errorf("%v has an unexpected char at pos %v", tagString, idx) } quotes = true continue } if inKey { key = append(key, r) } else { value = append(value, r) } } if quotes { return nil, fmt.Errorf("%v is not quoted properly", tagString) } if err := add(); err != nil { return nil, err } return d, nil } func getTagInfo(ft reflect.StructField) (string, tagChars) { s, ok := ft.Tag.Lookup("kong") if ok { return s, kongChars } return string(ft.Tag), bareChars } func newEmptyTag() *Tag { return &Tag{items: map[string][]string{}} } func tagSplitFn(r rune) bool { return r == ',' || r == ' ' } func parseTagString(s string) (*Tag, error) { items, err := parseTagItems(s, bareChars) if err != nil { return nil, err } t := &Tag{ items: items, } err = hydrateTag(t, nil) if err != nil { return nil, fmt.Errorf("%s: %s", s, err) } return t, nil } func parseTag(parent reflect.Value, ft reflect.StructField) (*Tag, error) { if ft.Tag.Get("kong") == "-" { t := newEmptyTag() t.Ignored = true return t, nil } items, err := parseTagItems(getTagInfo(ft)) if err != nil { return nil, err } t := &Tag{ items: items, } err = hydrateTag(t, ft.Type) if err != nil { return nil, failField(parent, ft, "%s", err) } return t, nil } func hydrateTag(t *Tag, typ reflect.Type) error { // nolint: gocyclo var typeName string var isBool bool var isBoolPtr bool if typ != nil { typeName = typ.Name() isBool = typ.Kind() == reflect.Bool isBoolPtr = typ.Kind() == reflect.Ptr && typ.Elem().Kind() == reflect.Bool } var err error t.Cmd = t.Has("cmd") t.Arg = t.Has("arg") required := t.Has("required") optional := t.Has("optional") if required && optional { return fmt.Errorf("can't specify both required and optional") } t.Required = required t.Optional = optional t.HasDefault = t.Has("default") t.Default = t.Get("default") // Arguments with defaults are always optional. if t.Arg && t.HasDefault { 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") t.Type = t.Get("type") t.TypeName = typeName for _, env := range t.GetAll("env") { t.Envs = append(t.Envs, strings.FieldsFunc(env, tagSplitFn)...) } t.Short, err = t.GetRune("short") if err != nil && t.Get("short") != "" { return fmt.Errorf("invalid short flag name %q: %s", t.Get("short"), err) } t.Hidden = t.Has("hidden") t.Format = t.Get("format") t.Sep, _ = t.GetSep("sep", ',') t.MapSep, _ = t.GetSep("mapsep", ';') t.Group = t.Get("group") for _, xor := range t.GetAll("xor") { t.Xor = append(t.Xor, strings.FieldsFunc(xor, tagSplitFn)...) } t.Prefix = t.Get("prefix") t.EnvPrefix = t.Get("envprefix") t.Embed = t.Has("embed") negatable := t.Has("negatable") if negatable && !isBool && !isBoolPtr { return fmt.Errorf("negatable can only be set on booleans") } t.Negatable = negatable aliases := t.Get("aliases") if len(aliases) > 0 { t.Aliases = append(t.Aliases, strings.FieldsFunc(aliases, tagSplitFn)...) } t.Vars = Vars{} for _, set := range t.GetAll("set") { parts := strings.SplitN(set, "=", 2) if len(parts) == 0 { return fmt.Errorf("set should be in the form key=value but got %q", set) } t.Vars[parts[0]] = parts[1] } t.PlaceHolder = t.Get("placeholder") t.Enum = t.Get("enum") scalarType := typ == nil || !(typ.Kind() == reflect.Slice || typ.Kind() == reflect.Map || typ.Kind() == reflect.Ptr) if t.Enum != "" && !(t.Required || t.HasDefault) && scalarType { return fmt.Errorf("enum value is only valid if it is either required or has a valid default value") } passthrough := t.Has("passthrough") if passthrough && !t.Arg && !t.Cmd { return fmt.Errorf("passthrough only makes sense for positional arguments or commands") } t.Passthrough = passthrough return nil } // Has returns true if the tag contained the given key. func (t *Tag) Has(k string) bool { _, ok := t.items[k] return ok } // Get returns the value of the given tag. // // Note that this will return the empty string if the tag is missing. func (t *Tag) Get(k string) string { values := t.items[k] if len(values) == 0 { return "" } return values[0] } // GetAll returns all encountered values for a tag, in the case of multiple occurrences. func (t *Tag) GetAll(k string) []string { return t.items[k] } // GetBool returns true if the given tag looks like a boolean truth string. func (t *Tag) GetBool(k string) (bool, error) { return strconv.ParseBool(t.Get(k)) } // GetFloat parses the given tag as a float64. func (t *Tag) GetFloat(k string) (float64, error) { return strconv.ParseFloat(t.Get(k), 64) } // GetInt parses the given tag as an int64. func (t *Tag) GetInt(k string) (int64, error) { return strconv.ParseInt(t.Get(k), 10, 64) } // GetRune parses the given tag as a rune. func (t *Tag) GetRune(k string) (rune, error) { value := t.Get(k) r, size := utf8.DecodeRuneInString(value) if r == utf8.RuneError || size < len(value) { return 0, errors.New("invalid rune") } return r, nil } // GetSep parses the given tag as a rune separator, allowing for a default or none. // The separator is returned, or -1 if "none" is specified. If the tag value is an // invalid utf8 sequence, the default rune is returned as well as an error. If the // tag value is more than one rune, the first rune is returned as well as an error. func (t *Tag) GetSep(k string, dflt rune) (rune, error) { tv := t.Get(k) if tv == "none" { return -1, nil } else if tv == "" { return dflt, nil } r, size := utf8.DecodeRuneInString(tv) if r == utf8.RuneError { return dflt, fmt.Errorf(`%v:"%v" has a rune error`, k, tv) } else if size != len(tv) { return r, fmt.Errorf(`%v:"%v" is more than a single rune`, k, tv) } return r, nil } kong-0.8.1/tag_test.go000066400000000000000000000117371451020262500146250ustar00rootroot00000000000000package kong_test import ( "strings" "testing" "github.com/alecthomas/assert/v2" "github.com/alecthomas/kong" ) func TestDefaultValueForOptionalArg(t *testing.T) { var cli struct { Arg string `kong:"arg,optional,default='\"\\'👌\\'\"'"` } p := mustNew(t, &cli) _, err := p.Parse(nil) assert.NoError(t, err) assert.Equal(t, "\"'👌'\"", cli.Arg) } func TestNoValueInTag(t *testing.T) { var cli struct { Empty1 string `kong:"default"` Empty2 string `kong:"default="` } p := mustNew(t, &cli) _, err := p.Parse(nil) assert.NoError(t, err) assert.Equal(t, "", cli.Empty1) assert.Equal(t, "", cli.Empty2) } func TestCommaInQuotes(t *testing.T) { var cli struct { Numbers string `kong:"default='1,2'"` } p := mustNew(t, &cli) _, err := p.Parse(nil) assert.NoError(t, err) assert.Equal(t, "1,2", cli.Numbers) } func TestBadString(t *testing.T) { var cli struct { Numbers string `kong:"default='yay'n"` } _, err := kong.New(&cli) assert.Error(t, err) } func TestNoQuoteEnd(t *testing.T) { var cli struct { Numbers string `kong:"default='yay"` } _, err := kong.New(&cli) assert.Error(t, err) } func TestEscapedQuote(t *testing.T) { var cli struct { DoYouKnow string `kong:"default='i don\\'t know'"` } p := mustNew(t, &cli) _, err := p.Parse(nil) assert.NoError(t, err) assert.Equal(t, "i don't know", cli.DoYouKnow) } func TestEscapingInQuotedTags(t *testing.T) { var cli struct { Regex1 string `kong:"default='\\d+\n'"` Regex2 string `default:"\\d+\n"` } p := mustNew(t, &cli) _, err := p.Parse(nil) assert.NoError(t, err) assert.Equal(t, "\\d+\n", cli.Regex1) assert.Equal(t, "\\d+\n", cli.Regex2) } func TestBareTags(t *testing.T) { var cli struct { Cmd struct { Arg string `arg` Flag string `required default:"👌"` } `cmd` } p := mustNew(t, &cli) _, err := p.Parse([]string{"cmd", "arg", "--flag=hi"}) assert.NoError(t, err) assert.Equal(t, "hi", cli.Cmd.Flag) assert.Equal(t, "arg", cli.Cmd.Arg) } func TestBareTagsWithJsonTag(t *testing.T) { var cli struct { Cmd struct { Arg string `json:"-" optional arg` Flag string `json:"best_flag" default:"\"'👌'\""` } `cmd json:"CMD"` } p := mustNew(t, &cli) _, err := p.Parse([]string{"cmd"}) assert.NoError(t, err) assert.Equal(t, "\"'👌'\"", cli.Cmd.Flag) assert.Equal(t, "", cli.Cmd.Arg) } func TestManySeps(t *testing.T) { var cli struct { Arg string `arg optional default:"hi"` } p := mustNew(t, &cli) _, err := p.Parse([]string{}) assert.NoError(t, err) assert.Equal(t, "hi", cli.Arg) } func TestTagSetOnEmbeddedStruct(t *testing.T) { type Embedded struct { Key string `help:"A key from ${where}."` } var cli struct { Embedded `set:"where=somewhere"` } buf := &strings.Builder{} p := mustNew(t, &cli, kong.Writers(buf, buf), kong.Exit(func(int) {})) _, err := p.Parse([]string{"--help"}) assert.NoError(t, err) assert.Contains(t, buf.String(), `A key from somewhere.`) } func TestTagSetOnCommand(t *testing.T) { type Command struct { Key string `help:"A key from ${where}."` } var cli struct { Command Command `set:"where=somewhere" cmd:""` } buf := &strings.Builder{} p := mustNew(t, &cli, kong.Writers(buf, buf), kong.Exit(func(int) {})) _, err := p.Parse([]string{"command", "--help"}) assert.NoError(t, err) assert.Contains(t, buf.String(), `A key from somewhere.`) } func TestTagSetOnFlag(t *testing.T) { var cli struct { Flag string `set:"where=somewhere" help:"A key from ${where}."` } buf := &strings.Builder{} p := mustNew(t, &cli, kong.Writers(buf, buf), kong.Exit(func(int) {})) _, err := p.Parse([]string{"--help"}) assert.NoError(t, err) assert.Contains(t, buf.String(), `A key from somewhere.`) } func TestTagAliases(t *testing.T) { type Command struct { Arg string `arg help:"Some arg"` } var cli struct { Cmd Command `cmd aliases:"alias1, alias2"` } p := mustNew(t, &cli) _, err := p.Parse([]string{"alias1", "arg"}) assert.NoError(t, err) assert.Equal(t, "arg", cli.Cmd.Arg) _, err = p.Parse([]string{"alias2", "arg"}) assert.NoError(t, err) assert.Equal(t, "arg", cli.Cmd.Arg) } func TestTagAliasesConflict(t *testing.T) { type Command struct { Arg string `arg help:"Some arg"` } var cli struct { Cmd Command `cmd hidden aliases:"other-cmd"` OtherCmd Command `cmd` } p := mustNew(t, &cli) _, err := p.Parse([]string{"other-cmd", "arg"}) assert.NoError(t, err) assert.Equal(t, "arg", cli.OtherCmd.Arg) } func TestTagAliasesSub(t *testing.T) { type SubCommand struct { Arg string `arg help:"Some arg"` } type Command struct { SubCmd SubCommand `cmd aliases:"other-sub-cmd"` } var cli struct { Cmd Command `cmd hidden` } p := mustNew(t, &cli) _, err := p.Parse([]string{"cmd", "other-sub-cmd", "arg"}) assert.NoError(t, err) assert.Equal(t, "arg", cli.Cmd.SubCmd.Arg) } func TestInvalidRuneErrors(t *testing.T) { cli := struct { Flag bool `short:"invalid"` }{} _, err := kong.New(&cli) assert.EqualError(t, err, ".Flag: invalid short flag name \"invalid\": invalid rune") } kong-0.8.1/testdata/000077500000000000000000000000001451020262500142645ustar00rootroot00000000000000kong-0.8.1/testdata/file.txt000066400000000000000000000000141451020262500157370ustar00rootroot00000000000000Hello world.kong-0.8.1/util.go000066400000000000000000000033051451020262500137600ustar00rootroot00000000000000package kong import ( "fmt" "os" "reflect" ) // ConfigFlag uses the configured (via kong.Configuration(loader)) configuration loader to load configuration // from a file specified by a flag. // // Use this as a flag value to support loading of custom configuration via a flag. type ConfigFlag string // BeforeResolve adds a resolver. func (c ConfigFlag) BeforeResolve(kong *Kong, ctx *Context, trace *Path) error { if kong.loader == nil { return fmt.Errorf("kong must be configured with kong.Configuration(...)") } path := string(ctx.FlagValue(trace.Flag).(ConfigFlag)) // nolint resolver, err := kong.LoadConfig(path) if err != nil { return err } ctx.AddResolver(resolver) return nil } // VersionFlag is a flag type that can be used to display a version number, stored in the "version" variable. type VersionFlag bool // BeforeReset writes the version variable and terminates with a 0 exit status. func (v VersionFlag) BeforeReset(app *Kong, vars Vars) error { fmt.Fprintln(app.Stdout, vars["version"]) app.Exit(0) return nil } // ChangeDirFlag changes the current working directory to a path specified by a flag // early in the parsing process, changing how other flags resolve relative paths. // // Use this flag to provide a "git -C" like functionality. // // It is not compatible with custom named decoders, e.g., existingdir. type ChangeDirFlag string // Decode is used to create a side effect of changing the current working directory. func (c ChangeDirFlag) Decode(ctx *DecodeContext) error { var path string err := ctx.Scan.PopValueInto("string", &path) if err != nil { return err } path = ExpandPath(path) ctx.Value.Target.Set(reflect.ValueOf(ChangeDirFlag(path))) return os.Chdir(path) } kong-0.8.1/util_test.go000066400000000000000000000030341451020262500150160ustar00rootroot00000000000000package kong import ( "io/ioutil" "os" "path/filepath" "runtime" "strings" "testing" "github.com/alecthomas/assert/v2" ) func TestConfigFlag(t *testing.T) { var cli struct { Config ConfigFlag Flag string } w, err := ioutil.TempFile("", "") assert.NoError(t, err) defer os.Remove(w.Name()) w.WriteString(`{"flag": "hello world"}`) // nolint: errcheck w.Close() p := Must(&cli, Configuration(JSON)) _, err = p.Parse([]string{"--config", w.Name()}) assert.NoError(t, err) assert.Equal(t, "hello world", cli.Flag) } func TestVersionFlag(t *testing.T) { var cli struct { Version VersionFlag } w := &strings.Builder{} p := Must(&cli, Vars{"version": "0.1.1"}) p.Stdout = w called := 1 p.Exit = func(s int) { called = s } _, err := p.Parse([]string{"--version"}) assert.NoError(t, err) assert.Equal(t, "0.1.1", strings.TrimSpace(w.String())) assert.Equal(t, 0, called) } func TestChangeDirFlag(t *testing.T) { cwd, err := os.Getwd() assert.NoError(t, err) defer os.Chdir(cwd) // nolint: errcheck dir := t.TempDir() file := filepath.Join(dir, "out.txt") err = os.WriteFile(file, []byte("foobar"), 0o600) assert.NoError(t, err) var cli struct { ChangeDir ChangeDirFlag `short:"C"` Path string `arg:"" type:"existingfile"` } p := Must(&cli) _, err = p.Parse([]string{"-C", dir, "out.txt"}) assert.NoError(t, err) if runtime.GOOS != "windows" { file, err = filepath.EvalSymlinks(file) // Needed because OSX uses a symlinked tmp dir. assert.NoError(t, err) } assert.Equal(t, file, cli.Path) } kong-0.8.1/visit.go000066400000000000000000000023411451020262500141400ustar00rootroot00000000000000package kong import ( "fmt" ) // Next should be called by Visitor to proceed with the walk. // // The walk will terminate if "err" is non-nil. type Next func(err error) error // Visitor can be used to walk all nodes in the model. type Visitor func(node Visitable, next Next) error // Visit all nodes. func Visit(node Visitable, visitor Visitor) error { return visitor(node, func(err error) error { if err != nil { return err } switch node := node.(type) { case *Application: return visitNodeChildren(node.Node, visitor) case *Node: return visitNodeChildren(node, visitor) case *Value: case *Flag: return Visit(node.Value, visitor) default: panic(fmt.Sprintf("unsupported node type %T", node)) } return nil }) } func visitNodeChildren(node *Node, visitor Visitor) error { if node.Argument != nil { if err := Visit(node.Argument, visitor); err != nil { return err } } for _, flag := range node.Flags { if err := Visit(flag, visitor); err != nil { return err } } for _, pos := range node.Positional { if err := Visit(pos, visitor); err != nil { return err } } for _, child := range node.Children { if err := Visit(child, visitor); err != nil { return err } } return nil }