Merge pull request #5454 from jvoorhis/f-autocomplete

Autocomplete for top-level commands
This commit is contained in:
M. Marsh 2018-04-26 11:18:56 -07:00 committed by GitHub
commit 6e2f06ef49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 1636 additions and 24 deletions

View File

@ -10,6 +10,7 @@ go:
- 1.9.x - 1.9.x
- 1.x - 1.x
install: install:
- make deps - make deps

View File

@ -14,13 +14,15 @@ import (
"github.com/hashicorp/packer/helper/enumflag" "github.com/hashicorp/packer/helper/enumflag"
"github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template" "github.com/hashicorp/packer/template"
"github.com/posener/complete"
) )
type BuildCommand struct { type BuildCommand struct {
Meta Meta
} }
func (c BuildCommand) Run(args []string) int { func (c *BuildCommand) Run(args []string) int {
var cfgColor, cfgDebug, cfgForce, cfgParallel bool var cfgColor, cfgDebug, cfgForce, cfgParallel bool
var cfgOnError string var cfgOnError string
flags := c.Meta.FlagSet("build", FlagSetBuildFilter|FlagSetVars) flags := c.Meta.FlagSet("build", FlagSetBuildFilter|FlagSetVars)
@ -283,7 +285,7 @@ func (c BuildCommand) Run(args []string) int {
return 0 return 0
} }
func (BuildCommand) Help() string { func (*BuildCommand) Help() string {
helpText := ` helpText := `
Usage: packer build [options] TEMPLATE Usage: packer build [options] TEMPLATE
@ -307,6 +309,25 @@ Options:
return strings.TrimSpace(helpText) return strings.TrimSpace(helpText)
} }
func (BuildCommand) Synopsis() string { func (*BuildCommand) Synopsis() string {
return "build image(s) from template" return "build image(s) from template"
} }
func (*BuildCommand) AutocompleteArgs() complete.Predictor {
return complete.PredictNothing
}
func (*BuildCommand) AutocompleteFlags() complete.Flags {
return complete.Flags{
"-color": complete.PredictNothing,
"-debug": complete.PredictNothing,
"-except": complete.PredictNothing,
"-only": complete.PredictNothing,
"-force": complete.PredictNothing,
"-machine-readable": complete.PredictNothing,
"-on-error": complete.PredictNothing,
"-parallel": complete.PredictNothing,
"-var": complete.PredictNothing,
"-var-file": complete.PredictNothing,
}
}

View File

@ -10,6 +10,8 @@ import (
"github.com/hashicorp/packer/fix" "github.com/hashicorp/packer/fix"
"github.com/hashicorp/packer/template" "github.com/hashicorp/packer/template"
"github.com/posener/complete"
) )
type FixCommand struct { type FixCommand struct {
@ -140,3 +142,13 @@ Options:
func (c *FixCommand) Synopsis() string { func (c *FixCommand) Synopsis() string {
return "fixes templates from old versions of packer" return "fixes templates from old versions of packer"
} }
func (c *FixCommand) AutocompleteArgs() complete.Predictor {
return complete.PredictNothing
}
func (c *FixCommand) AutocompleteFlags() complete.Flags {
return complete.Flags{
"-validate": complete.PredictNothing,
}
}

View File

@ -6,6 +6,8 @@ import (
"strings" "strings"
"github.com/hashicorp/packer/template" "github.com/hashicorp/packer/template"
"github.com/posener/complete"
) )
type InspectCommand struct { type InspectCommand struct {
@ -160,3 +162,13 @@ Options:
func (c *InspectCommand) Synopsis() string { func (c *InspectCommand) Synopsis() string {
return "see components of a template" return "see components of a template"
} }
func (c *InspectCommand) AutocompleteArgs() complete.Predictor {
return complete.PredictNothing
}
func (c *InspectCommand) AutocompleteFlags() complete.Flags {
return complete.Flags{
"-machine-readable": complete.PredictNothing,
}
}

View File

@ -15,6 +15,8 @@ import (
"github.com/hashicorp/packer/helper/flag-kv" "github.com/hashicorp/packer/helper/flag-kv"
"github.com/hashicorp/packer/helper/flag-slice" "github.com/hashicorp/packer/helper/flag-slice"
"github.com/hashicorp/packer/template" "github.com/hashicorp/packer/template"
"github.com/posener/complete"
) )
// archiveTemplateEntry is the name the template always takes within the slug. // archiveTemplateEntry is the name the template always takes within the slug.
@ -334,6 +336,20 @@ func (*PushCommand) Synopsis() string {
return "push a template and supporting files to a Packer build service" return "push a template and supporting files to a Packer build service"
} }
func (*PushCommand) AutocompleteArgs() complete.Predictor {
return complete.PredictNothing
}
func (*PushCommand) AutocompleteFlags() complete.Flags {
return complete.Flags{
"-name": complete.PredictNothing,
"-token": complete.PredictNothing,
"-sensitive": complete.PredictNothing,
"-var": complete.PredictNothing,
"-var-file": complete.PredictNothing,
}
}
func (c *PushCommand) upload( func (c *PushCommand) upload(
r *archive.Archive, opts *uploadOpts) (<-chan struct{}, <-chan error, error) { r *archive.Archive, opts *uploadOpts) (<-chan struct{}, <-chan error, error) {
if c.uploadFn != nil { if c.uploadFn != nil {

View File

@ -7,6 +7,8 @@ import (
"github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template" "github.com/hashicorp/packer/template"
"github.com/posener/complete"
) )
type ValidateCommand struct { type ValidateCommand struct {
@ -136,3 +138,17 @@ Options:
func (*ValidateCommand) Synopsis() string { func (*ValidateCommand) Synopsis() string {
return "check that a template is valid" return "check that a template is valid"
} }
func (*ValidateCommand) AutocompleteArgs() complete.Predictor {
return complete.PredictNothing
}
func (*ValidateCommand) AutocompleteFlags() complete.Flags {
return complete.Flags{
"-syntax-only": complete.PredictNothing,
"-except": complete.PredictNothing,
"-only": complete.PredictNothing,
"-var": complete.PredictNothing,
"-var-file": complete.PredictNothing,
}
}

View File

@ -210,9 +210,11 @@ func wrappedMain() int {
cli := &cli.CLI{ cli := &cli.CLI{
Args: args, Args: args,
Autocomplete: true,
Commands: Commands, Commands: Commands,
HelpFunc: excludeHelpFunc(Commands, []string{"plugin"}), HelpFunc: excludeHelpFunc(Commands, []string{"plugin"}),
HelpWriter: os.Stdout, HelpWriter: os.Stdout,
Name: "packer",
Version: version.Version, Version: version.Version,
} }

View File

@ -18,6 +18,9 @@ cli is the library that powers the CLI for
* Optional support for default subcommands so `cli` does something * Optional support for default subcommands so `cli` does something
other than error. other than error.
* Support for shell autocompletion of subcommands, flags, and arguments
with callbacks in Go. You don't need to write any shell code.
* Automatic help generation for listing subcommands * Automatic help generation for listing subcommands
* Automatic help flag recognition of `-h`, `--help`, etc. * Automatic help flag recognition of `-h`, `--help`, etc.

43
vendor/github.com/mitchellh/cli/autocomplete.go generated vendored Normal file
View File

@ -0,0 +1,43 @@
package cli
import (
"github.com/posener/complete/cmd/install"
)
// autocompleteInstaller is an interface to be implemented to perform the
// autocomplete installation and uninstallation with a CLI.
//
// This interface is not exported because it only exists for unit tests
// to be able to test that the installation is called properly.
type autocompleteInstaller interface {
Install(string) error
Uninstall(string) error
}
// realAutocompleteInstaller uses the real install package to do the
// install/uninstall.
type realAutocompleteInstaller struct{}
func (i *realAutocompleteInstaller) Install(cmd string) error {
return install.Install(cmd)
}
func (i *realAutocompleteInstaller) Uninstall(cmd string) error {
return install.Uninstall(cmd)
}
// mockAutocompleteInstaller is used for tests to record the install/uninstall.
type mockAutocompleteInstaller struct {
InstallCalled bool
UninstallCalled bool
}
func (i *mockAutocompleteInstaller) Install(cmd string) error {
i.InstallCalled = true
return nil
}
func (i *mockAutocompleteInstaller) Uninstall(cmd string) error {
i.UninstallCalled = true
return nil
}

View File

@ -11,6 +11,7 @@ import (
"text/template" "text/template"
"github.com/armon/go-radix" "github.com/armon/go-radix"
"github.com/posener/complete"
) )
// CLI contains the state necessary to run subcommands and parse the // CLI contains the state necessary to run subcommands and parse the
@ -58,14 +59,56 @@ type CLI struct {
// For example, if the key is "foo bar", then to access it our CLI // For example, if the key is "foo bar", then to access it our CLI
// must be accessed with "./cli foo bar". See the docs for CLI for // must be accessed with "./cli foo bar". See the docs for CLI for
// notes on how this changes some other behavior of the CLI as well. // notes on how this changes some other behavior of the CLI as well.
//
// The factory should be as cheap as possible, ideally only allocating
// a struct. The factory may be called multiple times in the course
// of a command execution and certain events such as help require the
// instantiation of all commands. Expensive initialization should be
// deferred to function calls within the interface implementation.
Commands map[string]CommandFactory Commands map[string]CommandFactory
// HiddenCommands is a list of commands that are "hidden". Hidden
// commands are not given to the help function callback and do not
// show up in autocomplete. The values in the slice should be equivalent
// to the keys in the command map.
HiddenCommands []string
// Name defines the name of the CLI. // Name defines the name of the CLI.
Name string Name string
// Version of the CLI. // Version of the CLI.
Version string Version string
// Autocomplete enables or disables subcommand auto-completion support.
// This is enabled by default when NewCLI is called. Otherwise, this
// must enabled explicitly.
//
// Autocomplete requires the "Name" option to be set on CLI. This name
// should be set exactly to the binary name that is autocompleted.
//
// Autocompletion is supported via the github.com/posener/complete
// library. This library supports both bash and zsh. To add support
// for other shells, please see that library.
//
// AutocompleteInstall and AutocompleteUninstall are the global flag
// names for installing and uninstalling the autocompletion handlers
// for the user's shell. The flag should omit the hyphen(s) in front of
// the value. Both single and double hyphens will automatically be supported
// for the flag name. These default to `autocomplete-install` and
// `autocomplete-uninstall` respectively.
//
// AutocompleteNoDefaultFlags is a boolean which controls if the default auto-
// complete flags like -help and -version are added to the output.
//
// AutocompleteGlobalFlags are a mapping of global flags for
// autocompletion. The help and version flags are automatically added.
Autocomplete bool
AutocompleteInstall string
AutocompleteUninstall string
AutocompleteNoDefaultFlags bool
AutocompleteGlobalFlags complete.Flags
autocompleteInstaller autocompleteInstaller // For tests
// HelpFunc and HelpWriter are used to output help information, if // HelpFunc and HelpWriter are used to output help information, if
// requested. // requested.
// //
@ -78,15 +121,24 @@ type CLI struct {
HelpFunc HelpFunc HelpFunc HelpFunc
HelpWriter io.Writer HelpWriter io.Writer
//---------------------------------------------------------------
// Internal fields set automatically
once sync.Once once sync.Once
autocomplete *complete.Complete
commandTree *radix.Tree commandTree *radix.Tree
commandNested bool commandNested bool
isHelp bool commandHidden map[string]struct{}
subcommand string subcommand string
subcommandArgs []string subcommandArgs []string
topFlags []string topFlags []string
// These are true when special global flags are set. We can/should
// probably use a bitset for this one day.
isHelp bool
isVersion bool isVersion bool
isAutocompleteInstall bool
isAutocompleteUninstall bool
} }
// NewClI returns a new CLI instance with sensible defaults. // NewClI returns a new CLI instance with sensible defaults.
@ -95,6 +147,7 @@ func NewCLI(app, version string) *CLI {
Name: app, Name: app,
Version: version, Version: version,
HelpFunc: BasicHelpFunc(app), HelpFunc: BasicHelpFunc(app),
Autocomplete: true,
} }
} }
@ -117,6 +170,14 @@ func (c *CLI) IsVersion() bool {
func (c *CLI) Run() (int, error) { func (c *CLI) Run() (int, error) {
c.once.Do(c.init) c.once.Do(c.init)
// If this is a autocompletion request, satisfy it. This must be called
// first before anything else since its possible to be autocompleting
// -help or -version or other flags and we want to show completions
// and not actually write the help or version.
if c.Autocomplete && c.autocomplete.Complete() {
return 0, nil
}
// Just show the version and exit if instructed. // Just show the version and exit if instructed.
if c.IsVersion() && c.Version != "" { if c.IsVersion() && c.Version != "" {
c.HelpWriter.Write([]byte(c.Version + "\n")) c.HelpWriter.Write([]byte(c.Version + "\n"))
@ -125,16 +186,50 @@ func (c *CLI) Run() (int, error) {
// Just print the help when only '-h' or '--help' is passed. // Just print the help when only '-h' or '--help' is passed.
if c.IsHelp() && c.Subcommand() == "" { if c.IsHelp() && c.Subcommand() == "" {
c.HelpWriter.Write([]byte(c.HelpFunc(c.Commands) + "\n")) c.HelpWriter.Write([]byte(c.HelpFunc(c.helpCommands(c.Subcommand())) + "\n"))
return 0, nil return 0, nil
} }
// If we're attempting to install or uninstall autocomplete then handle
if c.Autocomplete {
// Autocomplete requires the "Name" to be set so that we know what
// command to setup the autocomplete on.
if c.Name == "" {
return 1, fmt.Errorf(
"internal error: CLI.Name must be specified for autocomplete to work")
}
// If both install and uninstall flags are specified, then error
if c.isAutocompleteInstall && c.isAutocompleteUninstall {
return 1, fmt.Errorf(
"Either the autocomplete install or uninstall flag may " +
"be specified, but not both.")
}
// If the install flag is specified, perform the install or uninstall
if c.isAutocompleteInstall {
if err := c.autocompleteInstaller.Install(c.Name); err != nil {
return 1, err
}
return 0, nil
}
if c.isAutocompleteUninstall {
if err := c.autocompleteInstaller.Uninstall(c.Name); err != nil {
return 1, err
}
return 0, nil
}
}
// Attempt to get the factory function for creating the command // Attempt to get the factory function for creating the command
// implementation. If the command is invalid or blank, it is an error. // implementation. If the command is invalid or blank, it is an error.
raw, ok := c.commandTree.Get(c.Subcommand()) raw, ok := c.commandTree.Get(c.Subcommand())
if !ok { if !ok {
c.HelpWriter.Write([]byte(c.HelpFunc(c.helpCommands(c.subcommandParent())) + "\n")) c.HelpWriter.Write([]byte(c.HelpFunc(c.helpCommands(c.subcommandParent())) + "\n"))
return 1, nil return 127, nil
} }
command, err := raw.(CommandFactory)() command, err := raw.(CommandFactory)()
@ -216,6 +311,14 @@ func (c *CLI) init() {
c.HelpWriter = os.Stderr c.HelpWriter = os.Stderr
} }
// Build our hidden commands
if len(c.HiddenCommands) > 0 {
c.commandHidden = make(map[string]struct{})
for _, h := range c.HiddenCommands {
c.commandHidden[h] = struct{}{}
}
}
// Build our command tree // Build our command tree
c.commandTree = radix.New() c.commandTree = radix.New()
c.commandNested = false c.commandNested = false
@ -268,10 +371,113 @@ func (c *CLI) init() {
} }
} }
// Setup autocomplete if we have it enabled. We have to do this after
// the command tree is setup so we can use the radix tree to easily find
// all subcommands.
if c.Autocomplete {
c.initAutocomplete()
}
// Process the args // Process the args
c.processArgs() c.processArgs()
} }
func (c *CLI) initAutocomplete() {
if c.AutocompleteInstall == "" {
c.AutocompleteInstall = defaultAutocompleteInstall
}
if c.AutocompleteUninstall == "" {
c.AutocompleteUninstall = defaultAutocompleteUninstall
}
if c.autocompleteInstaller == nil {
c.autocompleteInstaller = &realAutocompleteInstaller{}
}
// Build the root command
cmd := c.initAutocompleteSub("")
// For the root, we add the global flags to the "Flags". This way
// they don't show up on every command.
if !c.AutocompleteNoDefaultFlags {
cmd.Flags = map[string]complete.Predictor{
"-" + c.AutocompleteInstall: complete.PredictNothing,
"-" + c.AutocompleteUninstall: complete.PredictNothing,
"-help": complete.PredictNothing,
"-version": complete.PredictNothing,
}
}
cmd.GlobalFlags = c.AutocompleteGlobalFlags
c.autocomplete = complete.New(c.Name, cmd)
}
// initAutocompleteSub creates the complete.Command for a subcommand with
// the given prefix. This will continue recursively for all subcommands.
// The prefix "" (empty string) can be used for the root command.
func (c *CLI) initAutocompleteSub(prefix string) complete.Command {
var cmd complete.Command
walkFn := func(k string, raw interface{}) bool {
// Keep track of the full key so that we can nest further if necessary
fullKey := k
if len(prefix) > 0 {
// If we have a prefix, trim the prefix + 1 (for the space)
// Example: turns "sub one" to "one" with prefix "sub"
k = k[len(prefix)+1:]
}
if idx := strings.Index(k, " "); idx >= 0 {
// If there is a space, we trim up to the space. This turns
// "sub sub2 sub3" into "sub". The prefix trim above will
// trim our current depth properly.
k = k[:idx]
}
if _, ok := cmd.Sub[k]; ok {
// If we already tracked this subcommand then ignore
return false
}
// If the command is hidden, don't record it at all
if _, ok := c.commandHidden[fullKey]; ok {
return false
}
if cmd.Sub == nil {
cmd.Sub = complete.Commands(make(map[string]complete.Command))
}
subCmd := c.initAutocompleteSub(fullKey)
// Instantiate the command so that we can check if the command is
// a CommandAutocomplete implementation. If there is an error
// creating the command, we just ignore it since that will be caught
// later.
impl, err := raw.(CommandFactory)()
if err != nil {
impl = nil
}
// Check if it implements ComandAutocomplete. If so, setup the autocomplete
if c, ok := impl.(CommandAutocomplete); ok {
subCmd.Args = c.AutocompleteArgs()
subCmd.Flags = c.AutocompleteFlags()
}
cmd.Sub[k] = subCmd
return false
}
walkPrefix := prefix
if walkPrefix != "" {
walkPrefix += " "
}
c.commandTree.WalkPrefix(walkPrefix, walkFn)
return cmd
}
func (c *CLI) commandHelp(command Command) { func (c *CLI) commandHelp(command Command) {
// Get the template to use // Get the template to use
tpl := strings.TrimSpace(defaultHelpTemplate) tpl := strings.TrimSpace(defaultHelpTemplate)
@ -386,6 +592,11 @@ func (c *CLI) helpCommands(prefix string) map[string]CommandFactory {
panic("not found: " + k) panic("not found: " + k)
} }
// If this is a hidden command, don't show it
if _, ok := c.commandHidden[k]; ok {
continue
}
result[k] = raw.(CommandFactory) result[k] = raw.(CommandFactory)
} }
@ -404,6 +615,19 @@ func (c *CLI) processArgs() {
continue continue
} }
// Check for autocomplete flags
if c.Autocomplete {
if arg == "-"+c.AutocompleteInstall || arg == "--"+c.AutocompleteInstall {
c.isAutocompleteInstall = true
continue
}
if arg == "-"+c.AutocompleteUninstall || arg == "--"+c.AutocompleteUninstall {
c.isAutocompleteUninstall = true
continue
}
}
if c.subcommand == "" { if c.subcommand == "" {
// Check for version flags if not in a subcommand. // Check for version flags if not in a subcommand.
if arg == "-v" || arg == "-version" || arg == "--version" { if arg == "-v" || arg == "-version" || arg == "--version" {
@ -456,6 +680,11 @@ func (c *CLI) processArgs() {
} }
} }
// defaultAutocompleteInstall and defaultAutocompleteUninstall are the
// default values for the autocomplete install and uninstall flags.
const defaultAutocompleteInstall = "autocomplete-install"
const defaultAutocompleteUninstall = "autocomplete-uninstall"
const defaultHelpTemplate = ` const defaultHelpTemplate = `
{{.Help}}{{if gt (len .Subcommands) 0}} {{.Help}}{{if gt (len .Subcommands) 0}}

View File

@ -1,5 +1,9 @@
package cli package cli
import (
"github.com/posener/complete"
)
const ( const (
// RunResultHelp is a value that can be returned from Run to signal // RunResultHelp is a value that can be returned from Run to signal
// to the CLI to render the help output. // to the CLI to render the help output.
@ -26,6 +30,22 @@ type Command interface {
Synopsis() string Synopsis() string
} }
// CommandAutocomplete is an extension of Command that enables fine-grained
// autocompletion. Subcommand autocompletion will work even if this interface
// is not implemented. By implementing this interface, more advanced
// autocompletion is enabled.
type CommandAutocomplete interface {
// AutocompleteArgs returns the argument predictor for this command.
// If argument completion is not supported, this should return
// complete.PredictNothing.
AutocompleteArgs() complete.Predictor
// AutocompleteFlags returns a mapping of supported flags and autocomplete
// options for this command. The map key for the Flags map should be the
// complete flag such as "-foo" or "--foo".
AutocompleteFlags() complete.Flags
}
// CommandHelpTemplate is an extension of Command that also has a function // CommandHelpTemplate is an extension of Command that also has a function
// for returning a template for the help rather than the help itself. In // for returning a template for the help rather than the help itself. In
// this scenario, both Help and HelpTemplate should be implemented. // this scenario, both Help and HelpTemplate should be implemented.

View File

@ -1,5 +1,9 @@
package cli package cli
import (
"github.com/posener/complete"
)
// MockCommand is an implementation of Command that can be used for tests. // MockCommand is an implementation of Command that can be used for tests.
// It is publicly exported from this package in case you want to use it // It is publicly exported from this package in case you want to use it
// externally. // externally.
@ -29,6 +33,23 @@ func (c *MockCommand) Synopsis() string {
return c.SynopsisText return c.SynopsisText
} }
// MockCommandAutocomplete is an implementation of CommandAutocomplete.
type MockCommandAutocomplete struct {
MockCommand
// Settable
AutocompleteArgsValue complete.Predictor
AutocompleteFlagsValue complete.Flags
}
func (c *MockCommandAutocomplete) AutocompleteArgs() complete.Predictor {
return c.AutocompleteArgsValue
}
func (c *MockCommandAutocomplete) AutocompleteFlags() complete.Flags {
return c.AutocompleteFlagsValue
}
// MockCommandHelpTemplate is an implementation of CommandHelpTemplate. // MockCommandHelpTemplate is an implementation of CommandHelpTemplate.
type MockCommandHelpTemplate struct { type MockCommandHelpTemplate struct {
MockCommand MockCommand

View File

@ -7,12 +7,25 @@ import (
"sync" "sync"
) )
// MockUi is a mock UI that is used for tests and is exported publicly for // NewMockUi returns a fully initialized MockUi instance
// use in external tests if needed as well. // which is safe for concurrent use.
func NewMockUi() *MockUi {
m := new(MockUi)
m.once.Do(m.init)
return m
}
// MockUi is a mock UI that is used for tests and is exported publicly
// for use in external tests if needed as well. Do not instantite this
// directly since the buffers will be initialized on the first write. If
// there is no write then you will get a nil panic. Please use the
// NewMockUi() constructor function instead. You can fix your code with
//
// sed -i -e 's/new(cli.MockUi)/cli.NewMockUi()/g' *_test.go
type MockUi struct { type MockUi struct {
InputReader io.Reader InputReader io.Reader
ErrorWriter *bytes.Buffer ErrorWriter *syncBuffer
OutputWriter *bytes.Buffer OutputWriter *syncBuffer
once sync.Once once sync.Once
} }
@ -59,6 +72,40 @@ func (u *MockUi) Warn(message string) {
} }
func (u *MockUi) init() { func (u *MockUi) init() {
u.ErrorWriter = new(bytes.Buffer) u.ErrorWriter = new(syncBuffer)
u.OutputWriter = new(bytes.Buffer) u.OutputWriter = new(syncBuffer)
}
type syncBuffer struct {
sync.RWMutex
b bytes.Buffer
}
func (b *syncBuffer) Write(data []byte) (int, error) {
b.Lock()
defer b.Unlock()
return b.b.Write(data)
}
func (b *syncBuffer) Read(data []byte) (int, error) {
b.RLock()
defer b.RUnlock()
return b.b.Read(data)
}
func (b *syncBuffer) Reset() {
b.Lock()
b.b.Reset()
b.Unlock()
}
func (b *syncBuffer) String() string {
return string(b.Bytes())
}
func (b *syncBuffer) Bytes() []byte {
b.RLock()
data := b.b.Bytes()
b.RUnlock()
return data
} }

21
vendor/github.com/posener/complete/LICENSE.txt generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2017 Eyal Posener
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.

75
vendor/github.com/posener/complete/args.go generated vendored Normal file
View File

@ -0,0 +1,75 @@
package complete
import (
"os"
"path/filepath"
)
// Args describes command line arguments
type Args struct {
// All lists of all arguments in command line (not including the command itself)
All []string
// Completed lists of all completed arguments in command line,
// If the last one is still being typed - no space after it,
// it won't appear in this list of arguments.
Completed []string
// Last argument in command line, the one being typed, if the last
// character in the command line is a space, this argument will be empty,
// otherwise this would be the last word.
Last string
// LastCompleted is the last argument that was fully typed.
// If the last character in the command line is space, this would be the
// last word, otherwise, it would be the word before that.
LastCompleted string
}
// Directory gives the directory of the current written
// last argument if it represents a file name being written.
// in case that it is not, we fall back to the current directory.
func (a Args) Directory() string {
if info, err := os.Stat(a.Last); err == nil && info.IsDir() {
return fixPathForm(a.Last, a.Last)
}
dir := filepath.Dir(a.Last)
if info, err := os.Stat(dir); err != nil || !info.IsDir() {
return "./"
}
return fixPathForm(a.Last, dir)
}
func newArgs(line []string) Args {
completed := removeLast(line[1:])
return Args{
All: line[1:],
Completed: completed,
Last: last(line),
LastCompleted: last(completed),
}
}
func (a Args) from(i int) Args {
if i > len(a.All) {
i = len(a.All)
}
a.All = a.All[i:]
if i > len(a.Completed) {
i = len(a.Completed)
}
a.Completed = a.Completed[i:]
return a
}
func removeLast(a []string) []string {
if len(a) > 0 {
return a[:len(a)-1]
}
return a
}
func last(args []string) (last string) {
if len(args) > 0 {
last = args[len(args)-1]
}
return
}

128
vendor/github.com/posener/complete/cmd/cmd.go generated vendored Normal file
View File

@ -0,0 +1,128 @@
// Package cmd used for command line options for the complete tool
package cmd
import (
"errors"
"flag"
"fmt"
"os"
"strings"
"github.com/posener/complete/cmd/install"
)
// CLI for command line
type CLI struct {
Name string
InstallName string
UninstallName string
install bool
uninstall bool
yes bool
}
const (
defaultInstallName = "install"
defaultUninstallName = "uninstall"
)
// Run is used when running complete in command line mode.
// this is used when the complete is not completing words, but to
// install it or uninstall it.
func (f *CLI) Run() bool {
err := f.validate()
if err != nil {
os.Stderr.WriteString(err.Error() + "\n")
os.Exit(1)
}
switch {
case f.install:
f.prompt()
err = install.Install(f.Name)
case f.uninstall:
f.prompt()
err = install.Uninstall(f.Name)
default:
// non of the action flags matched,
// returning false should make the real program execute
return false
}
if err != nil {
fmt.Printf("%s failed! %s\n", f.action(), err)
os.Exit(3)
}
fmt.Println("Done!")
return true
}
// prompt use for approval
// exit if approval was not given
func (f *CLI) prompt() {
defer fmt.Println(f.action() + "ing...")
if f.yes {
return
}
fmt.Printf("%s completion for %s? ", f.action(), f.Name)
var answer string
fmt.Scanln(&answer)
switch strings.ToLower(answer) {
case "y", "yes":
return
default:
fmt.Println("Cancelling...")
os.Exit(1)
}
}
// AddFlags adds the CLI flags to the flag set.
// If flags is nil, the default command line flags will be taken.
// Pass non-empty strings as installName and uninstallName to override the default
// flag names.
func (f *CLI) AddFlags(flags *flag.FlagSet) {
if flags == nil {
flags = flag.CommandLine
}
if f.InstallName == "" {
f.InstallName = defaultInstallName
}
if f.UninstallName == "" {
f.UninstallName = defaultUninstallName
}
if flags.Lookup(f.InstallName) == nil {
flags.BoolVar(&f.install, f.InstallName, false,
fmt.Sprintf("Install completion for %s command", f.Name))
}
if flags.Lookup(f.UninstallName) == nil {
flags.BoolVar(&f.uninstall, f.UninstallName, false,
fmt.Sprintf("Uninstall completion for %s command", f.Name))
}
if flags.Lookup("y") == nil {
flags.BoolVar(&f.yes, "y", false, "Don't prompt user for typing 'yes'")
}
}
// validate the CLI
func (f *CLI) validate() error {
if f.install && f.uninstall {
return errors.New("Install and uninstall are mutually exclusive")
}
return nil
}
// action name according to the CLI values.
func (f *CLI) action() string {
switch {
case f.install:
return "Install"
case f.uninstall:
return "Uninstall"
default:
return "unknown"
}
}

32
vendor/github.com/posener/complete/cmd/install/bash.go generated vendored Normal file
View File

@ -0,0 +1,32 @@
package install
import "fmt"
// (un)install in bash
// basically adds/remove from .bashrc:
//
// complete -C </path/to/completion/command> <command>
type bash struct {
rc string
}
func (b bash) Install(cmd, bin string) error {
completeCmd := b.cmd(cmd, bin)
if lineInFile(b.rc, completeCmd) {
return fmt.Errorf("already installed in %s", b.rc)
}
return appendToFile(b.rc, completeCmd)
}
func (b bash) Uninstall(cmd, bin string) error {
completeCmd := b.cmd(cmd, bin)
if !lineInFile(b.rc, completeCmd) {
return fmt.Errorf("does not installed in %s", b.rc)
}
return removeFromFile(b.rc, completeCmd)
}
func (bash) cmd(cmd, bin string) string {
return fmt.Sprintf("complete -C %s %s", bin, cmd)
}

View File

@ -0,0 +1,92 @@
package install
import (
"errors"
"os"
"os/user"
"path/filepath"
"github.com/hashicorp/go-multierror"
)
type installer interface {
Install(cmd, bin string) error
Uninstall(cmd, bin string) error
}
// Install complete command given:
// cmd: is the command name
func Install(cmd string) error {
is := installers()
if len(is) == 0 {
return errors.New("Did not find any shells to install")
}
bin, err := getBinaryPath()
if err != nil {
return err
}
for _, i := range is {
errI := i.Install(cmd, bin)
if errI != nil {
err = multierror.Append(err, errI)
}
}
return err
}
// Uninstall complete command given:
// cmd: is the command name
func Uninstall(cmd string) error {
is := installers()
if len(is) == 0 {
return errors.New("Did not find any shells to uninstall")
}
bin, err := getBinaryPath()
if err != nil {
return err
}
for _, i := range is {
errI := i.Uninstall(cmd, bin)
if errI != nil {
multierror.Append(err, errI)
}
}
return err
}
func installers() (i []installer) {
for _, rc := range [...]string{".bashrc", ".bash_profile"} {
if f := rcFile(rc); f != "" {
i = append(i, bash{f})
break
}
}
if f := rcFile(".zshrc"); f != "" {
i = append(i, zsh{f})
}
return
}
func getBinaryPath() (string, error) {
bin, err := os.Executable()
if err != nil {
return "", err
}
return filepath.Abs(bin)
}
func rcFile(name string) string {
u, err := user.Current()
if err != nil {
return ""
}
path := filepath.Join(u.HomeDir, name)
if _, err := os.Stat(path); err != nil {
return ""
}
return path
}

118
vendor/github.com/posener/complete/cmd/install/utils.go generated vendored Normal file
View File

@ -0,0 +1,118 @@
package install
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"os"
)
func lineInFile(name string, lookFor string) bool {
f, err := os.Open(name)
if err != nil {
return false
}
defer f.Close()
r := bufio.NewReader(f)
prefix := []byte{}
for {
line, isPrefix, err := r.ReadLine()
if err == io.EOF {
return false
}
if err != nil {
return false
}
if isPrefix {
prefix = append(prefix, line...)
continue
}
line = append(prefix, line...)
if string(line) == lookFor {
return true
}
prefix = prefix[:0]
}
}
func appendToFile(name string, content string) error {
f, err := os.OpenFile(name, os.O_RDWR|os.O_APPEND, 0)
if err != nil {
return err
}
defer f.Close()
_, err = f.WriteString(fmt.Sprintf("\n%s\n", content))
return err
}
func removeFromFile(name string, content string) error {
backup := name + ".bck"
err := copyFile(name, backup)
if err != nil {
return err
}
temp, err := removeContentToTempFile(name, content)
if err != nil {
return err
}
err = copyFile(temp, name)
if err != nil {
return err
}
return os.Remove(backup)
}
func removeContentToTempFile(name, content string) (string, error) {
rf, err := os.Open(name)
if err != nil {
return "", err
}
defer rf.Close()
wf, err := ioutil.TempFile("/tmp", "complete-")
if err != nil {
return "", err
}
defer wf.Close()
r := bufio.NewReader(rf)
prefix := []byte{}
for {
line, isPrefix, err := r.ReadLine()
if err == io.EOF {
break
}
if err != nil {
return "", err
}
if isPrefix {
prefix = append(prefix, line...)
continue
}
line = append(prefix, line...)
str := string(line)
if str == content {
continue
}
wf.WriteString(str + "\n")
prefix = prefix[:0]
}
return wf.Name(), nil
}
func copyFile(src string, dst string) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, in)
return err
}

