pax_global_header00006660000000000000000000000064135561745600014526gustar00rootroot0000000000000052 comment=d15c8fca8dfd0f4dba023021118452517cf36d9a kong-0.2.1/000077500000000000000000000000001355617456000124645ustar00rootroot00000000000000kong-0.2.1/.circleci/000077500000000000000000000000001355617456000143175ustar00rootroot00000000000000kong-0.2.1/.circleci/config.yml000066400000000000000000000014051355617456000163070ustar00rootroot00000000000000version: 2 jobs: build: docker: - image: circleci/golang:1.10 working_directory: /go/src/github.com/alecthomas/kong steps: - checkout - run: name: Prepare command: | go get -v github.com/jstemmer/go-junit-report go get -v -t -d ./... curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s v1.15.0 mkdir ~/report when: always - run: name: Lint command: | ./bin/golangci-lint run - run: name: Test command: | go test -v ./... 2>&1 | tee report.txt && go-junit-report < report.txt > ~/report/junit.xml - store_test_results: path: ~/report kong-0.2.1/.golangci.yml000066400000000000000000000015361355617456000150550ustar00rootroot00000000000000run: tests: true output: print-issued-lines: false linters: enable-all: true disable: - maligned - lll - gochecknoglobals linters-settings: govet: check-shadowing: true gocyclo: min-complexity: 10 dupl: threshold: 100 goconst: min-len: 5 min-occurrences: 3 gocyclo: min-complexity: 20 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' kong-0.2.1/COPYING000066400000000000000000000020371355617456000135210ustar00rootroot00000000000000Copyright (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.2.1/README.md000066400000000000000000000423171355617456000137520ustar00rootroot00000000000000

