Giter VIP home page Giter VIP logo

kong's Introduction

Kong is a command-line parser for Go

CircleCI Go Report Card Slack chat

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] <paths> ...
shell ls [<paths> ...]

Can be represented by the following command-line structure:

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 <path>":
  case "ls":
  default:
    panic(ctx.Command())
  }
}

Help

Help as a user of a Kong application

Every Kong application includes a --help flag that will display auto-generated help.

eg.

$ shell --help
usage: shell <command>

A shell-like example app.

Flags:
  --help   Show context-sensitive help.
  --debug  Debug mode.

Commands:
  rm <path> ...
    Remove files.

  ls [<path> ...]
    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 <paths> ...

Remove files.

Arguments:
  <paths> ...  Paths to remove.

Flags:
      --debug        Debug mode.

  -f, --force        Force removal.
  -r, --recursive    Recursively remove files.

Defining help in Kong

Help is automatically generated from the command-line structure itself, including help:"" and other tags. Variables will also be interpolated into the help string.

Finally, any command, or argument type implementing the interface Help() string will have this function called to retrieve more detail to augment the help tag. This allows for much more descriptive text than can fit in Go tags. See _examples/shell/help

Showing the command's detailed help

A command's additional help text is not shown from top-level help, but can be displayed within contextual help:

Top level help

 $ go run ./_examples/shell/help --help
Usage: help <command>

An app demonstrating HelpProviders

Flags:
  -h, --help    Show context-sensitive help.
      --flag    Regular flag help

Commands:
  echo    Regular command help

Contextual

 $ go run ./_examples/shell/help echo --help
Usage: help echo <msg>

Regular command help

๐Ÿš€ additional command help

Arguments:
  <msg>    Regular argument help

Flags:
  -h, --help    Show context-sensitive help.
      --flag    Regular flag help

Showing an argument's detailed help

Custom help will only be shown for positional arguments with named fields (see the README section on positional arguments for more details on what that means)

Contextual argument help

 $ go run ./_examples/shell/help msg --help
Usage: help echo <msg>

Regular argument help

๐Ÿ“ฃ additional argument help

Flags:
  -h, --help    Show context-sensitive help.
      --flag    Regular flag help

Command handling

There are two ways to handle commands in Kong.

Switch on the command string

When you call kong.Parse() it will return a unique string representation of the command. Each command branch in the hierarchy will be a bare word and each branching argument or required positional argument will be the name surrounded by angle brackets. Here's an example:

There's an example of this pattern here.

eg.

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 <path>":
  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.

eg.

type Context struct {
  Debug bool
}

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

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

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

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

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

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

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

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

Hooks: BeforeReset(), BeforeResolve(), BeforeApply(), AfterApply() and the Bind() option

If a node in the grammar has a BeforeReset(...), BeforeResolve (...), BeforeApply(...) error and/or AfterApply(...) error method, those methods will be called before values are reset, before validation/assignment, and after validation/assignment, respectively.

The --help flag is implemented with a BeforeReset hook.

Arguments to hooks are provided via the Run(...) method or Bind(...) option. *Kong, *Context and *Path are also bound and finally, hooks can also contribute bindings via kong.Context.Bind() and kong.Context.BindTo().

eg.

// A flag with a hook that, if triggered, will set the debug loggers output to stdout.
type debugFlag bool

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

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

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

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

  // ...
}

Another example of using hooks is load the env-file:

package main

import (
  "fmt"
  "github.com/alecthomas/kong"
  "github.com/joho/godotenv"
)

type EnvFlag string

// BeforeResolve loads env file.
func (c EnvFlag) BeforeReset(ctx *kong.Context, trace *kong.Path) error {
  path := string(ctx.FlagValue(trace.Flag).(EnvFlag)) // nolint
  path = kong.ExpandPath(path)
  if err := godotenv.Load(path); err != nil {
    return err
  }
  return nil
}

var CLI struct {
  EnvFile EnvFlag
  Flag `env:"FLAG"`
}

func main() {
  _ = kong.Parse(&CLI)
  fmt.Println(CLI.Flag)
}

Flags

Any mapped 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.

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.