39
vendor/github.com/posener/complete/cmd/install/zsh.go generated vendored Normal file
View File

@ -0,0 +1,39 @@
package install
import "fmt"
// (un)install in zsh
// basically adds/remove from .zshrc:
//
// autoload -U +X bashcompinit && bashcompinit"
// complete -C </path/to/completion/command> <command>
type zsh struct {
rc string
}
func (z zsh) Install(cmd, bin string) error {
completeCmd := z.cmd(cmd, bin)
if lineInFile(z.rc, completeCmd) {
return fmt.Errorf("already installed in %s", z.rc)
}
bashCompInit := "autoload -U +X bashcompinit && bashcompinit"
if !lineInFile(z.rc, bashCompInit) {
completeCmd = bashCompInit + "\n" + completeCmd
}
return appendToFile(z.rc, completeCmd)
}
func (z zsh) Uninstall(cmd, bin string) error {
completeCmd := z.cmd(cmd, bin)
if !lineInFile(z.rc, completeCmd) {
return fmt.Errorf("does not installed in %s", z.rc)
}
return removeFromFile(z.rc, completeCmd)
}
func (zsh) cmd(cmd, bin string) string {
return fmt.Sprintf("complete -o nospace -C %s %s", bin, cmd)
}

118
vendor/github.com/posener/complete/command.go generated vendored Normal file
View File