# 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) 1. [Introduction](#introduction) 2. [Help](#help) 3. [Command handling](#command-handling) 1. [Switch on the command string](#switch-on-the-command-string) 2. [Attach a `Run(...) error` method to each command](#attach-a-run-error-method-to-each-command) 4. [Hooks: BeforeResolve(), BeforeApply(), AfterApply() and the Bind() option](#hooks-beforeresolve-beforeapply-afterapply-and-the-bind-option) 5. [Flags](#flags) 6. [Commands and sub-commands](#commands-and-sub-commands) 7. [Branching positional arguments](#branching-positional-arguments) 8. [Terminating positional arguments](#terminating-positional-arguments) 9. [Slices](#slices) 10. [Maps](#maps) 11. [Custom named decoders](#custom-named-decoders) 12. [Custom decoders (mappers)](#custom-decoders-mappers) 13. [Supported tags](#supported-tags) 14. [Variable interpolation](#variable-interpolation) 15. [Modifying Kong's behaviour](#modifying-kongs-behaviour) 1. [`Name(help)` and `Description(help)` - set the application name description](#namehelp-and-descriptionhelp---set-the-application-name-description) 2. [`Configuration(loader, paths...)` - load defaults from configuration files](#configurationloader-paths---load-defaults-from-configuration-files) 3. [`Resolver(...)` - support for default values from external sources](#resolver---support-for-default-values-from-external-sources) 4. [`*Mapper(...)` - customising how the command-line is mapped to Go values](#mapper---customising-how-the-command-line-is-mapped-to-go-values) 5. [`ConfigureHelp(HelpOptions)` and `Help(HelpFunc)` - customising help](#configurehelphelpoptions-and-helphelpfunc---customising-help) 6. [`Bind(...)` - bind values for callback hooks and Run() methods](#bind---bind-values-for-callback-hooks-and-run-methods) 7. [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 is automatically generated. With no other arguments provided, help will display a full summary of all available commands. 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. ## 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/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 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(debug bool) 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(debug bool) 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(cli.Debug) ctx.FatalIfErrorf(err) } ``` ## Hooks: BeforeResolve(), BeforeApply(), AfterApply() and the Bind() option If a node in the grammar has a `BeforeResolve(...)`, `BeforeApply(...) error` and/or `AfterApply(...) error` method, those methods will be called before validation/assignment and after validation/assignment, respectively. The `--help` flag is implemented with a `BeforeApply` hook. Arguments to hooks are provided via the `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. var 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)) // ... } ``` ## 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` } ``` ## 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. ## Terminating positional arguments If a [mapped type](#mapper---customising-how-the-command-line-is-mapped-to-go-values) is tagged with `arg` it will be treated as the final positional values to be parsed on the command line. If a 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 `;` eg. `--set="key1=value1;key2=value2"`. ## 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. | | `existingfile` | An existing file. ~ expansion is applied. | | `existingdir` | An existing directory. ~ expansion is applied. | 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. ## Custom decoders (mappers) 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. `env:"X"` | Specify envar to use for default value. `name:"X"` | Long name, for overriding field name. `help:"X"` | Help text. `type:"X"` | Specify [named types](#custom-named-types) to use. `placeholder:"X"` | Placeholder text. `default:"X"` | Default value. `short:"X"` | Short name, if flag. `required` | If present, flag/arg is required. `optional` | If present, flag/arg is optional. `hidden` | If present, command or flag is hidden. `format:"X"` | Format for parsing input, if supported. `sep:"X"` | Separator for sequences (defaults to ","). May be `none` to disable splitting. `enum:"X,Y,..."` | Set of valid values allowed for this flag. `group:"X"` | Logical group for a flag or command. `xor:"X"` | Exclusive OR group for flags. Only one flag in the group can be used which is restricted within the same command. `prefix:"X"` | 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. ## 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} eg. ```go type cli struct { Config string `type:"path" default:"${config_file}"` } func main() { kong.Parse(&cli, kong.Vars{ "config_file": "~/.app.conf", }) } ``` ## 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#L103) for an example of how the JSON file is structured. ### `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 knows how to map command-line input to Go. type Mapper interface { // Decode scan into target. // // "ctx" contains context about the value being decoded that may be useful // to some mapperss. Decode(ctx *MapperContext, scan *Scanner, 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. ### `Bind(...)` - bind values for callback hooks and Run() methods See the [section on hooks](#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.2.1/_examples/000077500000000000000000000000001355617456000144415ustar00rootroot00000000000000kong-0.2.1/_examples/docker/000077500000000000000000000000001355617456000157105ustar00rootroot00000000000000kong-0.2.1/_examples/docker/README.md000066400000000000000000000001461355617456000171700ustar00rootroot00000000000000# Large-scale composed CLI This directory illustrates how a large-scale CLI app could be structured. kong-0.2.1/_examples/docker/commands.go000066400000000000000000000122471355617456000200460ustar00rootroot00000000000000// 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.2.1/_examples/docker/main.go000066400000000000000000000112451355617456000171660ustar00rootroot00000000000000// 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.2.1/_examples/server/000077500000000000000000000000001355617456000157475ustar00rootroot00000000000000kong-0.2.1/_examples/server/console.go000066400000000000000000000016171355617456000177450ustar00rootroot00000000000000// 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.2.1/_examples/server/main.go000066400000000000000000000065301355617456000172260ustar00rootroot00000000000000package 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" ) type context struct { kong *kong.Context rl *readline.Instance } 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:"./_examples/server/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.2.1/_examples/server/server_rsa_key000066400000000000000000000032131355617456000207140ustar00rootroot00000000000000-----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.2.1/_examples/server/server_rsa_key.pub000066400000000000000000000005761355617456000215120ustar00rootroot00000000000000ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDErVBs9BiJtzRUVl1zoYc+RrmhN4adewxmLJfglsdQmXWg0MFeD00BNt+36r/pkOPJURCGrzGAu9gWQvyoEu3/pQBo65vRG2Y+pDCbGg0ta5cUXCdiUEoClDiB/nz+M5wY+l0TB+OSPAtvnfoGeLblcxThxit0i9AiG0xor5mqNr/cwAJXplFlkJ3yEAEmppm77eZESWcXV3uB8y8D+GFqrtjlVivytSsEceKatP8YQ/poJt82mjjdJ9GdQO7b4QAru8D1wzx+D87+a81HX/2PM5bKvxkMsPpHqJpkJDZqcQ9yqKYwI62JtJ5U6azTtlyf9NKyCZ8bBrytCSldUkDn kong-0.2.1/_examples/shell/000077500000000000000000000000001355617456000155505ustar00rootroot00000000000000kong-0.2.1/_examples/shell/main.go000066400000000000000000000015541355617456000170300ustar00rootroot00000000000000package 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.2.1/build.go000066400000000000000000000127221355617456000141160ustar00rootroot00000000000000package kong import ( "fmt" "reflect" "strings" ) func build(k *Kong, ast interface{}) (app *Application, err error) { defer catch(&err) 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 := buildNode(k, iv, ApplicationNode, seenFlags) 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) (out []flattenedField) { v = reflect.Indirect(v) for i := 0; i < v.NumField(); i++ { ft := v.Type().Field(i) fv := v.Field(i) tag := parseTag(fv, ft) if tag.Ignored { continue } if ft.Anonymous || tag.Embed { if fv.Kind() == reflect.Interface { fv = fv.Elem() } sub := flattenedFields(fv) for _, subf := range sub { // Assign parent if it's not already set. if subf.tag.Group == "" { subf.tag.Group = tag.Group } // Accumulate prefixes. subf.tag.Prefix = tag.Prefix + subf.tag.Prefix // Combine parent vars. subf.tag.Vars = tag.Vars.CloneWith(subf.tag.Vars) } out = append(out, sub...) continue } if !fv.CanSet() { continue } out = append(out, flattenedField{field: ft, value: fv, tag: tag}) } return out } func buildNode(k *Kong, v reflect.Value, typ NodeType, seenFlags map[string]bool) *Node { node := &Node{ Type: typ, Target: v, Tag: newEmptyTag(), } for _, field := range flattenedFields(v) { ft := field.field fv := field.value tag := field.tag name := tag.Name if name == "" { name = tag.Prefix + strings.ToLower(dashedString(ft.Name)) } else { name = tag.Prefix + name } // Nested structs are either commands or args, unless they implement the Mapper interface. if ft.Type.Kind() == reflect.Struct && (tag.Cmd || tag.Arg) && k.registry.ForValue(fv) == nil { typ := CommandNode if tag.Arg { typ = ArgumentNode } buildChild(k, node, typ, v, ft, fv, tag, name, seenFlags) } else { buildField(k, node, v, ft, fv, tag, name, seenFlags) } } // "Unsee" flags. for _, flag := range node.Flags { delete(seenFlags, flag.Name) } // Scan through argument positionals to ensure optional is never before a required. last := true for i, p := range node.Positional { if !last && p.Required { fail("argument %q can not be required after an optional", p.Name) } last = p.Required p.Position = i } return node } 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) { child := buildNode(k, fv, typ, seenFlags) child.Tag = tag child.Parent = node child.Help = tag.Help child.Hidden = tag.Hidden child.Group = tag.Group 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 { fail("positional branch %s.%s must have at least one child positional argument named %q", v.Type().Name(), ft.Name, name) } value := child.Positional[0] child.Positional = child.Positional[1:] if child.Help == "" { child.Help = value.Help } child.Name = value.Name if child.Name != name { fail("first field in positional branch %s.%s must have the same name as the parent field (%s).", v.Type().Name(), ft.Name, child.Name) } child.Argument = value } else { child.Name = name } node.Children = append(node.Children, child) if len(child.Positional) > 0 && len(child.Children) > 0 { fail("can't mix positional arguments and branching arguments on %s.%s", v.Type().Name(), ft.Name) } } func buildField(k *Kong, node *Node, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) { mapper := k.registry.ForNamedValue(tag.Type, fv) if mapper == nil { fail("unsupported field type %s.%s (of type %s)", v.Type(), ft.Name, ft.Type) } value := &Value{ Name: name, Help: tag.Help, Default: tag.Default, DefaultValue: reflect.New(fv.Type()).Elem(), Mapper: mapper, Tag: tag, Target: fv, Enum: tag.Enum, // 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] { fail("duplicate flag --%s", value.Name) } seenFlags[value.Name] = true flag := &Flag{ Value: value, Short: tag.Short, PlaceHolder: tag.PlaceHolder, Env: tag.Env, Group: tag.Group, Xor: tag.Xor, Hidden: tag.Hidden, } value.Flag = flag node.Flags = append(node.Flags, flag) } } kong-0.2.1/callbacks.go000066400000000000000000000027331355617456000147370ustar00rootroot00000000000000package kong import ( "fmt" "reflect" "strings" ) type bindings map[reflect.Type]reflect.Value 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 { b[reflect.TypeOf(v)] = reflect.ValueOf(v) } return b } // 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 callMethod(name string, v, f reflect.Value, bindings bindings) error { in := []reflect.Value{} t := f.Type() if t.NumOut() != 1 || t.Out(0) != callbackReturnSignature { return fmt.Errorf("return value of %T.%s() must be exactly \"error\"", v.Type(), name) } for i := 0; i < t.NumIn(); i++ { pt := t.In(i) if arg, ok := bindings[pt]; ok { in = append(in, arg) } else { return fmt.Errorf("couldn't find binding of type %s for parameter %d of %s.%s(), use kong.Bind(%s)", pt, i, v.Type(), name, pt) } } out := f.Call(in) if out[0].IsNil() { return nil } return out[0].Interface().(error) } kong-0.2.1/camelcase.go000066400000000000000000000055061355617456000147360ustar00rootroot00000000000000package 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.2.1/config_test.go000066400000000000000000000022241355617456000153170ustar00rootroot00000000000000package kong_test import ( "encoding/json" "io/ioutil" "os" "testing" "github.com/alecthomas/kong" "github.com/stretchr/testify/require" ) 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) require.NoError(t, err) require.Equal(t, "first", cli.Flag) } func TestConfigValidation(t *testing.T) { var cli struct { Flag string `json:"flag,omitempty" enum:"valid"` } cli.Flag = "invalid" conf, cleanConf := makeConfig(t, &cli) defer cleanConf() p := mustNew(t, &cli, kong.Configuration(kong.JSON, conf)) _, err := p.Parse(nil) require.Error(t, err) } func makeConfig(t *testing.T, config interface{}) (path string, cleanup func()) { t.Helper() w, err := ioutil.TempFile("", "") require.NoError(t, err) defer w.Close() err = json.NewEncoder(w).Encode(config) require.NoError(t, err) return w.Name(), func() { os.Remove(w.Name()) } } kong-0.2.1/context.go000066400000000000000000000426241355617456000145070ustar00rootroot00000000000000package kong import ( "fmt" "reflect" "sort" "strconv" "strings" "github.com/pkg/errors" ) // 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 } // 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[reflect.TypeOf(iface).Elem()] = reflect.ValueOf(impl) } // 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 { err := Visit(c.Model, func(node Visitable, next Next) error { if value, ok := node.(*Value); ok { if value.Enum != "" { if err := checkEnum(value, value.Target); err != nil { return err } } } return next(nil) }) if err != nil { return err } for _, resolver := range c.combineResolvers() { if err := resolver.Validate(c.Model); err != nil { return err } } for _, path := range c.Path { 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) trace(node *Node) (err error) { // nolint: gocyclo positional := 0 flags := []*Flag{} for _, group := range node.AllFlags(false) { flags = append(flags, group...) } for !c.scan.Peek().IsEOL() { token := c.scan.Peek() switch token.Type { case UntypedToken: switch v := token.Value.(type) { case string: switch { // Indicates end of parsing. All remaining arguments are treated as positional arguments only. case v == "--": c.scan.Pop() 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) } // 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) } 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] 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 } // 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) } } } return findPotentialCandidates(token.String(), candidates, "unexpected argument %s", token) default: return fmt.Errorf("unexpected token %s", token) } } 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 } for _, resolver := range resolvers { s, err := resolver.Resolve(c, path, flag) if err != nil { return err } if s == nil { continue } scan := Scan().PushTyped(s, 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(inserted, c.Path...) 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() 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 (c *Context) parseFlag(flags []*Flag, match string) (err error) { defer catch(&err) candidates := []string{} for _, flag := range flags { long := "--" + flag.Name short := "-" + string(flag.Short) candidates = append(candidates, long) if flag.Short != 0 { candidates = append(candidates, short) } if short != match && long != match { continue } // Found a matching flag. c.scan.Pop() err := flag.Parse(c.scan, c.getValue(flag.Value)) if err != nil { if e, ok := errors.Cause(err).(*expectedError); ok && e.token.InferredType().IsAny(FlagToken, ShortFlagToken) { return errors.Errorf("%s; perhaps try %s=%q?", err, flag.ShortSummary(), e.token) } return err } c.Path = append(c.Path, &Path{Flag: flag}) return nil } return findPotentialCandidates(match, candidates, "unknown flag %s", match) } // 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) { defer catch(&err) node := c.Selected() if node == nil { return fmt.Errorf("no command selected") } 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 = callMethod("Run", method.node.Target, method.method, method.binds); err != nil { return err } } return nil } // 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 _ = c.help(options, c) return nil } func checkMissingFlags(flags []*Flag) error { missing := []string{} for _, flag := range flags { if !flag.Required || flag.Set { continue } missing = append(missing, flag.Summary()) } if len(missing) == 0 { return nil } 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++ { missing = append(missing, "<"+values[positional].Name+">") } 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.Errorf("enum can only be applied to a slice or value") default: enumMap := value.EnumMap() v := fmt.Sprintf("%v", target) if enumMap[v] { return nil } enums := []string{} for enum := range enumMap { enums = append(enums, fmt.Sprintf("%q", enum)) } sort.Strings(enums) return fmt.Errorf("%s must be one of %s but got %q", value.ShortSummary(), strings.Join(enums, ","), target.Interface()) } } func checkXorDuplicates(paths []*Path) error { for _, path := range paths { seen := map[string]*Flag{} for _, flag := range path.Flags { if !flag.Set { continue } if flag.Xor == "" { continue } if seen[flag.Xor] != nil { return fmt.Errorf("--%s and --%s can't be used together", seen[flag.Xor].Name, flag.Name) } seen[flag.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) } kong-0.2.1/defaults.go000066400000000000000000000006151355617456000146240ustar00rootroot00000000000000package 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.2.1/defaults_test.go000066400000000000000000000013301355617456000156560ustar00rootroot00000000000000package kong import ( "testing" "time" "github.com/stretchr/testify/require" ) 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 { // nolint: scopelint t.Run(tt.name, func(t *testing.T) { err := ApplyDefaults(&tt.target) require.NoError(t, err) require.Equal(t, tt.expected, tt.target) }) } } kong-0.2.1/doc.go000066400000000000000000000016271355617456000135660ustar00rootroot00000000000000// 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.2.1/error.go000066400000000000000000000004431355617456000141450ustar00rootroot00000000000000package 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 } // Cause returns the original cause of the error. func (p *ParseError) Cause() error { return p.error } kong-0.2.1/global.go000066400000000000000000000004661355617456000142610ustar00rootroot00000000000000package 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.2.1/global_test.go000066400000000000000000000007721355617456000153200ustar00rootroot00000000000000package kong import ( "os" "testing" "github.com/stretchr/testify/require" ) 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 { require.Equal(t, Error{msg: "fail=' is not quoted properly"}, r) } }() Parse(&cli, Exit(func(_ int) { panic("exiting") })) require.Fail(t, "we were expecting a panic") } kong-0.2.1/go.mod000066400000000000000000000003741355617456000135760ustar00rootroot00000000000000module github.com/alecthomas/kong require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/mitchellh/mapstructure v1.1.2 github.com/pkg/errors v0.8.1 github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/testify v1.2.2 ) kong-0.2.1/go.sum000066400000000000000000000015431355617456000136220ustar00rootroot00000000000000github.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/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= 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= kong-0.2.1/guesswidth.go000066400000000000000000000002241355617456000151770ustar00rootroot00000000000000// +build appengine !linux,!freebsd,!darwin,!dragonfly,!netbsd,!openbsd package kong import "io" func guessWidth(w io.Writer) int { return 80 } kong-0.2.1/guesswidth_unix.go000066400000000000000000000014471355617456000162520ustar00rootroot00000000000000// +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.2.1/help.go000066400000000000000000000217461355617456000137550ustar00rootroot00000000000000package kong import ( "bytes" "fmt" "go/doc" "io" "strings" ) const ( defaultIndent = 2 defaultColumnPadding = 4 ) // Help flag. type helpValue bool func (h helpValue) BeforeApply(ctx *Context) error { options := ctx.Kong.helpOptions options.Summary = false err := ctx.Kong.help(options, ctx) if err != nil { return err } ctx.Kong.Exit(1) 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 // 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 } // 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 } // 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 // 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) } if flags := node.AllFlags(true); len(flags) > 0 { w.Print("") w.Print("Flags:") writeFlags(w.Indent(), flags) } cmds := node.Leaves(hide) if len(cmds) > 0 { w.Print("") w.Print("Commands:") if w.Tree { writeCommandTree(w, node) } else { iw := w.Indent() if w.Compact { writeCompactCommandList(cmds, iw) } else { writeCommandList(cmds, iw) } } } } 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) { iw := w.Indent() 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(iw, rows) } // nolint: unused type helpCommandGroup struct { Name string Commands []*Node } // nolint: unused, deadcode func collectCommandGroups(nodes []*Node) []helpCommandGroup { groups := map[string][]*Node{} for _, node := range nodes { groups[node.Group] = append(groups[node.Group], node) } out := []helpCommandGroup{} for name, nodes := range groups { if name == "" { name = "Commands" } out = append(out, helpCommandGroup{Name: name, Commands: nodes}) } 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 HelpOptions } func newHelpWriter(ctx *Context, options HelpOptions) *helpWriter { lines := []string{} w := &helpWriter{ indent: "", width: guessWidth(ctx.Stdout), lines: &lines, 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, " ")) } func (h *helpWriter) Indent() *helpWriter { return &helpWriter{indent: h.indent + " ", lines: h.lines, width: h.width - 2, HelpOptions: h.HelpOptions} } 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(), arg.Help}) } 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), flag.Help}) } } } 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 { flagString += fmt.Sprintf("-%c, --%s", flag.Short, 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 (w *HelpOptions) commandTree(node *Node, prefix string) (rows [][2]string) { var nodeName string switch node.Type { default: nodeName += prefix + node.Name case ArgumentNode: nodeName += prefix + "<" + node.Name + ">" } rows = append(rows, [2]string{nodeName, node.Help}) if w.Indenter == nil { prefix = SpaceIndenter(prefix) } else { prefix = w.Indenter(prefix) } for _, arg := range node.Positional { rows = append(rows, [2]string{prefix + arg.Summary(), arg.Help}) } for _, subCmd := range node.Children { rows = append(rows, w.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 } kong-0.2.1/help_test.go000066400000000000000000000123461355617456000150100ustar00rootroot00000000000000package kong_test import ( "bytes" "testing" "github.com/stretchr/testify/require" "github.com/alecthomas/kong" ) // nolint: govet type threeArg struct { RequiredThree bool `required` Three string `arg` } func (threeArg) Help() string { return `Detailed help provided through the HelpProvider interface.` } func TestHelp(t *testing.T) { // nolint: govet 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.Writers(w, w), kong.Exit(func(int) { exited = true panic(true) // Panic to fake "exit". }), ) t.Run("Full", func(t *testing.T) { require.PanicsWithValue(t, true, func() { _, err := app.Parse([]string{"--help"}) require.NoError(t, err) }) require.True(t, exited) expected := `Usage: test-app --required A test app. Flags: --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. 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) require.Equal(t, expected, w.String()) }) t.Run("Selected", func(t *testing.T) { exited = false w.Truncate(0) require.PanicsWithValue(t, true, func() { _, err := app.Parse([]string{"two", "hello", "--help"}) require.NoError(t, err) }) require.True(t, exited) expected := `Usage: test-app two --required --required-two --required-three Sub-sub-arg. Detailed help provided through the HelpProvider interface. Flags: --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()) require.Equal(t, expected, w.String()) }) } func TestHelpTree(t *testing.T) { // nolint: govet 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"` Two struct { 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.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) { require.PanicsWithValue(t, true, func() { _, err := app.Parse([]string{"--help"}) require.NoError(t, err) }) require.True(t, exited) expected := `Usage: test-app A test app. Flags: --help Show context-sensitive help. Commands: one subcommand one - thing subcommand thing - argument - subcommand other two Another subcommand. - Sub-sub-arg. - four 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) } require.Equal(t, expected, w.String()) }) t.Run("Selected", func(t *testing.T) { exited = false w.Truncate(0) require.PanicsWithValue(t, true, func() { _, err := app.Parse([]string{"one", "--help"}) require.NoError(t, err) }) require.True(t, exited) expected := `Usage: test-app one subcommand one Flags: --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) } require.Equal(t, expected, w.String()) }) } kong-0.2.1/hooks.go000066400000000000000000000013151355617456000141360ustar00rootroot00000000000000package kong // BeforeResolve is a documentation-only interface describing hooks that run before values are set. 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.2.1/interpolate.go000066400000000000000000000013061355617456000153410ustar00rootroot00000000000000package kong import ( "fmt" "regexp" ) var interpolationRegex = regexp.MustCompile(`((?:\${([[:alpha:]_][[:word:]]*))(?:=([^}]+))?})|(\$)|([^$]+)`) // Interpolate variables from vars into s for substrings in the form ${var} or ${var=default}. func interpolate(s string, vars map[string]string) (string, error) { out := "" matches := interpolationRegex.FindAllStringSubmatch(s, -1) for _, match := range matches { if name := match[2]; name != "" { value, ok := vars[name] if !ok { // No default value. if match[3] == "" { return "", fmt.Errorf("undefined variable ${%s}", name) } value = match[3] } out += value } else { out += match[0] } } return out, nil } kong-0.2.1/interpolate_test.go000066400000000000000000000005011355617456000163740ustar00rootroot00000000000000package kong import ( "testing" "github.com/stretchr/testify/require" ) func TestInterpolate(t *testing.T) { vars := map[string]string{ "age": "35", } actual, err := interpolate("${name=Bobby Brown} is ${age} years old", vars) require.NoError(t, err) require.Equal(t, `Bobby Brown is 35 years old`, actual) } kong-0.2.1/kong.go000066400000000000000000000215561355617456000137620ustar00rootroot00000000000000package kong import ( "fmt" "io" "os" "path/filepath" "reflect" "strings" ) var ( callbackReturnSignature = reflect.TypeOf((*error)(nil)).Elem() ) // Error reported by Kong. type Error struct{ msg string } func (e Error) Error() string { return e.msg } func fail(format string, args ...interface{}) { panic(Error{msg: 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 } // 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 noDefaultHelp bool usageOnError bool help HelpPrinter helpOptions HelpOptions helpFlag *Flag vars Vars // Set temporarily by Options. These are applied after build(). postBuildOptions []Option } // 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{}, } 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 } 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 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) 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) { vars = vars.CloneWith(value.Tag.Vars) if value.Default, err = interpolate(value.Default, vars); err != nil { return fmt.Errorf("default value for %s: %s", value.Summary(), err) } if value.Enum, err = interpolate(value.Enum, vars); err != nil { return fmt.Errorf("enum value for %s: %s", value.Summary(), err) } vars = vars.CloneWith(map[string]string{ "default": value.Default, "enum": value.Enum, }) if value.Help, err = interpolate(value.Help, vars); 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{ Value: &Value{ Name: "help", Help: "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) { defer catch(&err) 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 = 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 := callMethod(name, value, 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.Default == "" || ctx.values[flag.Value].IsValid() || !flag.Target.IsValid() { continue } method := getMethod(flag.Target, name) if !method.IsValid() { continue } path := &Path{Flag: flag} if err := callMethod(name, flag.Target, 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() } // Maybe display usage information. if err, ok := err.(*ParseError); ok && k.usageOnError { options := k.helpOptions _ = k.help(options, err.Context) fmt.Fprintln(k.Stdout) } k.Errorf("%s", msg) k.Exit(1) } // LoadConfig from path using the loader configured via Configuration(loader). // // "path" will have ~/ expanded. func (k *Kong) LoadConfig(path string) (Resolver, error) { path = ExpandPath(path) r, err := os.Open(path) // nolint: gas if err != nil { return nil, err } defer r.Close() return k.loader(r) } func catch(err *error) { msg := recover() if test, ok := msg.(Error); ok { *err = test } else if msg != nil { panic(msg) } } kong-0.2.1/kong.png000066400000000000000000002060021355617456000141300ustar00rootroot00000000000000PNG  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.2.1/kong_test.go000066400000000000000000000430541355617456000150160ustar00rootroot00000000000000package kong_test import ( "bytes" "fmt" "strings" "testing" "github.com/stretchr/testify/require" "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...) require.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"}) require.NoError(t, err) require.Equal(t, "user create ", ctx.Command()) t.Run("Missing", func(t *testing.T) { _, err := p.Parse([]string{"user", "create", "10"}) require.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"}) require.NoError(t, err) require.Equal(t, 10, cli.User.ID.ID) require.Equal(t, "user delete", ctx.Command()) t.Run("Missing", func(t *testing.T) { _, err = p.Parse([]string{"user"}) require.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{}) require.NoError(t, err) require.Equal(t, "", cli.Flag) require.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"}) require.NoError(t, err) require.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`}) require.NoError(t, err) require.Equal(t, []string{"a,b", "c"}, cli.Slice) } func TestArgSlice(t *testing.T) { // nolint: govet var cli struct { Slice []int `arg` Flag bool } parser := mustNew(t, &cli) _, err := parser.Parse([]string{"1", "2", "3", "--flag"}) require.NoError(t, err) require.Equal(t, []int{1, 2, 3}, cli.Slice) require.Equal(t, true, cli.Flag) } func TestArgSliceWithSeparator(t *testing.T) { // nolint: govet var cli struct { Slice []string `arg` Flag bool } parser := mustNew(t, &cli) _, err := parser.Parse([]string{"a,b", "c", "--flag"}) require.NoError(t, err) require.Equal(t, []string{"a,b", "c"}, cli.Slice) require.Equal(t, true, cli.Flag) } func TestUnsupportedFieldErrors(t *testing.T) { var cli struct { Keys struct{} } _, err := kong.New(&cli) require.Error(t, err) } func TestMatchingArgField(t *testing.T) { var cli struct { ID struct { NotID int `kong:"arg"` } `kong:"arg"` } _, err := kong.New(&cli) require.Error(t, err) } func TestCantMixPositionalAndBranches(t *testing.T) { var cli struct { Arg string `kong:"arg"` Command struct { } `kong:"cmd"` } _, err := kong.New(&cli) require.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"}) require.NoError(t, err) require.Equal(t, "moo", cli.Flag1) require.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{}) require.Error(t, err) } func TestOptionalArg(t *testing.T) { var cli struct { Arg string `kong:"arg,optional"` } parser := mustNew(t, &cli) _, err := parser.Parse([]string{}) require.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{}) require.NoError(t, err) require.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{}) require.NoError(t, err) require.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{}) require.Error(t, err) } func TestInvalidRequiredAfterOptional(t *testing.T) { var cli struct { ID int `kong:"arg,optional"` Name string `kong:"arg"` } _, err := kong.New(&cli) require.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"}) require.NoError(t, err) require.Equal(t, "gak", cli.Name.Name) require.Equal(t, true, cli.Name.Enabled) }) t.Run("WithoutFlag", func(t *testing.T) { _, err := parser.Parse([]string{"gak"}) require.NoError(t, err) require.Equal(t, "gak", cli.Name.Name) }) t.Run("WithNothing", func(t *testing.T) { _, err := parser.Parse([]string{}) require.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"}) require.NoError(t, err) require.Equal(t, "gak", cli.Name) require.Equal(t, 5, cli.ID) }) t.Run("ExtraOptional", func(t *testing.T) { _, err := parser.Parse([]string{"gak"}) require.NoError(t, err) require.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) require.Error(t, err) } func TestCommandMissingTagIsInvalid(t *testing.T) { var cli struct { One struct{} } _, err := kong.New(&cli) require.Error(t, err) } func TestDuplicateFlag(t *testing.T) { var cli struct { Flag bool Cmd struct { Flag bool } `kong:"cmd"` } _, err := kong.New(&cli) require.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) require.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"}) require.NoError(t, err) require.Error(t, ctx.Error) require.Equal(t, "one", ctx.Command()) } 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{} // nolint: scopelint t.Run(test.name, func(t *testing.T) { _, err := p.Parse(strings.Split(test.input, " ")) require.NoError(t, err) require.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"}) require.NoError(t, err) require.True(t, cli.Bool) require.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"}) require.NoError(t, err) require.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) require.NoError(t, err) require.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"}) require.NoError(t, err) require.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"}) require.NoError(t, err) require.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"}) require.NoError(t, err) require.Equal(t, "moo", cli.Embedded) require.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"}) require.NoError(t, err) require.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") require.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"}) require.NoError(t, err) err = ctx.Run("hello") require.NoError(t, err) require.Equal(t, "twohello", cli.One.Arg) ctx, err = p.Parse([]string{"two", "three"}) require.NoError(t, err) err = ctx.Run("ERROR") require.Error(t, err) ctx, err = p.Parse([]string{"three", "sub-command", "arg"}) require.NoError(t, err) err = ctx.Run("ping") require.NoError(t, err) require.Equal(t, "argping", cli.Three.SubCommand.Arg) } func TestInterpolationIntoModel(t *testing.T) { var cli struct { Flag string `default:"${default}" help:"Help, I need ${somebody}" enum:"${enum}"` EnumRef string `enum:"a,b" help:"One of ${enum}"` } _, err := kong.New(&cli) require.Error(t, err) p, err := kong.New(&cli, kong.Vars{ "default": "Some default value.", "somebody": "chickens!", "enum": "a,b,c,d", }) require.NoError(t, err) flag := p.Model.Flags[1] flag2 := p.Model.Flags[2] require.Equal(t, "Some default value.", flag.Default) require.Equal(t, "Help, I need chickens!", flag.Help) require.Equal(t, map[string]bool{"a": true, "b": true, "c": true, "d": true}, flag.EnumMap()) require.Equal(t, "One of a,b", flag2.Help) } func TestErrorMissingArgs(t *testing.T) { var cli struct { One string `arg:""` Two string `arg:""` } p := mustNew(t, &cli) _, err := p.Parse(nil) require.Error(t, err) require.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"}) require.NoError(t, err) _, err = p.Parse([]string{"--flag", "false"}) require.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"}) require.NoError(t, err) require.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"}) require.NoError(t, err) require.Equal(t, "foo", cli.SomeFlag) require.Equal(t, "yes", cli.TestInterface.(*TestImpl).Flag) } func TestExcludedField(t *testing.T) { var cli struct { Flag string Excluded string `kong:"-"` } p := mustNew(t, &cli) _, err := p.Parse([]string{"--flag=foo"}) require.NoError(t, err) _, err = p.Parse([]string{"--excluded=foo"}) require.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"}) require.NoError(t, err) require.Contains(t, buf.String(), `--one-flag=STRING`) require.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) require.NoError(t, err) require.Equal(t, "default", string(cli.Flag)) require.Equal(t, []string{"before:default", "after:default"}, ctx.values) } func TestEnum(t *testing.T) { var cli struct { Flag string `enum:"a,b,c"` } _, err := mustNew(t, &cli).Parse([]string{"--flag", "d"}) require.EqualError(t, err, "--flag must be one of \"a\",\"b\",\"c\" but got \"d\"") } 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"}) require.NoError(t, err) require.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"}) require.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) require.NoError(t, err) require.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) require.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" enum:"valid" default:"valid"` } p := mustNew(t, &cli) _, err := p.Parse(nil) require.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"}) require.EqualError(t, err, "--one and --two can't be used together") p = mustNew(t, &cli) _, err = p.Parse([]string{"--one", "--hello"}) require.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"}) require.NoError(t, err) p = mustNew(t, &cli) _, err = p.Parse([]string{"--two=hi", "cmd", "--three"}) require.Error(t, err, "--two and --three can't be used together") } func TestEnumSequence(t *testing.T) { var cli struct { State []string `enum:"a,b,c" default:"a"` } p := mustNew(t, &cli) _, err := p.Parse(nil) require.NoError(t, err) require.Equal(t, []string{"a"}, cli.State) } kong-0.2.1/levenshtein.go000066400000000000000000000014121355617456000153350ustar00rootroot00000000000000package 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.2.1/mapper.go000066400000000000000000000410461355617456000143040ustar00rootroot00000000000000package kong import ( "encoding/json" "io/ioutil" "math/bits" "net/url" "os" "reflect" "strconv" "strings" "time" "github.com/pkg/errors" ) var ( mapperValueType = reflect.TypeOf((*MapperValue)(nil)).Elem() boolMapperType = reflect.TypeOf((*BoolMapper)(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. type MapperValue interface { Decode(ctx *DecodeContext) error } 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) } return target.Addr().Interface().(MapperValue).Decode(ctx) } func (m *mapperValueAdapter) IsBool() bool { return m.isBool } // 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 MappverValue interface, it will be used. type Mapper interface { // Decode ctx.Value with ctx.Scanner into target. Decode(ctx *DecodeContext, target reflect.Value) error } // A BoolMapper is a Mapper to a value that is a boolean. // // This is used solely for formatting help. type BoolMapper interface { IsBool() 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: golint 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) { return &mapperValueAdapter{impl.Implements(boolMapperType)} } } var mapper Mapper var ok bool if mapper, ok = r.types[typ]; ok { return mapper } else 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(64)). RegisterKind(reflect.Uint8, uintDecoder(bits.UintSize)). 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 { err := ctx.Scan.PopValueInto("string", target.Addr().Interface()) return err })). 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()). RegisterName("path", pathMapper(r)). RegisterName("existingfile", existingFileMapper(r)). RegisterName("existingdir", existingDirMapper(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: target.SetBool(strings.ToLower(v) == "true") case bool: target.SetBool(v) default: return errors.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 { var value string if err := ctx.Scan.PopValueInto("duration", &value); err != nil { return err } r, err := time.ParseDuration(value) if err != nil { return errors.Errorf("expected duration but got %q: %s", value, err) } target.Set(reflect.ValueOf(r)) 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 } switch v := t.Value.(type) { case string: n, err := strconv.ParseInt(v, 10, bits) if err != nil { return errors.Errorf("expected an int but got %q (%T)", t, t.Value) } target.SetInt(n) case float64: target.SetInt(int64(v)) case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: target.Set(reflect.ValueOf(v)) default: return errors.Errorf("expected an int but got %q (%T)", t, t.Value) } 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 } switch v := t.Value.(type) { case string: n, err := strconv.ParseUint(v, 10, bits) if err != nil { return errors.Errorf("expected a uint but got %q (%T)", t, t.Value) } target.SetUint(n) case float64: target.SetUint(uint64(v)) case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: target.Set(reflect.ValueOf(v)) default: return errors.Errorf("expected an int but got %q (%T)", t, t.Value) } 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 errors.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 errors.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() var childScanner *Scanner if ctx.Value.Flag != nil { t := ctx.Scan.Pop() // If decoding a flag, we need an argument. if t.IsEOL() { return errors.Errorf("unexpected EOL") } switch v := t.Value.(type) { case string: childScanner = Scan(SplitEscaped(v, ';')...) case []map[string]interface{}: for _, m := range v { err := jsonTranscode(m, target.Addr().Interface()) if err != nil { return errors.WithStack(err) } } return nil case map[string]interface{}: return jsonTranscode(v, target.Addr().Interface()) default: return errors.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 errors.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.Errorf("type:\"\" on map field must be in the form \"[]:[]\"") } keyTypeName, valueTypeName = parts[0], parts[1] } keyScanner := Scan(key) keyDecoder := r.ForNamedType(keyTypeName, el.Key()) keyValue := reflect.New(el.Key()).Elem() if err := keyDecoder.Decode(ctx.WithScanner(keyScanner), keyValue); err != nil { return errors.Errorf("invalid map key %q", key) } valueScanner := Scan(value) valueDecoder := r.ForNamedType(valueTypeName, el.Elem()) valueValue := reflect.New(el.Elem()).Elem() if err := valueDecoder.Decode(ctx.WithScanner(valueScanner), valueValue); err != nil { return errors.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 argument. if t.IsEOL() { return errors.Errorf("unexpected EOL") } switch v := t.Value.(type) { case string: childScanner = Scan(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 errors.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 errors.WithStack(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 errors.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 } path = ExpandPath(path) target.SetString(path) 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 errors.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 } path = ExpandPath(path) stat, err := os.Stat(path) if err != nil { return err } if stat.IsDir() { return errors.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 errors.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 } path = ExpandPath(path) stat, err := os.Stat(path) if err != nil { return err } if !stat.IsDir() { return errors.Errorf("%q exists but is not a directory", path) } target.SetString(path) 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 errors.WithStack(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) { escaped := false token := "" for _, ch := range s { switch { case escaped: token += string(ch) escaped = false case ch == '\\': 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.Replace(e, string(sep), `\`+string(sep), -1)) } return strings.Join(escaped, string(sep)) } // 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: golint var filename string err := ctx.Scan.PopValueInto("filename", &filename) if err != nil { return err } filename = ExpandPath(filename) data, err := ioutil.ReadFile(filename) // nolint: gosec if err != nil { return errors.Errorf("failed to open %q: %s", filename, err) } *f = data return nil } func jsonTranscode(in, out interface{}) error { data, err := json.Marshal(in) if err != nil { return errors.WithStack(err) } return errors.Wrapf(json.Unmarshal(data, out), "%#v -> %T", in, out) } kong-0.2.1/mapper_test.go000066400000000000000000000112541355617456000153410ustar00rootroot00000000000000package kong_test import ( "bytes" "fmt" "io/ioutil" "net/url" "os" "reflect" "testing" "time" "github.com/stretchr/testify/require" "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) require.NoError(t, err) require.Equal(t, "", cli.Flag) _, err = k.Parse([]string{"--flag"}) require.NoError(t, err) require.Equal(t, "MOO", cli.Flag) } func TestNamedMapper(t *testing.T) { var cli struct { Flag string `type:"moo"` } k := mustNew(t, &cli, kong.NamedMapper("moo", testMooMapper{})) _, err := k.Parse(nil) require.NoError(t, err) require.Equal(t, "", cli.Flag) _, err = k.Parse([]string{"--flag"}) require.NoError(t, err) require.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"}) require.NoError(t, err) expected, err := time.Parse("2006", "2008") require.NoError(t, err) require.Equal(t, 2008, expected.Year()) require.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"}) require.NoError(t, err) require.Equal(t, time.Second*5, cli.Flag) } func TestSplitEscaped(t *testing.T) { require.Equal(t, []string{"a", "b"}, kong.SplitEscaped("a,b", ',')) require.Equal(t, []string{"a,b", "c"}, kong.SplitEscaped(`a\,b,c`, ',')) } func TestJoinEscaped(t *testing.T) { require.Equal(t, `a,b`, kong.JoinEscaped([]string{"a", "b"}, ',')) require.Equal(t, `a\,b,c`, kong.JoinEscaped([]string{`a,b`, `c`}, ',')) require.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"}) require.NoError(t, err) require.Equal(t, map[string]string{"first": "MOO", "second": "MOO"}, cli.TypedValue) _, err = k.Parse([]string{"--typed-key", "first=5s", "--typed-key", "second=10s"}) require.NoError(t, err) require.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"}) require.NoError(t, err) require.Equal(t, map[string]string{"a": "b", "c": "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"}) require.NoError(t, err) require.Equal(t, "http://w3.org", cli.URL.String()) _, err = p.Parse([]string{":foo"}) require.Error(t, err) } func TestSliceConsumesRemainingPositionalArgs(t *testing.T) { var cli struct { Remainder []string `arg:""` } p := mustNew(t, &cli) _, err := p.Parse([]string{"--", "ls", "-lart"}) require.NoError(t, err) require.Equal(t, []string{"ls", "-lart"}, cli.Remainder) } 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"}) require.NoError(t, err) require.Equal(t, "foo", cli.Value.decoded) } func TestFileContentFlag(t *testing.T) { var cli struct { File kong.FileContentFlag } f, err := ioutil.TempFile("", "") require.NoError(t, err) defer os.Remove(f.Name()) fmt.Fprint(f, "hello world") f.Close() _, err = mustNew(t, &cli).Parse([]string{"--file", f.Name()}) require.NoError(t, err) require.Equal(t, []byte("hello world"), []byte(cli.File)) } 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 require.Panics(t, func() { _, err := parser.Parse([]string{"--help"}) require.NoError(t, err) }) require.NotContains(t, b.String(), `--file=FILE-CONTENT-FLAG,...`) } kong-0.2.1/model.go000066400000000000000000000242441355617456000141210ustar00rootroot00000000000000package kong import ( "fmt" "math" "os" "reflect" "strconv" "strings" "github.com/pkg/errors" ) // 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 string Hidden bool Flags []*Flag Positional []*Positional Children []*Node Target reflect.Value // Pointer to the value in the grammar that this Node is associated with. Tag *Tag 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 { 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{} for _, arg := range n.Positional { args = append(args, arg.Summary()) } if len(args) != 0 { summary += " " + strings.Join(args, " ") } 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 case ArgumentNode: out += " " + "<" + n.Name + ">" } return strings.TrimSpace(out) } // A Value is either a flag or a variable positional argument. type Value struct { Flag *Flag // Nil if positional argument. Name string Help string 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). } // 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 } // 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.(BoolMapper); ok && m.IsBool() { return true } return v.Target.Kind() == reflect.Bool } // 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) { defer func() { if rerr := recover(); rerr != nil { switch rerr := rerr.(type) { case Error: err = errors.Wrap(rerr, v.ShortSummary()) default: panic(fmt.Sprintf("mapper %T failed to apply to %s: %s", v.Mapper, v.Summary(), rerr)) } } }() err = v.Mapper.Decode(&DecodeContext{Value: v, Scan: scan}, target) if err != nil { return errors.Wrap(err, v.ShortSummary()) } 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 v.Tag.Env != "" { envar := os.Getenv(v.Tag.Env) 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, v.Tag.Env, envar) } return nil } } if v.Default != "" { 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 string // Logical grouping when displaying. May also be used by configuration loaders to group options logically. Xor string PlaceHolder string Env string Short rune Hidden bool } func (f *Flag) String() string { out := "--" + f.Name if f.Short != 0 { out = fmt.Sprintf("-%c, %s", f.Short, out) } if !f.IsBool() { out += "=" + f.FormatPlaceHolder() } return out } // FormatPlaceHolder formats the placeholder string for a Flag. func (f *Flag) FormatPlaceHolder() string { tail := "" if f.Value.IsSlice() { tail += ",..." } if f.Default != "" { if f.Value.Target.Kind() == reflect.String { return strconv.Quote(f.Default) + tail } return f.Default + tail } if f.PlaceHolder != "" { return f.PlaceHolder + tail } if f.Value.IsMap() { return "KEY=VALUE;..." } return strings.ToUpper(f.Name) + tail } // 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.2.1/model_test.go000066400000000000000000000010121355617456000151440ustar00rootroot00000000000000package kong_test import ( "testing" "github.com/stretchr/testify/require" ) 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()) } require.Equal(t, []string{"one two", "one three "}, actual) } kong-0.2.1/options.go000066400000000000000000000133401355617456000145070ustar00rootroot00000000000000package kong import ( "io" "os/user" "path/filepath" "reflect" "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: golint // 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 := 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 }) } // NoDefaultHelp disables the default help flags. func NoDefaultHelp() Option { return OptionFunc(func(k *Kong) error { k.noDefaultHelp = true return nil }) } // Name overrides the application name. func Name(name string) Option { return OptionFunc(func(k *Kong) error { k.postBuildOptions = append(k.postBuildOptions, OptionFunc(func(k *Kong) error { k.Model.Name = name return nil })) return nil }) } // Description sets the application description. func Description(description string) Option { return OptionFunc(func(k *Kong) error { k.postBuildOptions = append(k.postBuildOptions, OptionFunc(func(k *Kong) error { k.Model.Help = description return nil })) 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[reflect.TypeOf(iface).Elem()] = reflect.ValueOf(impl) return nil }) } // Help printer to use. func Help(help HelpPrinter) Option { return OptionFunc(func(k *Kong) error { k.help = help 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 }) } // 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 = true 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 }) } // 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. // // ~ 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 { resolver, _ := k.LoadConfig(path) 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 } kong-0.2.1/options_test.go000066400000000000000000000015761355617456000155560ustar00rootroot00000000000000package kong import ( "reflect" "testing" "github.com/stretchr/testify/require" ) func TestOptions(t *testing.T) { var cli struct{} p, err := New(&cli, Name("name"), Description("description"), Writers(nil, nil), Exit(nil)) require.NoError(t, err) require.Equal(t, "name", p.Model.Name) require.Equal(t, "description", p.Model.Help) require.Nil(t, p.Stdout) require.Nil(t, p.Stderr) require.Nil(t, p.Exit) } type impl string func (impl) Method() {} func TestBindTo(t *testing.T) { type iface interface { Method() } saw := "" method := func(i iface) error { // nolint: unparam saw = string(i.(impl)) return nil } var cli struct{} p, err := New(&cli, BindTo(impl("foo"), (*iface)(nil))) require.NoError(t, err) err = callMethod("method", reflect.ValueOf(impl("??")), reflect.ValueOf(method), p.bindings) require.NoError(t, err) require.Equal(t, "foo", saw) } kong-0.2.1/resolver.go000066400000000000000000000026021355617456000146540ustar00rootroot00000000000000package 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: golint return r(context, parent, flag) } func (r ResolverFunc) Validate(app *Application) error { return nil } // nolint: golint // JSON returns a Resolver that retrieves values from a JSON source. // // Hyphens in flag names are replaced with underscores. 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.Replace(flag.Name, "-", "_", -1) raw, ok := values[name] if !ok { return nil, nil } return raw, nil } return f, nil } kong-0.2.1/resolver_test.go000066400000000000000000000130721355617456000157160ustar00rootroot00000000000000package kong_test import ( "errors" "os" "reflect" "strings" "testing" "github.com/stretchr/testify/require" "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) (*kong.Kong, func()) { t.Helper() restoreEnv := tempEnv(env) parser := mustNew(t, cli) return parser, restoreEnv } func TestEnvarsFlagBasic(t *testing.T) { var cli struct { String string `env:"KONG_STRING"` Slice []int `env:"KONG_SLICE"` } parser, unsetEnvs := newEnvParser(t, &cli, envMap{ "KONG_STRING": "bye", "KONG_SLICE": "5,2,9", }) defer unsetEnvs() _, err := parser.Parse([]string{}) require.NoError(t, err) require.Equal(t, "bye", cli.String) require.Equal(t, []int{5, 2, 9}, cli.Slice) } 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"}) require.NoError(t, err) require.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{}) require.NoError(t, err) require.Equal(t, []int{5, 2, 9}, cli.Slice) } 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) require.NoError(t, err) require.Equal(t, "default", cli.Flag) parser, restoreEnv = newEnvParser(t, &cli, envMap{"KONG_FLAG": "moo"}) defer restoreEnv() _, err = parser.Parse(nil) require.NoError(t, err) require.Equal(t, "moo", cli.Flag) } func TestJSONBasic(t *testing.T) { var cli struct { String string Slice []int SliceWithCommas []string Bool bool } json := `{ "string": "🍕", "slice": [5, 8], "bool": true, "slice_with_commas": ["a,b", "c"] }` r, err := kong.JSON(strings.NewReader(json)) require.NoError(t, err) parser := mustNew(t, &cli, kong.Resolvers(r)) _, err = parser.Parse([]string{}) require.NoError(t, err) require.Equal(t, "🍕", cli.String) require.Equal(t, []int{5, 8}, cli.Slice) require.Equal(t, []string{"a,b", "c"}, cli.SliceWithCommas) require.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{}) require.NoError(t, err) require.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) require.NoError(t, err) require.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) require.NoError(t, err) require.Equal(t, []int{2}, cli.Int) } func TestResolverSatisfiesRequired(t *testing.T) { // nolint: govet 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) require.NoError(t, err) require.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) require.NoError(t, err) require.Equal(t, "one", string(cli.Flag)) require.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) require.EqualError(t, err, "invalid") } kong-0.2.1/scanner.go000066400000000000000000000120371355617456000144470ustar00rootroot00000000000000package 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 { if v, ok := t.Value.(string); ok { if strings.HasPrefix(v, "--") { return FlagToken } 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 } // Scan creates a new Scanner from args with untyped tokens. func Scan(args ...string) *Scanner { s := &Scanner{} for _, arg := range args { s.args = append(s.args, Token{Value: arg}) } return s } // 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.2.1/scanner_test.go000066400000000000000000000011761355617456000155100ustar00rootroot00000000000000package kong import ( "testing" "github.com/stretchr/testify/require" ) func TestScannerTake(t *testing.T) { s := Scan("a", "b", "c") require.Equal(t, s.Pop().Value, "a") require.Equal(t, s.Pop().Value, "b") require.Equal(t, s.Pop().Value, "c") require.Equal(t, s.Pop().Type, EOLToken) } func TestScannerPeek(t *testing.T) { s := Scan("a", "b", "c") require.Equal(t, s.Peek().Value, "a") require.Equal(t, s.Pop().Value, "a") require.Equal(t, s.Peek().Value, "b") require.Equal(t, s.Pop().Value, "b") require.Equal(t, s.Peek().Value, "c") require.Equal(t, s.Pop().Value, "c") require.Equal(t, s.Peek().Type, EOLToken) } kong-0.2.1/tag.go000066400000000000000000000113441355617456000135710ustar00rootroot00000000000000package kong import ( "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 Default string Format string PlaceHolder string Env string Short rune Hidden bool Sep rune Enum string Group string Xor string Vars Vars Prefix string // Optional prefix on anonymous structs. All sub-flags will have this prefix. Embed bool // Storage for all tag keys for arbitrary lookups. items map[string][]string } type tagChars struct { sep, quote, assign rune } var kongChars = tagChars{sep: ',', quote: '\'', assign: '='} var bareChars = tagChars{sep: ' ', quote: '"', assign: ':'} func parseTagItems(tagString string, chr tagChars) map[string][]string { d := map[string][]string{} key := []rune{} value := []rune{} quotes := false inKey := true add := func() { d[string(key)] = append(d[string(key)], string(value)) key = []rune{} value = []rune{} inKey = true } 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 { add() continue } if r == chr.assign && inKey { inKey = false continue } if r == '\\' { if next == chr.quote { idx++ r = chr.quote } } else if r == chr.quote { if quotes { quotes = false if next == chr.sep || eof { continue } fail("%v has an unexpected char at pos %v", tagString, idx) } else { quotes = true continue } } if inKey { key = append(key, r) } else { value = append(value, r) } } if quotes { fail("%v is not quoted properly", tagString) } add() return d } 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 parseTag(fv reflect.Value, ft reflect.StructField) *Tag { if ft.Tag.Get("kong") == "-" { t := newEmptyTag() t.Ignored = true return t } t := &Tag{ items: parseTagItems(getTagInfo(ft)), } t.Cmd = t.Has("cmd") t.Arg = t.Has("arg") required := t.Has("required") optional := t.Has("optional") if required && optional { fail("can't specify both required and optional") } t.Required = required t.Optional = optional t.Default = t.Get("default") // Arguments with defaults are always optional. if t.Arg && t.Default != "" { t.Optional = true } t.Name = t.Get("name") t.Help = t.Get("help") t.Type = t.Get("type") t.Env = t.Get("env") t.Short, _ = t.GetRune("short") t.Hidden = t.Has("hidden") t.Format = t.Get("format") t.Sep, _ = t.GetRune("sep") t.Group = t.Get("group") t.Xor = t.Get("xor") t.Prefix = t.Get("prefix") t.Embed = t.Has("embed") if t.Sep == 0 { if t.Get("sep") == "none" { t.Sep = -1 } else { t.Sep = ',' } } t.Vars = Vars{} for _, set := range t.GetAll("set") { parts := strings.SplitN(set, "=", 2) if len(parts) == 0 { fail("set should be in the form key=value but got %q", set) } t.Vars[parts[0]] = parts[1] } t.PlaceHolder = t.Get("placeholder") if t.PlaceHolder == "" { t.PlaceHolder = strings.ToUpper(dashedString(fv.Type().Name())) } t.Enum = t.Get("enum") return t } // 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) { r, _ := utf8.DecodeRuneInString(t.Get(k)) if r == utf8.RuneError { return 0, fmt.Errorf("%v has a rune error", t.Get(k)) } return r, nil } kong-0.2.1/tag_test.go000066400000000000000000000066561355617456000146420ustar00rootroot00000000000000package kong_test import ( "strings" "testing" "github.com/stretchr/testify/require" "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) require.NoError(t, err) require.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) require.NoError(t, err) require.Equal(t, "", cli.Empty1) require.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) require.NoError(t, err) require.Equal(t, "1,2", cli.Numbers) } func TestBadString(t *testing.T) { var cli struct { Numbers string `kong:"default='yay'n"` } _, err := kong.New(&cli) require.Error(t, err) } func TestNoQuoteEnd(t *testing.T) { var cli struct { Numbers string `kong:"default='yay"` } _, err := kong.New(&cli) require.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) require.NoError(t, err) require.Equal(t, "i don't know", cli.DoYouKnow) } func TestBareTags(t *testing.T) { // nolint: govet 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"}) require.NoError(t, err) require.Equal(t, "hi", cli.Cmd.Flag) require.Equal(t, "arg", cli.Cmd.Arg) } func TestBareTagsWithJsonTag(t *testing.T) { // nolint: govet 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"}) require.NoError(t, err) require.Equal(t, "\"'👌'\"", cli.Cmd.Flag) require.Equal(t, "", cli.Cmd.Arg) } func TestManySeps(t *testing.T) { // nolint: govet var cli struct { Arg string `arg optional default:"hi"` } p := mustNew(t, &cli) _, err := p.Parse([]string{}) require.NoError(t, err) require.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"}) require.NoError(t, err) require.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"}) require.NoError(t, err) require.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"}) require.NoError(t, err) require.Contains(t, buf.String(), `A key from somewhere.`) } kong-0.2.1/util.go000066400000000000000000000020011355617456000137610ustar00rootroot00000000000000package kong import ( "fmt" ) // 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)) 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 // BeforeApply writes the version variable and terminates with a 0 exit status. func (v VersionFlag) BeforeApply(app *Kong, vars Vars) error { fmt.Fprintln(app.Stdout, vars["version"]) app.Exit(0) return nil } kong-0.2.1/util_test.go000066400000000000000000000015721355617456000150340ustar00rootroot00000000000000package kong import ( "io/ioutil" "os" "strings" "testing" "github.com/stretchr/testify/require" ) func TestConfigFlag(t *testing.T) { var cli struct { Config ConfigFlag Flag string } w, err := ioutil.TempFile("", "") require.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()}) require.NoError(t, err) require.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"}) require.NoError(t, err) require.Equal(t, "0.1.1", strings.TrimSpace(w.String())) require.Equal(t, 0, called) } kong-0.2.1/visit.go000066400000000000000000000023411355617456000141510ustar00rootroot00000000000000package 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 }