type CLI struct {
  Command struct {
    Flag string

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

If a sub-command is tagged with default:"1" it will be selected if there are no further arguments. If a sub-command is tagged with default:"withargs" it will be selected even if there are further arguments or flags and those arguments or flags are valid for the sub-command. This allows the user to omit the sub-command name on the CLI if its arguments/flags are not ambiguous with the sibling commands or flags.

Branching positional arguments

In addition to sub-commands, structs can also be configured as branching positional arguments.

This is achieved by tagging an unmapped 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 <name> to <name>

Can be represented with the following:

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

This looks a little verbose in this contrived example, but typically this will not be the case.

Positional arguments

If a field is tagged with arg:"" it will be treated as the final positional value to be parsed on the command line. By default positional arguments are required, but specifying optional:"" will alter this.

If a positional argument is a slice, all remaining arguments will be appended to that slice.

Slices

Slice values are treated specially. First the input is split on the sep:"<rune>" 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 <file> <file> ...

You would use the following:

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 <key>=<value> <key>=<value> ...

You would use the following:

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

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

Pointers

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

For example:

var CLI struct {
	Foo *int
}

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

Nested data structure

Kong support a nested data structure as well with embed:"". You can combine embed:"" with prefix:"":

var CLI struct {
  Logging struct {
    Level string `enum:"debug,info,warn,error" default:"info"`
    Type string `enum:"json,console" default:"console"`
  } `embed:"" prefix:"logging."`
}

This configures Kong to accept flags --logging.level and --logging.type.

Custom named decoders

Kong includes a number of builtin custom type mappers. These can be used by specifying the tag type:"<type>". They are registered with the option function NamedMapper(name, mapper).

Name Description
path A path. ~ expansion is applied. - is accepted for stdout, and will be passed unaltered.
existingfile An existing file. ~ expansion is applied. - is accepted for stdin, and will be passed unaltered.
existingdir An existing directory. ~ expansion is applied.
counter Increment a numeric field. Useful for -vvv. Can accept -s, --long or --long=N.
filecontent Read the file at path into the field. ~ expansion is applied. - is accepted for stdin, and will be passed unaltered.

Slices and maps treat type tags specially. For slices, the type:"" tag specifies the element type. For maps, the tag has the format tag:"[<key>]:[<value>]" where either may be omitted.

Supported field types

Custom decoders (mappers)

Any field implementing encoding.TextUnmarshaler or json.Unmarshaler will use those interfaces for decoding values. Kong also includes builtin support for many common Go types:

Type Description
time.Duration Populated using time.ParseDuration().
time.Time Populated using time.Parse(). Format defaults to RFC3339 but can be overridden with the format:"X" tag.
*os.File Path to a file that will be opened, or - for os.Stdin. File must be closed by the user.
*url.URL Populated with url.Parse().

For more fine-grained control, if a field implements the MapperValue interface it will be used to decode arguments into the field.

Supported tags

Tags can be in two forms:

  1. Standard Go syntax, eg. kong:"required,name='foo'".
  2. Bare tags, eg. required:"" name:"foo"

Both can coexist with standard Tag parsing.

Tag Description
cmd:"" If present, struct is a command.
arg:"" If present, field is an argument. Required by default.
env:"X,Y,..." Specify envars to use for default value. The envs are resolved in the declared order. The first value found is used.
name:"X" Long name, for overriding field name.
help:"X" Help text.
type:"X" Specify named types to use.
placeholder:"X" Placeholder text.
default:"X" Default value.
default:"1" On a command, make it the default.
default:"withargs" On a command, make it the default and allow args/flags from that command
short:"X" Short name, if flag.
aliases:"X,Y" One or more aliases (for cmd or flag).
required:"" If present, flag/arg is required.
optional:"" If present, flag/arg is optional.
hidden:"" If present, command or flag is hidden.
negatable:"" If present on a bool field, supports prefixing a flag with --no- to invert the default value
format:"X" Format for parsing input, if supported.
sep:"X" Separator for sequences (defaults to ","). May be none to disable splitting.
mapsep:"X" Separator for maps (defaults to ";"). May be none to disable splitting.
enum:"X,Y,..." Set of valid values allowed for this flag. An enum field must be required or have a valid default.
group:"X" Logical group for a flag or command.
xor:"X,Y,..." Exclusive OR groups for flags. Only one flag in the group can be used which is restricted within the same command. When combined with required, at least one of the xor group will be required.
prefix:"X" Prefix for all sub-flags.
envprefix:"X" Envar prefix for all sub-flags.
set:"K=V" Set a variable for expansion by child elements. Multiples can occur.
embed:"" If present, this field's children will be embedded in the parent. Useful for composition.
passthrough:"" If present on a positional argument, it stops flag parsing when encountered, as if -- was processed before. Useful for external command wrappers, like exec. On a command it requires that the command contains only one argument of type []string which is then filled with everything following the command, unparsed.
- Ignore the field. Useful for adding non-CLI fields to a configuration struct. e.g `kong:"-"`

Plugins

Kong CLI's can be extended by embedding the kong.Plugin type and populating it with pointers to Kong annotated structs. For example:

var pluginOne struct {
  PluginOneFlag string
}
var pluginTwo struct {
  PluginTwoFlag string
}
var cli struct {
  BaseFlag string
  kong.Plugins
}
cli.Plugins = kong.Plugins{&pluginOne, &pluginTwo}

Additionally if an interface type is embedded, it can also be populated with a Kong annotated struct.

Dynamic Commands

While plugins give complete control over extending command-line interfaces, Kong also supports dynamically adding commands via kong.DynamicCommand().

Variable interpolation

Kong supports limited variable interpolation into help strings, enum lists and default values.

Variables are in the form:

${<name>}
${<name>=<default>}

Variables are set with the Vars{"key": "value", ...} option. Undefined variable references in the grammar without a default will result in an error at construction time.

Variables can also be set via the set:"K=V" tag. In this case, those variables will be available for that node and all children. This is useful for composition by allowing the same struct to be reused.

When interpolating into flag or argument help strings, some extra variables are defined from the value itself:

${default}
${enum}

For flags with associated environment variables, the variable ${env} can be interpolated into the help string. In the absence of this variable in the help string, Kong will append ($$${env}) to the help string.

eg.

type cli struct {
  Config string `type:"path" default:"${config_file}"`
}

func main() {
  kong.Parse(&cli,
    kong.Vars{
      "config_file": "~/.app.conf",
    })
}

Validation

Kong does validation on the structure of a command-line, but also supports extensible validation. Any node in the tree may implement the following interface:

type Validatable interface {
    Validate() error
 }

If one of these nodes is in the active command-line it will be called during normal validation.

Modifying Kong's behaviour

Each Kong parser can be configured via functional options passed to New(cli interface{}, options...Option).

The full set of options can be found here.

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.

kong.Parse(&cli, kong.Configuration(kong.JSON, "/etc/myapp.json", "~/.myapp.json"))

See the tests for an example of how the JSON file is structured.

List of Configuration Loaders

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.

*Mapper(...) - customising how the command-line is mapped to Go values

Command-line arguments are mapped to Go values via the Mapper interface:

// A Mapper represents how a field is mapped from command-line values to Go.
//
// Mappers can be associated with concrete fields via pointer, reflect.Type, reflect.Kind, or via a "type" tag.
//
// Additionally, if a type implements the MapperValue interface, it will be used.
type Mapper interface {
	// Decode ctx.Value with ctx.Scanner into target.
	Decode(ctx *DecodeContext, target reflect.Value) error
}

All builtin Go types (as well as a bunch of useful stdlib types like time.Time) have mappers registered by default. Mappers for custom types can be added using kong.??Mapper(...) options. Mappers are applied to fields in four ways:

  1. NamedMapper(string, Mapper) and using the tag key type:"<name>".
  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 for details).
  2. Custom help can be wired into Kong via the Help(HelpFunc) option. The HelpFunc is passed a Context, which contains the parsed context for the current command-line. See the implementation of PrintHelp for an example.
  3. Use ValueFormatter(HelpValueFormatter) if you want to just customize the help text that is accompanied by flags and arguments.
  4. Use Groups([]Group) if you want to customize group titles or add a header.

Bind(...) - bind values for callback hooks and Run() methods

See the section on hooks for details.

Other options

The full set of options can be found here.

kong's People

Contributors

abhinav avatar alecthomas avatar camh- avatar colin-valentini avatar connyay avatar gak avatar hasheddan avatar josephschmitt avatar jotaen avatar keilin-anz avatar lespea avatar matfax avatar mfesenko avatar michal-kralik avatar mickael-menu avatar mitar avatar nsemikov avatar nsp avatar prashantv avatar pyqlsa avatar raxod502 avatar renovate[bot] avatar saj avatar shueybubbles avatar staktrace avatar talal avatar titusjaka avatar willabides avatar yazver avatar zbindenren avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

kong's Issues

Print usage on input without commands/args/flags

In the scenario of app invocation from terminal without any command/flags

> my-app

I'd expect the same output of:

> my-app --help

Instead, I have the error about the missing command.

my-app: error: expected one of "cmd1", "cmd2"

Is there a way/configuration to achieve my expected outcome?

The closest function I've found is kong.UsageOnError(), except it prints usage help for any error (when for any other type of error I'd expect to just print the specific error).