@ -0,0 +1,118 @@
package complete
import "github.com/posener/complete/match"
// Command represents a command line
// It holds the data that enables auto completion of command line
// Command can also be a sub command.
type Command struct {
// Sub is map of sub commands of the current command
// The key refer to the sub command name, and the value is it's
// Command descriptive struct.
Sub Commands
// Flags is a map of flags that the command accepts.
// The key is the flag name, and the value is it's predictions.
Flags Flags
// GlobalFlags is a map of flags that the command accepts.
// Global flags that can appear also after a sub command.
GlobalFlags Flags
// Args are extra arguments that the command accepts, those who are
// given without any flag before.
Args Predictor
}
// Predict returns all possible predictions for args according to the command struct
func (c *Command) Predict(a Args) (predictions []string) {
predictions, _ = c.predict(a)
return
}
// Commands is the type of Sub member, it maps a command name to a command struct
type Commands map[string]Command
// Predict completion of sub command names names according to command line arguments
func (c Commands) Predict(a Args) (prediction []string) {
for sub := range c {
if match.Prefix(sub, a.Last) {
prediction = append(prediction, sub)
}
}
return
}
// Flags is the type Flags of the Flags member, it maps a flag name to the flag predictions.
type Flags map[string]Predictor
// Predict completion of flags names according to command line arguments
func (f Flags) Predict(a Args) (prediction []string) {
for flag := range f {
// If the flag starts with a hyphen, we avoid emitting the prediction
// unless the last typed arg contains a hyphen as well.
flagHyphenStart := len(flag) != 0 && flag[0] == '-'
lastHyphenStart := len(a.Last) != 0 && a.Last[0] == '-'
if flagHyphenStart && !lastHyphenStart {
continue
}
if match.Prefix(flag, a.Last) {
prediction = append(prediction, flag)
}
}
return
}
// predict options
// only is set to true if no more options are allowed to be returned
// those are in cases of special flag that has specific completion arguments,
// and other flags or sub commands can't come after it.
func (c *Command) predict(a Args) (options []string, only bool) {
// search sub commands for predictions first
subCommandFound := false
for i, arg := range a.Completed {
if cmd, ok := c.Sub[arg]; ok {
subCommandFound = true
// recursive call for sub command
options, only = cmd.predict(a.from(i))
if only {
return
}
// We matched so stop searching. Continuing to search can accidentally
// match a subcommand with current set of commands, see issue #46.
break
}
}
// if last completed word is a global flag that we need to complete
if predictor, ok := c.GlobalFlags[a.LastCompleted]; ok && predictor != nil {
Log("Predicting according to global flag %s", a.LastCompleted)
return predictor.Predict(a), true
}
options = append(options, c.GlobalFlags.Predict(a)...)
// if a sub command was entered, we won't add the parent command
// completions and we return here.
if subCommandFound {
return
}
// if last completed word is a command flag that we need to complete
if predictor, ok := c.Flags[a.LastCompleted]; ok && predictor != nil {
Log("Predicting according to flag %s", a.LastCompleted)
return predictor.Predict(a), true
}
options = append(options, c.Sub.Predict(a)...)
options = append(options, c.Flags.Predict(a)...)
if c.Args != nil {
options = append(options, c.Args.Predict(a)...)
}
return
}

