Skip to content

Completions

maxlandon edited this page Jan 8, 2023 · 4 revisions

For those who have followed the documentation in order, we now get to the comfort-of-life aspects of this library: completions and validations. This page focuses on completions.

Note that the completions provided are not those of the cobra library, although you could still use cobra-provided ones would you want to.

As mentionned in the README, the flags library uses the carapace completion engine to generate completions for all components of a given command/tree (commands/flags/arguments).

The package provided to generate completions is github.com/reeflective/flags/gen/completions.

Out of the box, generating completions with this package (and given carapace is installed) will provide cross-shell completions for:

  • commands
  • flags (interspersed, optional ones, and with much attention for their types/requirements)
  • Arguments for flags, when those have a list of valid choices.

Generating the completions

The following call is enough to generate completions for the entire command tree:

package main

import (
    "github.com/reeflective/flags/gen/flags"
    "github.com/reeflective/flags/gen/completions"
)

func main() {
    structData := Root{}

    // Generate the command tree
    rootCmd := flags.Generate(&structData)
        
    // Generate the completions
    // This has added a hidden completion command to our cobra command root.
    comps := completions.Generate(rootCmd, structData, nil)
        
    // Call to this method will hide the hidden cobra completion command.
    comps.Standalone()
                
    // And run the binary, either for execution or completion.
    rootCmd.Execute()
}

Specifying completions with tags

Some basic completers can be specified with struct tags, on either positional or flags fields. See the godoc file for a list of the valid complete tag directives and their effect.

type Create struct {
    Args struct {
      Directory string  `complete:"Dir"`
      Files     []string `complete:"Files"`
    } `positional-args:"yes" required:"true"`
        
    // Flags
    NoFail     bool     `flag:"n no-fail"`
    Extensions []string `short:"p" long:"perms" complete:"FilterExt,json,go`
    APIKey     string   `flag:"k key" desc:"API key to authenticate" required:"yes"`
}

Note that if a given type implements the Completer below, but that a complete tag directive is specified on the field where this type is used, the tagged directive will have precedence. (This might change in the future, with possibility to combine both)

Implementing completers

Overview

Types can also implement completers. Note that for flags (not positionals), this incurs a caveat: the type must implement the flag.Value interface, explained in the flags page. For positional arguments, there are no special requirements; the fields' types just have to implement the following interface:

type Completer interface {
    Complete(ctx carapace.Context) carapace.Action
}

This interface might seem deliberately complex, but is so on purpose:

  • If you import the completions generation package, you will need carapace anyway.
  • It allows to use a single completion signature for all completers, regardless of their complexity.
  • For the most advanced cases (and please be assured that they will be rare), the carapace.Context object gives full access to the current command line words, split-args, current prefix, etc. In 95% of cases, however, you won't need to use it, even when providing completion for slices/maps.

The following is a type implementing the completer. It is used in the example binary as a positional argument in several commands:

func (p *Host) Complete(ctx carapace.Context) carapace.Action {
	action := carapace.ActionStyledValuesDescribed(
		"192.168.1.1", "A first ip address", style.BgBlue,
		"192.168.3.12", "a second address", style.BrightGreen,
		"10.203.23.45", "and a third one", style.BrightCyan,
		"127.0.0.1", "and a third one", style.BrightCyan,
		"219.293.91.10", "", style.Blue,
	).Tag("IPv4 addresses").Invoke(ctx).Filter(ctx.Args).ToA()
    
	return action
}

A few explanations:

  • Tag("IPv4 addresses") is used by certain shells with grouping support.
  • Invoke(ctx).Filter(ctx.Args) is a safety call to ensure that (as a positional) values cannot be repeated.
  • Both of those calls are not strictly necessary (for instance, the second one is automatic when completing flags with array/map containers.)

Please see the relevant carapace documentation for writing completers. This link is also provided in the godoc file for quick access from you editor.

Individual types

You might make use of either individual or slice/map types for your positional slots or flags. Therefore, you could potentially implement the Completer interface either around the list as type, or around the indivual reflect.Elem type of this slice/map.

It is strongly advised to implement the completer around the individual Elem, for several reasons:

  • You can reuse this type more easily in several commands/positional/flags fields.
  • The completion library will automatically adapt its completion behavior when Elem is part of a slice/map.

Array/map types

Therefore, implementing the Completer around a list type should only ever be done if you really wish to provide the entire list completion by yourself, which I personally see absolutely no valid reason for.

The completions package, regardless, will always:

  • Adapt its completion logic based where is implemented the completer.
  • Will always use the completer implemented by an array type rather than the one provided by its Elems.