Config file path

How to load configuration from the path defined in the CLI/ENV? Do it parse twice?

type Cli struct {
    Config string `type:"path"`
    ...
}

func main() {
    var cli Cli
    _ := kong.Parse(&cli)
    ctx := kong.Parse(&cli, kong.Configuration(kong.JSON, cli.Config))
}

Children under hidden parent revealed in help/usage print out

Problem

Children command under a hidden parent is hidden in the root usage print out but revealed in helped text one level above the hidden parent.

Gist to demo the problem

https://gist.github.com/lyonlai/9ba5a40861759612a20ee69d010e0976

Steps to reproduce

  1. compile the file.
  2. run ./main and you should see no database update commands
  3. run ./main database and you will see the intended to be hidden update commands shows up in the print out

Canโ€™t bind values for interfaces

Given this source:

package main

import (
	"context"
	"fmt"

	"github.com/alecthomas/kong"
)

type Cmd struct {
}

func (Cmd) Run(ctx context.Context) error {
	fmt.Println("ok")
	return nil
}

type Options struct {
	Cmd Cmd `cmd:""`
}

func main() {
	var opts Options
	cli := kong.Parse(&opts)
	ctx := context.TODO()
	cli.FatalIfErrorf(cli.Run(ctx))
}

When I run go run example.go cmd, I get the following error:

example: error: couldn't find binding of type context.Context for parameter 0 of main.Cmd.Run(), use kong.Bind(context.Context)

I can bind values of concrete types, but it doesnโ€™t seem like I can bind interface values. Am I missing something?

(Thanks for making Kong, BTW. Great library.)

examples

hi.
Was a fan of kingpin. I know you moved onto kong. Was wondering if you can provide some examples of

  1. how to check if evvironment variables is set
  2. select from set of values

hope this library is as great as kingpin!

commandTree unexported

Hi

To experiment I tried to create my own HelpPrinter by copying DefaultHelpPrinter and make some changes.

The Problem I have now, is that CommandTree is unexported. Would a PR accepted to change that?

Default sub-command

Apologies in advance if I'm missing the obvious.

I'm aiming to have a command with subcommands where there is a default subcommand when no subcommand is entered on the command line. The help message for that would look something like this:

cli --help
Commands:
  command        command help
  command sub    sub-command help

cli command --help
Flags:
  --command-opt=STRING    command-opt help. Should NOT be part of sub command.

cli command sub --help
Flags:
  --sub-opt=STRING    sub-opt help. Should ONLY be part of sub command.

I've tried a couple of alternatives:

  1. Create a DefaultCommand with empty name in tag: name:"" (full file).
    This doesn't work and prints:
Commands:
  command default    command help
  command sub        sub help
  1. Command as parent command with Sub as nested command field.
    Here I don't get command command help listed in cli --help and
    the --command-opt appears in cli command sub --help (full file).

  2. Command and Sub as "sibling" commands with Sub name being: command sub (full file).

var cli struct {
	Command CommandCmd `cmd:"" help:"command help"`
	Sub     SubCmd     `cmd:"" help:"sub-command help" name:"sub"`
}

Here I get an error when trying to run cli command sub:
main: error: unexpected argument sub.