86
vendor/github.com/posener/complete/complete.go generated vendored Normal file
View File

@ -0,0 +1,86 @@
// Package complete provides a tool for bash writing bash completion in go.
//
// Writing bash completion scripts is a hard work. This package provides an easy way
// to create bash completion scripts for any command, and also an easy way to install/uninstall
// the completion of the command.
package complete
import (
"flag"
"fmt"
"os"
"strings"
"github.com/posener/complete/cmd"
)
const (
envComplete = "COMP_LINE"
envDebug = "COMP_DEBUG"
)
// Complete structs define completion for a command with CLI options
type Complete struct {
Command Command
cmd.CLI
}
// New creates a new complete command.
// name is the name of command we want to auto complete.
// IMPORTANT: it must be the same name - if the auto complete
// completes the 'go' command, name must be equal to "go".
// command is the struct of the command completion.
func New(name string, command Command) *Complete {
return &Complete{
Command: command,
CLI: cmd.CLI{Name: name},
}
}
// Run runs the completion and add installation flags beforehand.
// The flags are added to the main flag CommandLine variable.
func (c *Complete) Run() bool {
c.AddFlags(nil)
flag.Parse()
return c.Complete()
}
// Complete a command from completion line in environment variable,
// and print out the complete options.
// returns success if the completion ran or if the cli matched
// any of the given flags, false otherwise
// For installation: it assumes that flags were added and parsed before
// it was called.
func (c *Complete) Complete() bool {
line, ok := getLine()
if !ok {
// make sure flags parsed,
// in case they were not added in the main program
return c.CLI.Run()
}
Log("Completing line: %s", line)
a := newArgs(line)
options := c.Command.Predict(a)
Log("Completion: %s", options)
output(options)
return true
}
func getLine() ([]string, bool) {
line := os.Getenv(envComplete)
if line == "" {
return nil, false
}
return strings.Split(line, " "), true
}
func output(options []string) {
Log("")
// stdout of program defines the complete options
for _, option := range options {
fmt.Println(option)
}
}

23
vendor/github.com/posener/complete/log.go generated vendored Normal file
View File

@ -0,0 +1,23 @@
package complete
import (
"io"
"io/ioutil"
"log"
"os"
)
// Log is used for debugging purposes
// since complete is running on tab completion, it is nice to
// have logs to the stderr (when writing your own completer)
// to write logs, set the COMP_DEBUG environment variable and
// use complete.Log in the complete program
var Log = getLogger()
func getLogger() func(format string, args ...interface{}) {
var logfile io.Writer = ioutil.Discard
if os.Getenv(envDebug) != "" {
logfile = os.Stderr
}
return log.New(logfile, "complete ", log.Flags()).Printf
}

19
vendor/github.com/posener/complete/match/file.go generated vendored Normal file
View File

@ -0,0 +1,19 @@
package match
import "strings"
// File returns true if prefix can match the file
func File(file, prefix string) bool {
// special case for current directory completion
if file == "./" && (prefix == "." || prefix == "") {
return true
}
if prefix == "." && strings.HasPrefix(file, ".") {
return true
}
file = strings.TrimPrefix(file, "./")
prefix = strings.TrimPrefix(prefix, "./")
return strings.HasPrefix(file, prefix)
}

6
vendor/github.com/posener/complete/match/match.go generated vendored Normal file
View File