Positional args before command

I'd like to write a tool similar to pg_ctlcluster which synopsis is:

pg_ctlcluster <VERSION> <CLUSTER> <COMMAND>

where COMMAND can be something lile start, stop, etc...

For some reasons I'd like to have the command in that specific order without flags for neither VERSION nor CLUSTER.

A very naive approach is something like:

var CLI struct {
	Version string `arg`
	Cluster string `arg`
	Start struct {} `cmd`
	Stop struct {} `cmd`
}

Which results to:

panic: can't mix positional arguments and branching arguments on *struct { Version string "arg"; Cluster string "arg"; Start struct {} "cmd"; Stop struct {} "cmd" }

Am I missing something?

default values are ignored when used in Go standard struct tag syntax.

When using the Go standard syntax, the default struct tag is ignored:

var cli struct {
	URL  string `help:"URL of the AMQP server" default:"127.0.0.1:5432"`
        Bind string `kong:"help:'address to bind to',default:'0.0.0.0:9000'"`
}
[...]
	log.Printf("Config: %#v", cli)

results in (indentations by me to enhance readability)

2020/04/26 15:16:18 Config: struct { 
URL string "help:\"URL of the AMQP server\" default:\"127.0.0.1:5432\"";
Bind string "kong:\"help:'address to bind to',default:'0.0.0.0:9000'\"" }
{URL:"127.0.0.1:5432", Bind:""}

In and of itself, it is not much of a problem, one can use the simplified syntax. However, every linter gets a bit crazy about that.

mapsep tag

Would a pull request for a mapsep tag be accepted? A ; char is very unfortunate since ot terminates the command if you donโ€˜t put it into โ€˜.

Help Option Exit Code 1

Currently if the the default --help option is used the app returns exit code 1. I personally don't think that someone specifically calling --help should result in an exit code other than 0. Is there a way to change this behavior? I've reviewed the docs and Issues pretty thoroughly and can't find anything.

I'm also open to any rational that presents a good argument for why a program should return an exit code other than 0 for a user (person or automation) specifically calling --help as the only parameter sent to a program.

`-x` in an `arg` string slice confuses kong

I'm writing a program that takes a bunch of arguments, as well as a shell command as input (interpreted by kong as an arg) and exec's that command after doing a little processing. Unfortunately, kong doesn't deal well with the presence of short arguments given to the command-as-argument confuse kong and it errors. Here's an example:

package main

import (
	"fmt"

	"github.com/alecthomas/kong"
)

var CLI struct {
	Run struct {
		Cmd []string `arg name:"command" help:"Command to run."`
	} `cmd help:"Run command."`
}

func main() {
	ctx := kong.Parse(&CLI)
	switch ctx.Command() {
	case "run <command>":
		fmt.Println(CLI)
	default:
		panic(ctx.Command())
	}
}

Results

A plain command works:

$ go run main.go run ls
{{[ls]}}

A command with flags works after a --:

$ go run main.go run -- ls -la
{{[ls -la]}}

A command with flags is interpreted by kong and errors:

$ go run main.go run ls -la
main: error: unknown flag -l
exit status 1

Expected result

I would have expected kong to stop parsing further arguments once it finds things fitting the terminating argument string slice & treat the command line given above like it does it in the "works after a --" example.

Workaround

Have users pass in the -- flag, but that they can pass the un----escaped command line means if they start out without the escape and then add flags later, the command may fail without them realizing (or expecting) it.

Support bare tag parsing

kong:"" tags are good because they are the standard form, but are also quite verbose. We should also support raw tags like so:

type cli struct {
  Flag bool `required help:"A flag."`
  Arg string `arg optional help:"An arg."`
}

The complication here is that the standard Go tag parser only supports tags in the form <key>:"<value>". However, we are in luck because @gak's parser already effectively supports the above form, with some minor enhancements. If we parameterise the separator, the quote character and the assignment character, and allow multiple contiguous separator characters, we'll have a parser that works.

Inside kong:"" parser:

separator=","
quote="'"
assignment=":"

Outside:

separator=" " (space)
quote="\""
assignment="="

cannot parse int to int16 value

hcl config:

kafka {
  replication-factor = 3
}