@ -0,0 +1,6 @@
package match
// Match matches two strings
// it is used for comparing a term to the last typed
// word, the prefix, and see if it is a possible auto complete option.
type Match func(term, prefix string) bool

9
vendor/github.com/posener/complete/match/prefix.go generated vendored Normal file
View File

@ -0,0 +1,9 @@
package match
import "strings"
// Prefix is a simple Matcher, if the word is it's prefix, there is a match
// Match returns true if a has the prefix as prefix
func Prefix(long, prefix string) bool {
return strings.HasPrefix(long, prefix)
}

21
vendor/github.com/posener/complete/metalinter.json generated vendored Normal file
View File

@ -0,0 +1,21 @@
{
"Vendor": true,
"DisableAll": true,
"Enable": [
"gofmt",
"goimports",
"interfacer",
"goconst",
"misspell",
"unconvert",
"gosimple",
"golint",
"structcheck",
"deadcode",
"vet"
],
"Exclude": [
"initTests is unused"
],
"Deadline": "2m"
}

41
vendor/github.com/posener/complete/predict.go generated vendored Normal file
View File

@ -0,0 +1,41 @@
package complete
// Predictor implements a predict method, in which given
// command line arguments returns a list of options it predicts.
type Predictor interface {
Predict(Args) []string
}
// PredictOr unions two predicate functions, so that the result predicate
// returns the union of their predication
func PredictOr(predictors ...Predictor) Predictor {
return PredictFunc(func(a Args) (prediction []string) {
for _, p := range predictors {
if p == nil {
continue
}
prediction = append(prediction, p.Predict(a)...)
}
return
})
}
// PredictFunc determines what terms can follow a command or a flag
// It is used for auto completion, given last - the last word in the already
// in the command line, what words can complete it.
type PredictFunc func(Args) []string
// Predict invokes the predict function and implements the Predictor interface
func (p PredictFunc) Predict(a Args) []string {
if p == nil {
return nil
}
return p(a)
}
// PredictNothing does not expect anything after.
var PredictNothing Predictor
// PredictAnything expects something, but nothing particular, such as a number
// or arbitrary name.
var PredictAnything = PredictFunc(func(Args) []string { return nil })

108
vendor/github.com/posener/complete/predict_files.go generated vendored Normal file
View File

@ -0,0 +1,108 @@
package complete
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/posener/complete/match"
)
// PredictDirs will search for directories in the given started to be typed
// path, if no path was started to be typed, it will complete to directories
// in the current working directory.
func PredictDirs(pattern string) Predictor {
return files(pattern, false)
}
// PredictFiles will search for files matching the given pattern in the started to
// be typed path, if no path was started to be typed, it will complete to files that
// match the pattern in the current working directory.
// To match any file, use "*" as pattern. To match go files use "*.go", and so on.
func PredictFiles(pattern string) Predictor {
return files(pattern, true)
}
func files(pattern string, allowFiles bool) PredictFunc {
// search for files according to arguments,
// if only one directory has matched the result, search recursively into
// this directory to give more results.
return func(a Args) (prediction []string) {
prediction = predictFiles(a, pattern, allowFiles)
// if the number of prediction is not 1, we either have many results or
// have no results, so we return it.
if len(prediction) != 1 {
return
}
// only try deeper, if the one item is a directory
if stat, err := os.Stat(prediction[0]); err != nil || !stat.IsDir() {
return
}
a.Last = prediction[0]
return predictFiles(a, pattern, allowFiles)
}
}
func predictFiles(a Args, pattern string, allowFiles bool) []string {
if strings.HasSuffix(a.Last, "/..") {
return nil
}
dir := a.Directory()
files := listFiles(dir, pattern, allowFiles)
// add dir if match
files = append(files, dir)
return PredictFilesSet(files).Predict(a)
}
// PredictFilesSet predict according to file rules to a given set of file names
func PredictFilesSet(files []string) PredictFunc {
return func(a Args) (prediction []string) {
// add all matching files to prediction
for _, f := range files {
f = fixPathForm(a.Last, f)
// test matching of file to the argument
if match.File(f, a.Last) {
prediction = append(prediction, f)
}
}
return
}
}
func listFiles(dir, pattern string, allowFiles bool) []string {
// set of all file names
m := map[string]bool{}
// list files
if files, err := filepath.Glob(filepath.Join(dir, pattern)); err == nil {
for _, f := range files {
if stat, err := os.Stat(f); err != nil || stat.IsDir() || allowFiles {
m[f] = true
}
}
}
// list directories
if dirs, err := ioutil.ReadDir(dir); err == nil {
for _, d := range dirs {
if d.IsDir() {
m[filepath.Join(dir, d.Name())] = true
}
}
}
list := make([]string, 0, len(m))
for k := range m {
list = append(list, k)
}
return list
}

19
vendor/github.com/posener/complete/predict_set.go generated vendored Normal file
View File

@ -0,0 +1,19 @@
package complete
import "github.com/posener/complete/match"
// PredictSet expects specific set of terms, given in the options argument.
func PredictSet(options ...string) Predictor {
return predictSet(options)
}
type predictSet []string
func (p predictSet) Predict(a Args) (prediction []string) {
for _, m := range p {
if match.Prefix(m, a.Last) {
prediction = append(prediction, m)
}
}
return
}

116
vendor/github.com/posener/complete/readme.md generated vendored Normal file
View File