struct to set

type KafkaConfig struct {
	BootstrapServer   string `help:"The advertised address and port of the kafka brokers" default:"localhost:9093"`
	ReplicationFactor int16  `help:"The replication factor for topics" default:"1"`
}
 panic: mapper kong.MapperFunc failed to apply to --kafka-replication-factor=1: reflect.Set: value of type int is not assignable to type int16 [recovered]

happens at

kong/mapper.go

Line 340 in 51a1ea9

target.Set(reflect.ValueOf(v))

Add default short -h to --help flag, conditionally

Dear Alec,

I noticed that -h is not enabled by default. I would like to suggest to enable it in root command conditionally, if no other command uses the -h shorthand in postBuild. Do you think it is a good idea? If so, I can PR it, just let me know. Or, even simpler: just enable -h shorthand by default - is it not Linux standard?

No license is specified for Kong?

Hi! It appears that no license information is specified for Kong, with the exception of camelcase.go and levenshtein.go from 3rd-party sources which you carefully documented.

This came to my attention when I used the dh-make-golang tool to create a preliminary Debian package for Kong (for Chroma 0.6.1) and saw the following warning:

2018/12/22 21:49:17 Could not determine license for "github.com/alecthomas/kong":
    GET https://api.github.com/repos/alecthomas/kong/license: 404 Not Found []

Thanks in advance for any clarification!

int mapper tests fail on 32-bit systems

On armv7hl and i686 builds (both 32-bit), these tests fail due to constant overflow:

# github.com/alecthomas/kong_test [github.com/alecthomas/kong.test]
./mapper_test.go:287:26: constant 9223372036854775807 overflows int
./mapper_test.go:290:26: constant 4294967295 overflows int
./mapper_test.go:312:16: constant -9223372036854775808 overflows int
FAIL	github.com/alecthomas/kong [build failed]

Counter named type doesn't accept arguments

When using counter named type, only the short version is allowed, even though --help shows I should be able to provide an int type value as an argument in the longer flag version as well.

Configuration:

type cli struct {
    Verbosity int `short:"v" type"counter" help:"Set verbosity level."`
}

--help output:

Flags:
      --help                      Show context-sensitive help.
  -v, --verbosity=INT             Set verbosity level.

When I try executing with the longer version:

go run main.go --verbosity=3
main: error: unexpected flag argument "3"
exit status 1

Allow single dash as argument value

Some CLI tools traditionally use the single dash to mean "stdin".

Examples are tar tvfz - or cat -

Kong however chokes when encountering a - not followed with a letter with error: expected short flag.

command aliases (short commands)

Some flag handling packages allow shortcuts for commands.
I can do it my putting commands double in the parent struct like this, but it is kind of ugly:

type rootCmd struct {
	LongName    longNameCmd        `cmd:"" help:"Do something."`
	X1                 longNameCmd        `cmd:"" name:"ln"`
}

Is there a better way? Should I (try to) make a pull request for an extra tag, or to enable the short tag for commands as well?

embedding reusable subcommands from the same struct

In my current project, I faced the following challenge. I have a cli whichs commands are composed of two subcommand sets. E.g, build and find as root commands and both have the same sub commands with the same methods, e.g., build foo, build goo, find foo, and find goo. Ideally, there would be only one sub command struct for foo and goo that is embedded in the root commands. However, I could not find an idiomatic way to call the root commands (i.e, parents) from the sub commands Run methods. I had to access context.Command() and identify the struct field using reflection.

So I'm wondering if you considered such a case - maybe I haven't seen the obvious again - or if you would like to see a feature that allows to traverse to parent command structs and execute a Run on it. Thereby, someone could create quite advanced command chains.

Show environment variable in help

Discoverability of environment variables is an issue in general. Command-line arguments can probably help with this. It would be great if when you invoke the help (e.g. --help) if the arguments that have associate environment variables could be shown there.

For example, I have this snippet in my struct:

WhiteList  []string `env:"ANONYMIZER_WHITE_LIST" sep:";" help:"List of tags that should be white listed." default:"(0008,103e);(0020,0011)"`

But, when I invoke my command with --help there is no mention of the environment variable:

Flags:
...
      --white-list=(0008,103e);(0020,0011),...
                   List of tags that should be white listed.

What if the output showed something like this for flags with the 'env' property:

Flags:
...
      --white-list=(0008,103e);(0020,0011),...
                   List of tags that should be white listed.
                   Environment variable: ANONYMIZER_WHITE_LIST

Combination of `required` and `enum` cause other sub-command failure

For given sample:

package main

import (
	"fmt"

	"github.com/alecthomas/kong"
)

var CLI struct {
	One struct {
		OneArg string `arg:"" required:""`
	} `cmd: ""`
	Two struct {
		TwoArg string `arg:"" enum:"a,b,c" required:""`
	} `cmd: ""`
}

func main() {
	ctx := kong.Parse(&CLI)
	switch cmd := ctx.Command(); cmd {
	case "one <one-arg>", "two <two-arg>":
		fmt.Println("command:", cmd)
	default:
		panic(ctx.Command())
	}
}

I expect the following behaviour:

> go run main.go one X
command: one <one-arg>
> go run main.go two a
command: two <two-arg>

However, I get:

> go run main.go one X          
main: error: <two-arg> must be one of "a","b","c" but got ""
exit status 1
> go run main.go two a
command: two <two-arg>

When I remove the enum tag or replace required with default:"a" in command two, command one behaves as expected.

optional branching positional arguments

Since I am unclear how to describe this, I will just show the DSL for the behaviour I would like:

Usage: cli <command>

Commands:
  cmd [<arg> [sub <arg>]]
  cmd sub

I can construct a struct where, ./cli cmd executes cmd other, thus effectively giving me cmd [<arg>]: (both the docs do not show this, and it feels like a hack, is there a better way?)

Usage: cli <command>

Commands:
  cmd other
  cmd <arg>
  cmd sub
var CLI struct {
        Cmd struct{
                Other struct{} `cmd default:"1"`
                Arg struct {
                        Arg string `arg`
                } `arg`
                Sub struct{} `cmd`
        } `cmd`
}

Again trickery with default:"1" gives me ./cli cmd arg which executes cmd <arg>, but it does not document well:

Usage: cli <command>

Commands:
  cmd other
  cmd <arg> other
  cmd <arg> sub [<arg>]
  cmd sub
var CLI struct {
        Cmd struct{
                Other struct{} `cmd default:"1"`
                Arg struct {
                        Arg string `arg`
                        Other struct{} `cmd default:"1"`
                        Sub struct{
                                Arg string `arg optional`
                        } `cmd`
                } `arg`
                Sub struct{} `cmd`
        } `cmd`
}

If there is a better way, I am all ears, otherwise I the following workaround would make the docs read correctly without refactoring the parsing logic:

Usage: cli <command>

Commands:
  cmd                             # this is implied by `default:"1"`
  cmd other                       # I am fine with this being visible, but somebody may want to hide it
  cmd <arg>                       # this is implied by `default"1"`
  cmd <arg> sub [<arg>]
  cmd sub
var CLI struct {
        Cmd struct{
                Other struct{} `cmd default:"1"`
                Arg struct {
                        Arg string `arg`  // with `cmd <arg>` now visible, either this tag
                        Other struct{} `cmd hidden default:"1"`
                        Sub struct{
                                Arg string `arg optional`
                        } `cmd`
                } `arg` // or this one should control visibility
                Sub struct{} `cmd`
        } `cmd`
}

Factor parsing state into a "ParseContext".

This will hold the state for the parse, which we can then use for not only parsing, but for context-sensitive help, and context-sensitive completion.

For example, if at a subcommand app cmd --help, the parse context would have the cumulative flags, cumulative commands, as well as the current node. This allows help to be context-sensitive, and completion as well.

Discussion about Help

While I was working on custom HelpPrinter implementation I realized that I had to copy a lot of code from the DefaultHelpPrinter function. And with the newly added interpolation of the $env variable, the current HelpPrinter function is very close to what I was looking for. To close the gap I would like propose the following changes:

1) Better representation of enum values as placeholder

For the following Tags:

StringFlag string `enum:"a,b,c" help:"Help."`

I would like to see the following placeholder:

Flags:
      --string-flag=a|b|c        Help.