@ -0,0 +1,116 @@
# complete
[![Build Status](https://travis-ci.org/posener/complete.svg?branch=master)](https://travis-ci.org/posener/complete)
[![codecov](https://codecov.io/gh/posener/complete/branch/master/graph/badge.svg)](https://codecov.io/gh/posener/complete)
[![GoDoc](https://godoc.org/github.com/posener/complete?status.svg)](http://godoc.org/github.com/posener/complete)
[![Go Report Card](https://goreportcard.com/badge/github.com/posener/complete)](https://goreportcard.com/report/github.com/posener/complete)
A tool for bash writing bash completion in go.
Writing bash completion scripts is a hard work. This package provides an easy way
to create bash completion scripts for any command, and also an easy way to install/uninstall
the completion of the command.
## go command bash completion
In [gocomplete](./gocomplete) there is an example for bash completion for the `go` command line.
This is an example that uses the `complete` package on the `go` command - the `complete` package
can also be used to implement any completions, see [Usage](#usage).
### Install
1. Type in your shell:
```
go get -u github.com/posener/complete/gocomplete
gocomplete -install
```
2. Restart your shell
Uninstall by `gocomplete -uninstall`
### Features
- Complete `go` command, including sub commands and all flags.
- Complete packages names or `.go` files when necessary.
- Complete test names after `-run` flag.
## complete package
Supported shells:
- [x] bash
- [x] zsh
### Usage
Assuming you have program called `run` and you want to have bash completion
for it, meaning, if you type `run` then space, then press the `Tab` key,
the shell will suggest relevant complete options.
In that case, we will create a program called `runcomplete`, a go program,
with a `func main()` and so, that will make the completion of the `run`
program. Once the `runcomplete` will be in a binary form, we could
`runcomplete -install` and that will add to our shell all the bash completion
options for `run`.
So here it is:
```go
import "github.com/posener/complete"
func main() {
// create a Command object, that represents the command we want
// to complete.
run := complete.Command{
// Sub defines a list of sub commands of the program,
// this is recursive, since every command is of type command also.
Sub: complete.Commands{
// add a build sub command
"build": complete.Command {
// define flags of the build sub command
Flags: complete.Flags{
// build sub command has a flag '-cpus', which
// expects number of cpus after it. in that case
// anything could complete this flag.
"-cpus": complete.PredictAnything,
},
},
},
// define flags of the 'run' main command
Flags: complete.Flags{
// a flag -o, which expects a file ending with .out after
// it, the tab completion will auto complete for files matching
// the given pattern.
"-o": complete.PredictFiles("*.out"),
},
// define global flags of the 'run' main command
// those will show up also when a sub command was entered in the
// command line
GlobalFlags: complete.Flags{
// a flag '-h' which does not expects anything after it
"-h": complete.PredictNothing,
},
}
// run the command completion, as part of the main() function.
// this triggers the autocompletion when needed.
// name must be exactly as the binary that we want to complete.
complete.New("run", run).Run()
}
```
### Self completing program
In case that the program that we want to complete is written in go we
can make it self completing.
Here is an [example](./example/self/main.go)

12
vendor/github.com/posener/complete/test.sh generated vendored Executable file
View File

@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -e
echo "" > coverage.txt
for d in $(go list ./... | grep -v vendor); do
go test -v -race -coverprofile=profile.out -covermode=atomic $d
if [ -f profile.out ]; then
cat profile.out >> coverage.txt
rm profile.out
fi
done

46
vendor/github.com/posener/complete/utils.go generated vendored Normal file
View File

@ -0,0 +1,46 @@
package complete
import (
"os"
"path/filepath"
"strings"
)
// fixPathForm changes a file name to a relative name
func fixPathForm(last string, file string) string {
// get wording directory for relative name
workDir, err := os.Getwd()
if err != nil {
return file
}
abs, err := filepath.Abs(file)
if err != nil {
return file
}
// if last is absolute, return path as absolute
if filepath.IsAbs(last) {
return fixDirPath(abs)
}
rel, err := filepath.Rel(workDir, abs)
if err != nil {
return file
}
// fix ./ prefix of path
if rel != "." && strings.HasPrefix(last, ".") {
rel = "./" + rel
}
return fixDirPath(rel)
}
func fixDirPath(path string) string {
info, err := os.Stat(path)
if err == nil && info.IsDir() && !strings.HasSuffix(path, "/") {
path += "/"
}
return path
}

31
vendor/vendor.json vendored
View File

@ -1011,10 +1011,11 @@
"revisionTime": "2017-05-10T07:48:58Z" "revisionTime": "2017-05-10T07:48:58Z"
}, },
{ {
"checksumSHA1": "UP+pXl+ic9y6qrpZA5MqDIAuGfw=", "checksumSHA1": "UIqCj7qI0hhIMpAhS9YYqs2jD48=",
"path": "github.com/mitchellh/cli", "path": "github.com/mitchellh/cli",
"revision": "ee8578a9c12a5bb9d55303b9665cc448772c81b8",
"revisionTime": "2017-03-28T05:23:52Z" "revision": "65fcae5817c8600da98ada9d7edf26dd1a84837b",
"revisionTime": "2017-09-08T18:10:43Z"
}, },
{ {
"checksumSHA1": "mVqDwKcibat0IKAdzAhfGIHPwI8=", "checksumSHA1": "mVqDwKcibat0IKAdzAhfGIHPwI8=",
@ -1163,6 +1164,30 @@
"path": "github.com/pmezard/go-difflib/difflib", "path": "github.com/pmezard/go-difflib/difflib",
"revision": "792786c7400a136282c1664665ae0a8db921c6c2" "revision": "792786c7400a136282c1664665ae0a8db921c6c2"
}, },
{
"checksumSHA1": "rTNABfFJ9wtLQRH8uYNkEZGQOrY=",
"path": "github.com/posener/complete",
"revision": "88e59760adaddb8276c9b15511302890690e2dae",
"revisionTime": "2017-09-08T12:52:45Z"
},
{
"checksumSHA1": "NB7uVS0/BJDmNu68vPAlbrq4TME=",
"path": "github.com/posener/complete/cmd",
"revision": "88e59760adaddb8276c9b15511302890690e2dae",
"revisionTime": "2017-09-08T12:52:45Z"
},
{
"checksumSHA1": "Hwojin3GxRyKwPAiz5r7UszqkPc=",
"path": "github.com/posener/complete/cmd/install",
"revision": "88e59760adaddb8276c9b15511302890690e2dae",
"revisionTime": "2017-09-08T12:52:45Z"
},
{
"checksumSHA1": "DMo94FwJAm9ZCYCiYdJU2+bh4no=",
"path": "github.com/posener/complete/match",
"revision": "88e59760adaddb8276c9b15511302890690e2dae",
"revisionTime": "2017-09-08T12:52:45Z"
},
{ {
"checksumSHA1": "Kq0fF7R65dDcGReuhf47O3LQgrY=", "checksumSHA1": "Kq0fF7R65dDcGReuhf47O3LQgrY=",
"path": "github.com/profitbricks/profitbricks-sdk-go", "path": "github.com/profitbricks/profitbricks-sdk-go",

View File

@ -106,3 +106,18 @@ The set of machine-readable message types can be found in the
documentation section. This section contains documentation on all the message documentation section. This section contains documentation on all the message
types exposed by Packer core as well as all the components that ship with types exposed by Packer core as well as all the components that ship with
Packer by default. Packer by default.
## Autocompletion
The `packer` command features opt-in subcommand autocompletion that you can
enable for your shell with `packer -autocomplete-install`. After doing so,
you can invoke a new shell and use the feature.
For example, assume a tab is typed at the end of each prompt line:
```
$ packer p
plugin push
$ packer push -
-name -sensitive -token -var -var-file
```