And if there is a default tag:

StringFlag string `enum:"a,b,c" help:"Help." default:"b"`

Maybe something like this:

Flags:
      --string-flag=a|(b)|c        Help.

Or maybe even make the selected value bold with github.com/fatih/color.

2) AutomaticEnv option

Add a new Option AutomaticEnv which would do the following:

Visit all flags and args and sets the empty env field tags with the following:

strings.ToUpper(app.Model.Name+"_"+strings.ReplaceAll(flag.Name,"-","_"))

So a flag-name would correspond to APPNAME_FLAG_NAME

Let me know what you think. I am willing to do the implementation if you want.

Option for full text search across all help

Using the AWS CLI illustrates how useful that would be, as it's basically impossible to discover how to use it unless you already know exactly what you're looking for. Something like a --help[=<foo>] would be excellent.

Is it possible to disable to automatically add envar to help?

Sorry to bother again.

I am writing a custom HelpPrinter but since the latest commit I had to add the following:

newHelp = strings.ReplaceAll(help, "($"+envVar+")", "")

This works, but is error-prone. Should you decide to change the format the above code can break.

Is there a better way to disable that new behavior?

Shell completions

Would you be open to adding bash and/or zsh completion generation in a manner similar to cobra?

Default env variables for flags

I'm struggling to understand if there's a way in Kong to enable automatic resolution for flag from environment variables without specifying the env tag on each flag. I've tried playing around with hooks and resolvers but couldn't get it working.

What I want is to be able to pull configuration values from flags, config files and environment variables automatically. Also would be great to be able to specify a prefix for environment variables if needed.

Show used config files (if any)

Hi

Thanks for the cool library. One thing I am missing is, how to show which config file was used (if any).

Something like this would be great:

$ command subcommand
using configuration file: .test.json
...

It would be possible to do that in the Configuration Option like:

func Configuration(loader ConfigurationLoader, showUsedConfigs bool, paths ...string) Option {
        return OptionFunc(func(k *Kong) error {
                k.loader = loader
                for _, path := range paths {
                        resolver, _ := k.LoadConfig(path)
                        if resolver != nil {
                                if showUsedConfigs {
                                        fmt.Fprintf(k.Stderr, "using configuration file: %s\n", path)
                                }
                                k.resolvers = append(k.resolvers, resolver)
                        }
                }
                return nil
        })
}

But there are two problems:

  1. It would break the existing API.
  2. Is also prints the used configs with the --help flag.

Any interests in adding such a feature?

I could also create a custom config file Resolver. I suppose in that case I would be able to get the value of the help flag and there would be no API break.

Doesn't handle numbers in command names

    Vitess2 vitess2.Commands `cmd hidden:"" help:""`
main: error: unexpected argument vitess2, did you mean "vitess"?

This works:

    NewVitess vitess2.Commands `cmd hidden:"" help:""`

Set short name for default help flag

Hi Alec,

I was wondering if there is a way to set the short name for the default help flag. I was able to do it by adding the following option after forking your project:

// DefaultHelpShort sets the short name for the default help flag.
func DefaultHelpShort(v rune) Option {
	return OptionFunc(func(k *Kong) error {
		k.postBuildOptions = append(k.postBuildOptions, OptionFunc(func(k *Kong) error {
			k.Model.HelpFlag.Short = v
			return nil
		}))
		return nil
	})
}

Is there a better way to do this that already exists? If not, would you consider adding this functionality?

Thank you.

Counter type value.

Is there a simple way to create a counter-like value?

The main idea behind this is to increase verbosity level. For example:

foo -v

or

foo -vvv

Thanks in advance.

Extend Tag for non-core decoders

From @alecthomas:

We need a solution for non-core decoders, plugins, etc. that need configuration through tags.

For example, the "slice" decoder supports an optional sep=',' parameter, which is currently extracted from tags. Two options I can think of, but if you have any ideas I'd be interested:

  1. Extend Tag to support arbitrary key/value lookups for non-core tags. eg. Tag.Get("sep")
  2. Replace Tag entirely with a generic key/value thing. eg. Tag.GetBool("cmd"), Tag.Get("help"), Tag.GetRune("short"), etc.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.