diff options
35 files changed, 1367 insertions, 263 deletions
@@ -268,15 +268,15 @@ revision = "79abb63cd66e41cb1473e26d11ebdcd68b04c8e5" [[projects]] - digest = "1:2e72f9cdc8b6f94a145fa1c97e305e1654d40507d04d2fbb0c37bf461a4b85f7" + digest = "1:abe9f3f23399646a6263682cacc9e86969f6c7e768f0ef036449926aa24cbbef" name = "github.com/spf13/cobra" packages = [ ".", "doc", ] pruneopts = "UT" - revision = "67fc4837d267bc9bfd6e47f77783fcc3dffc68de" - version = "v0.0.4" + revision = "f2b07da1e2c38d5f12845a4f607e2e1018cbb1f5" + version = "v0.0.5" [[projects]] digest = "1:9424f440bba8f7508b69414634aef3b2b3a877e522d8a4624692412805407bb7" @@ -50,7 +50,7 @@ [[constraint]] name = "github.com/spf13/cobra" - version = "v0.0.4" + version = "v0.0.5" [[constraint]] name = "github.com/dustin/go-humanize" diff --git a/bridge/bridges.go b/bridge/bridges.go index 6cbd13fe..ce6013e3 100644 --- a/bridge/bridges.go +++ b/bridge/bridges.go @@ -19,10 +19,9 @@ func NewBridge(repo *cache.RepoCache, target string, name string) (*core.Bridge, return core.NewBridge(repo, target, name) } -// Instantiate a new bridge for a repo, from the combined target and name contained -// in the full name -func NewBridgeFromFullName(repo *cache.RepoCache, fullName string) (*core.Bridge, error) { - return core.NewBridgeFromFullName(repo, fullName) +// LoadBridge instantiate a new bridge from a repo configuration +func LoadBridge(repo *cache.RepoCache, name string) (*core.Bridge, error) { + return core.LoadBridge(repo, name) } // Attempt to retrieve a default bridge for the given repo. If zero or multiple @@ -38,6 +37,6 @@ func ConfiguredBridges(repo repository.RepoCommon) ([]string, error) { } // Remove a configured bridge -func RemoveBridges(repo repository.RepoCommon, fullName string) error { - return core.RemoveBridge(repo, fullName) +func RemoveBridge(repo repository.RepoCommon, name string) error { + return core.RemoveBridge(repo, name) } diff --git a/bridge/core/bridge.go b/bridge/core/bridge.go index 3513b790..1b960e0e 100644 --- a/bridge/core/bridge.go +++ b/bridge/core/bridge.go @@ -9,15 +9,19 @@ import ( "strings" "time" + "github.com/pkg/errors" + "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/repository" - "github.com/pkg/errors" ) var ErrImportNotSupported = errors.New("import is not supported") var ErrExportNotSupported = errors.New("export is not supported") -const bridgeConfigKeyPrefix = "git-bug.bridge" +const ( + keyTarget = "target" + bridgeConfigKeyPrefix = "git-bug.bridge" +) var bridgeImpl map[string]reflect.Type @@ -81,15 +85,27 @@ func NewBridge(repo *cache.RepoCache, target string, name string) (*Bridge, erro return bridge, nil } -// Instantiate a new bridge for a repo, from the combined target and name contained -// in the full name -func NewBridgeFromFullName(repo *cache.RepoCache, fullName string) (*Bridge, error) { - target, name, err := splitFullName(fullName) +// LoadBridge instantiate a new bridge from a repo configuration +func LoadBridge(repo *cache.RepoCache, name string) (*Bridge, error) { + conf, err := loadConfig(repo, name) + if err != nil { + return nil, err + } + + target := conf[keyTarget] + bridge, err := NewBridge(repo, target, name) if err != nil { return nil, err } - return NewBridge(repo, target, name) + err = bridge.impl.ValidateConfig(conf) + if err != nil { + return nil, errors.Wrap(err, "invalid configuration") + } + + // will avoid reloading configuration before an export or import call + bridge.conf = conf + return bridge, nil } // Attempt to retrieve a default bridge for the given repo. If zero or multiple @@ -108,22 +124,7 @@ func DefaultBridge(repo *cache.RepoCache) (*Bridge, error) { return nil, fmt.Errorf("multiple bridge are configured, you need to select one explicitely") } - target, name, err := splitFullName(bridges[0]) - if err != nil { - return nil, err - } - - return NewBridge(repo, target, name) -} - -func splitFullName(fullName string) (string, string, error) { - split := strings.Split(fullName, ".") - - if len(split) != 2 { - return "", "", fmt.Errorf("bad bridge fullname: %s", fullName) - } - - return split[0], split[1], nil + return LoadBridge(repo, bridges[0]) } // ConfiguredBridges return the list of bridge that are configured for the given @@ -134,7 +135,7 @@ func ConfiguredBridges(repo repository.RepoCommon) ([]string, error) { return nil, errors.Wrap(err, "can't read configured bridges") } - re, err := regexp.Compile(bridgeConfigKeyPrefix + `.([^.]+\.[^.]+)`) + re, err := regexp.Compile(bridgeConfigKeyPrefix + `.([^.]+)`) if err != nil { panic(err) } @@ -163,17 +164,17 @@ func ConfiguredBridges(repo repository.RepoCommon) ([]string, error) { } // Remove a configured bridge -func RemoveBridge(repo repository.RepoCommon, fullName string) error { - re, err := regexp.Compile(`^[^.]+\.[^.]+$`) +func RemoveBridge(repo repository.RepoCommon, name string) error { + re, err := regexp.Compile(`^[a-zA-Z0-9]+`) if err != nil { panic(err) } - if !re.MatchString(fullName) { - return fmt.Errorf("bad bridge fullname: %s", fullName) + if !re.MatchString(name) { + return fmt.Errorf("bad bridge fullname: %s", name) } - keyPrefix := fmt.Sprintf("git-bug.bridge.%s", fullName) + keyPrefix := fmt.Sprintf("git-bug.bridge.%s", name) return repo.RmConfigs(keyPrefix) } @@ -184,14 +185,18 @@ func (b *Bridge) Configure(params BridgeParams) error { return err } - b.conf = conf + err = b.impl.ValidateConfig(conf) + if err != nil { + return fmt.Errorf("invalid configuration: %v", err) + } + b.conf = conf return b.storeConfig(conf) } func (b *Bridge) storeConfig(conf Configuration) error { for key, val := range conf { - storeKey := fmt.Sprintf("git-bug.bridge.%s.%s.%s", b.impl.Target(), b.Name, key) + storeKey := fmt.Sprintf("git-bug.bridge.%s.%s", b.Name, key) err := b.repo.StoreConfig(storeKey, val) if err != nil { @@ -204,7 +209,7 @@ func (b *Bridge) storeConfig(conf Configuration) error { func (b *Bridge) ensureConfig() error { if b.conf == nil { - conf, err := b.loadConfig() + conf, err := loadConfig(b.repo, b.Name) if err != nil { return err } @@ -214,10 +219,10 @@ func (b *Bridge) ensureConfig() error { return nil } -func (b *Bridge) loadConfig() (Configuration, error) { - keyPrefix := fmt.Sprintf("git-bug.bridge.%s.%s.", b.impl.Target(), b.Name) +func loadConfig(repo *cache.RepoCache, name string) (Configuration, error) { + keyPrefix := fmt.Sprintf("git-bug.bridge.%s.", name) - pairs, err := b.repo.ReadConfigs(keyPrefix) + pairs, err := repo.ReadConfigs(keyPrefix) if err != nil { return nil, errors.Wrap(err, "error while reading bridge configuration") } @@ -228,11 +233,6 @@ func (b *Bridge) loadConfig() (Configuration, error) { result[key] = value } - err = b.impl.ValidateConfig(result) - if err != nil { - return nil, errors.Wrap(err, "invalid configuration") - } - return result, nil } diff --git a/bridge/github/config.go b/bridge/github/config.go index 707b3e2f..1971abbc 100644 --- a/bridge/github/config.go +++ b/bridge/github/config.go @@ -17,7 +17,6 @@ import ( "time" "github.com/pkg/errors" - "golang.org/x/crypto/ssh/terminal" "github.com/MichaelMure/git-bug/bridge/core" @@ -25,7 +24,9 @@ import ( ) const ( + target = "github" githubV3Url = "https://api.github.com" + keyTarget = "target" keyOwner = "owner" keyProject = "project" keyToken = "token" @@ -101,6 +102,7 @@ func (*Github) Configure(repo repository.RepoCommon, params core.BridgeParams) ( return nil, fmt.Errorf("project doesn't exist or authentication token has an incorrect scope") } + conf[keyTarget] = target conf[keyToken] = token conf[keyOwner] = owner conf[keyProject] = project @@ -109,6 +111,12 @@ func (*Github) Configure(repo repository.RepoCommon, params core.BridgeParams) ( } func (*Github) ValidateConfig(conf core.Configuration) error { + if v, ok := conf[keyTarget]; !ok { + return fmt.Errorf("missing %s key", keyTarget) + } else if v != target { + return fmt.Errorf("unexpected target name: %v", v) + } + if _, ok := conf[keyToken]; !ok { return fmt.Errorf("missing %s key", keyToken) } diff --git a/bridge/launchpad/config.go b/bridge/launchpad/config.go index d8efea46..c02fd16d 100644 --- a/bridge/launchpad/config.go +++ b/bridge/launchpad/config.go @@ -17,7 +17,9 @@ import ( var ErrBadProjectURL = errors.New("bad Launchpad project URL") const ( + target = "launchpad-preview" keyProject = "project" + keyTarget = "target" defaultTimeout = 60 * time.Second ) @@ -61,6 +63,7 @@ func (*Launchpad) Configure(repo repository.RepoCommon, params core.BridgeParams } conf[keyProject] = project + conf[keyTarget] = target return conf, nil } @@ -69,6 +72,10 @@ func (*Launchpad) ValidateConfig(conf core.Configuration) error { return fmt.Errorf("missing %s key", keyProject) } + if _, ok := conf[keyTarget]; !ok { + return fmt.Errorf("missing %s key", keyTarget) + } + return nil } @@ -130,7 +130,8 @@ func readBug(repo repository.ClockedRepo, ref string) (*Bug, error) { } bug := Bug{ - id: id, + id: id, + editTime: 0, } // Load each OperationPack @@ -191,7 +192,10 @@ func readBug(repo repository.ClockedRepo, ref string) (*Bug, error) { bug.createTime = lamport.Time(createTime) } - bug.editTime = lamport.Time(editTime) + // Due to rebase, edit Lamport time are not necessarily ordered + if editTime > uint64(bug.editTime) { + bug.editTime = lamport.Time(editTime) + } // Update the clocks if err := repo.CreateWitness(bug.createTime); err != nil { diff --git a/bug/op_add_comment.go b/bug/op_add_comment.go index 59ea08e0..095b7f43 100644 --- a/bug/op_add_comment.go +++ b/bug/op_add_comment.go @@ -136,6 +136,9 @@ type AddCommentTimelineItem struct { CommentTimelineItem } +// Sign post method for gqlgen +func (a *AddCommentTimelineItem) IsAuthored() {} + // Convenience function to apply the operation func AddComment(b Interface, author identity.Interface, unixTime int64, message string) (*AddCommentOperation, error) { return AddCommentWithFiles(b, author, unixTime, message, nil) @@ -149,6 +152,3 @@ func AddCommentWithFiles(b Interface, author identity.Interface, unixTime int64, b.Append(addCommentOp) return addCommentOp, nil } - -// Sign post method for gqlgen -func (item *AddCommentTimelineItem) IsAuthored() {} diff --git a/bug/op_create.go b/bug/op_create.go index e0e8154b..e52e6254 100644 --- a/bug/op_create.go +++ b/bug/op_create.go @@ -156,6 +156,9 @@ type CreateTimelineItem struct { CommentTimelineItem } +// Sign post method for gqlgen +func (c *CreateTimelineItem) IsAuthored() {} + // Convenience function to apply the operation func Create(author identity.Interface, unixTime int64, title, message string) (*Bug, *CreateOperation, error) { return CreateWithFiles(author, unixTime, title, message, nil) @@ -173,6 +176,3 @@ func CreateWithFiles(author identity.Interface, unixTime int64, title, message s return newBug, createOp, nil } - -// Sign post method for gqlgen -func (item *CreateTimelineItem) IsAuthored() {} diff --git a/bug/op_label_change.go b/bug/op_label_change.go index c5b3765d..4c019d67 100644 --- a/bug/op_label_change.go +++ b/bug/op_label_change.go @@ -174,6 +174,9 @@ func (l LabelChangeTimelineItem) Hash() git.Hash { return l.hash } +// Sign post method for gqlgen +func (l *LabelChangeTimelineItem) IsAuthored() {} + // ChangeLabels is a convenience function to apply the operation func ChangeLabels(b Interface, author identity.Interface, unixTime int64, add, remove []string) ([]LabelChangeResult, *LabelChangeOperation, error) { var added, removed []Label @@ -303,6 +306,3 @@ func (l LabelChangeResult) String() string { panic(fmt.Sprintf("unknown label change status %v", l.Status)) } } - -// Sign post method for gqlgen -func (item *LabelChangeTimelineItem) IsAuthored() {} diff --git a/bug/op_set_status.go b/bug/op_set_status.go index de7b8526..52ba8135 100644 --- a/bug/op_set_status.go +++ b/bug/op_set_status.go @@ -124,6 +124,9 @@ func (s SetStatusTimelineItem) Hash() git.Hash { return s.hash } +// Sign post method for gqlgen +func (s *SetStatusTimelineItem) IsAuthored() {} + // Convenience function to apply the operation func Open(b Interface, author identity.Interface, unixTime int64) (*SetStatusOperation, error) { op := NewSetStatusOp(author, unixTime, OpenStatus) @@ -143,6 +146,3 @@ func Close(b Interface, author identity.Interface, unixTime int64) (*SetStatusOp b.Append(op) return op, nil } - -// Sign post method for gqlgen -func (item *SetStatusTimelineItem) IsAuthored() {} diff --git a/bug/op_set_title.go b/bug/op_set_title.go index dce06014..31113943 100644 --- a/bug/op_set_title.go +++ b/bug/op_set_title.go @@ -150,6 +150,9 @@ func (s SetTitleTimelineItem) Hash() git.Hash { return s.hash } +// Sign post method for gqlgen +func (s *SetTitleTimelineItem) IsAuthored() {} + // Convenience function to apply the operation func SetTitle(b Interface, author identity.Interface, unixTime int64, title string) (*SetTitleOperation, error) { it := NewOperationIterator(b) @@ -178,6 +181,3 @@ func SetTitle(b Interface, author identity.Interface, unixTime int64, title stri b.Append(setTitleOp) return setTitleOp, nil } - -// Sign post method for gqlgen -func (item *SetTitleTimelineItem) IsAuthored() {} diff --git a/commands/bridge_pull.go b/commands/bridge_pull.go index f9958882..c7a22d6d 100644 --- a/commands/bridge_pull.go +++ b/commands/bridge_pull.go @@ -23,7 +23,7 @@ func runBridgePull(cmd *cobra.Command, args []string) error { if len(args) == 0 { b, err = bridge.DefaultBridge(backend) } else { - b, err = bridge.NewBridgeFromFullName(backend, args[0]) + b, err = bridge.LoadBridge(backend, args[0]) } if err != nil { diff --git a/commands/bridge_rm.go b/commands/bridge_rm.go index 80a831ff..1c840e8a 100644 --- a/commands/bridge_rm.go +++ b/commands/bridge_rm.go @@ -1,10 +1,13 @@ package commands import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/MichaelMure/git-bug/bridge" "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/util/interrupt" - "github.com/spf13/cobra" ) func runBridgeRm(cmd *cobra.Command, args []string) error { @@ -15,16 +18,17 @@ func runBridgeRm(cmd *cobra.Command, args []string) error { defer backend.Close() interrupt.RegisterCleaner(backend.Close) - err = bridge.RemoveBridges(backend, args[0]) + err = bridge.RemoveBridge(backend, args[0]) if err != nil { return err } + fmt.Printf("Successfully removed bridge configuration %v", args[0]) return nil } var bridgeRmCmd = &cobra.Command{ - Use: "rm name <name>", + Use: "rm <name>", Short: "Delete a configured bridge.", PreRunE: loadRepo, RunE: runBridgeRm, diff --git a/doc/man/git-bug-bridge-rm.1 b/doc/man/git-bug-bridge-rm.1 index 630eba93..324d4237 100644 --- a/doc/man/git-bug-bridge-rm.1 +++ b/doc/man/git-bug-bridge-rm.1 @@ -10,7 +10,7 @@ git\-bug\-bridge\-rm \- Delete a configured bridge. .SH SYNOPSIS .PP -\fBgit\-bug bridge rm name <name> [flags]\fP +\fBgit\-bug bridge rm <name> [flags]\fP .SH DESCRIPTION diff --git a/doc/md/git-bug_bridge_rm.md b/doc/md/git-bug_bridge_rm.md index ab58e969..54941b2f 100644 --- a/doc/md/git-bug_bridge_rm.md +++ b/doc/md/git-bug_bridge_rm.md @@ -7,7 +7,7 @@ Delete a configured bridge. Delete a configured bridge. ``` -git-bug bridge rm name <name> [flags] +git-bug bridge rm <name> [flags] ``` ### Options @@ -1,6 +1,7 @@ //go:generate go run doc/gen_markdown.go //go:generate go run doc/gen_manpage.go //go:generate go run misc/gen_bash_completion.go +//go:generate go run misc/gen_powershell_completion.go //go:generate go run misc/gen_zsh_completion.go package main diff --git a/identity/identity.go b/identity/identity.go index be3c16ec..f952bbf9 100644 --- a/identity/identity.go +++ b/identity/identity.go @@ -8,12 +8,12 @@ import ( "strings" "time" - "github.com/MichaelMure/git-bug/util/timestamp" "github.com/pkg/errors" "github.com/MichaelMure/git-bug/repository" "github.com/MichaelMure/git-bug/util/git" "github.com/MichaelMure/git-bug/util/lamport" + "github.com/MichaelMure/git-bug/util/timestamp" ) const identityRefPattern = "refs/identities/" diff --git a/misc/gen_bash_completion.go b/misc/gen_bash_completion.go index f2506606..2d5e400b 100644 --- a/misc/gen_bash_completion.go +++ b/misc/gen_bash_completion.go @@ -15,7 +15,7 @@ func main() { cwd, _ := os.Getwd() dir := path.Join(cwd, "misc", "bash_completion", "git-bug") - fmt.Println("Generating bash completion file ...") + fmt.Println("Generating Bash completion file ...") err := commands.RootCmd.GenBashCompletionFile(dir) if err != nil { diff --git a/misc/gen_powershell_completion.go b/misc/gen_powershell_completion.go new file mode 100644 index 00000000..c2766399 --- /dev/null +++ b/misc/gen_powershell_completion.go @@ -0,0 +1,24 @@ +// +build ignore + +package main + +import ( + "fmt" + "log" + "os" + "path" + + "github.com/MichaelMure/git-bug/commands" +) + +func main() { + cwd, _ := os.Getwd() + filepath := path.Join(cwd, "misc", "powershell_completion", "git-bug") + + fmt.Println("Generating PowerShell completion file ...") + + err := commands.RootCmd.GenPowerShellCompletionFile(filepath) + if err != nil { + log.Fatal(err) + } +} diff --git a/misc/gen_zsh_completion.go b/misc/gen_zsh_completion.go index 184cab43..f80477d7 100644 --- a/misc/gen_zsh_completion.go +++ b/misc/gen_zsh_completion.go @@ -15,7 +15,7 @@ func main() { cwd, _ := os.Getwd() filepath := path.Join(cwd, "misc", "zsh_completion", "git-bug") - fmt.Println("Generating zsh completion file ...") + fmt.Println("Generating ZSH completion file ...") err := commands.RootCmd.GenZshCompletionFile(filepath) if err != nil { diff --git a/misc/powershell_completion/git-bug b/misc/powershell_completion/git-bug new file mode 100644 index 00000000..7eff1cda --- /dev/null +++ b/misc/powershell_completion/git-bug @@ -0,0 +1,207 @@ +using namespace System.Management.Automation +using namespace System.Management.Automation.Language +Register-ArgumentCompleter -Native -CommandName 'git-bug' -ScriptBlock { + param($wordToComplete, $commandAst, $cursorPosition) + $commandElements = $commandAst.CommandElements + $command = @( + 'git-bug' + for ($i = 1; $i -lt $commandElements.Count; $i++) { + $element = $commandElements[$i] + if ($element -isnot [StringConstantExpressionAst] -or + $element.StringConstantType -ne [StringConstantType]::BareWord -or + $element.Value.StartsWith('-')) { + break + } + $element.Value + } + ) -join ';' + $completions = @(switch ($command) { + 'git-bug' { + [CompletionResult]::new('add', 'add', [CompletionResultType]::ParameterValue, 'Create a new bug.') + [CompletionResult]::new('bridge', 'bridge', [CompletionResultType]::ParameterValue, 'Configure and use bridges to other bug trackers.') + [CompletionResult]::new('commands', 'commands', [CompletionResultType]::ParameterValue, 'Display available commands.') + [CompletionResult]::new('comment', 'comment', [CompletionResultType]::ParameterValue, 'Display or add comments to a bug.') + [CompletionResult]::new('deselect', 'deselect', [CompletionResultType]::ParameterValue, 'Clear the implicitly selected bug.') + [CompletionResult]::new('label', 'label', [CompletionResultType]::ParameterValue, 'Display, add or remove labels to/from a bug.') + [CompletionResult]::new('ls', 'ls', [CompletionResultType]::ParameterValue, 'List bugs.') + [CompletionResult]::new('ls-id', 'ls-id', [CompletionResultType]::ParameterValue, 'List bug identifiers.') + [CompletionResult]::new('ls-label', 'ls-label', [CompletionResultType]::ParameterValue, 'List valid labels.') + [CompletionResult]::new('pull', 'pull', [CompletionResultType]::ParameterValue, 'Pull bugs update from a git remote.') + [CompletionResult]::new('push', 'push', [CompletionResultType]::ParameterValue, 'Push bugs update to a git remote.') + [CompletionResult]::new('select', 'select', [CompletionResultType]::ParameterValue, 'Select a bug for implicit use in future commands.') + [CompletionResult]::new('show', 'show', [CompletionResultType]::ParameterValue, 'Display the details of a bug.') + [CompletionResult]::new('status', 'status', [CompletionResultType]::ParameterValue, 'Display or change a bug status.') + [CompletionResult]::new('termui', 'termui', [CompletionResultType]::ParameterValue, 'Launch the terminal UI.') + [CompletionResult]::new('title', 'title', [CompletionResultType]::ParameterValue, 'Display or change a title of a bug.') + [CompletionResult]::new('user', 'user', [CompletionResultType]::ParameterValue, 'Display or change the user identity.') + [CompletionResult]::new('version', 'version', [CompletionResultType]::ParameterValue, 'Show git-bug version information.') + [CompletionResult]::new('webui', 'webui', [CompletionResultType]::ParameterValue, 'Launch the web UI.') + break + } + 'git-bug;add' { + [CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'Provide a title to describe the issue') + [CompletionResult]::new('--title', 'title', [CompletionResultType]::ParameterName, 'Provide a title to describe the issue') + [CompletionResult]::new('-m', 'm', [CompletionResultType]::ParameterName, 'Provide a message to describe the issue') + [CompletionResult]::new('--message', 'message', [CompletionResultType]::ParameterName, 'Provide a message to describe the issue') + [CompletionResult]::new('-F', 'F', [CompletionResultType]::ParameterName, 'Take the message from the given file. Use - to read the message from the standard input') + [CompletionResult]::new('--file', 'file', [CompletionResultType]::ParameterName, 'Take the message from the given file. Use - to read the message from the standard input') + break + } + 'git-bug;bridge' { + [CompletionResult]::new('configure', 'configure', [CompletionResultType]::ParameterValue, 'Configure a new bridge.') + [CompletionResult]::new('pull', 'pull', [CompletionResultType]::ParameterValue, 'Pull updates.') + [CompletionResult]::new('rm', 'rm', [CompletionResultType]::ParameterValue, 'Delete a configured bridge.') + break + } + 'git-bug;bridge;configure' { + [CompletionResult]::new('-n', 'n', [CompletionResultType]::ParameterName, 'A distinctive name to identify the bridge') + [CompletionResult]::new('--name', 'name', [CompletionResultType]::ParameterName, 'A distinctive name to identify the bridge') + [CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'The target of the bridge. Valid values are [github,launchpad-preview]') + [CompletionResult]::new('--target', 'target', [CompletionResultType]::ParameterName, 'The target of the bridge. Valid values are [github,launchpad-preview]') + [CompletionResult]::new('-u', 'u', [CompletionResultType]::ParameterName, 'The URL of the target repository') + [CompletionResult]::new('--url', 'url', [CompletionResultType]::ParameterName, 'The URL of the target repository') + [CompletionResult]::new('-o', 'o', [CompletionResultType]::ParameterName, 'The owner of the target repository') + [CompletionResult]::new('--owner', 'owner', [CompletionResultType]::ParameterName, 'The owner of the target repository') + [CompletionResult]::new('-T', 'T', [CompletionResultType]::ParameterName, 'The authentication token for the API') + [CompletionResult]::new('--token', 'token', [CompletionResultType]::ParameterName, 'The authentication token for the API') + [CompletionResult]::new('-p', 'p', [CompletionResultType]::ParameterName, 'The name of the target repository') + [CompletionResult]::new('--project', 'project', [CompletionResultType]::ParameterName, 'The name of the target repository') + break + } + 'git-bug;bridge;pull' { + break + } + 'git-bug;bridge;rm' { + break + } + 'git-bug;commands' { + [CompletionResult]::new('-p', 'p', [CompletionResultType]::ParameterName, 'Output the command description as well as Markdown compatible comment') + [CompletionResult]::new('--pretty', 'pretty', [CompletionResultType]::ParameterName, 'Output the command description as well as Markdown compatible comment') + break + } + 'git-bug;comment' { + [CompletionResult]::new('add', 'add', [CompletionResultType]::ParameterValue, 'Add a new comment to a bug.') + break + } + 'git-bug;comment;add' { + [CompletionResult]::new('-F', 'F', [CompletionResultType]::ParameterName, 'Take the message from the given file. Use - to read the message from the standard input') + [CompletionResult]::new('--file', 'file', [CompletionResultType]::ParameterName, 'Take the message from the given file. Use - to read the message from the standard input') + [CompletionResult]::new('-m', 'm', [CompletionResultType]::ParameterName, 'Provide the new message from the command line') + [CompletionResult]::new('--message', 'message', [CompletionResultType]::ParameterName, 'Provide the new message from the command line') + break + } + 'git-bug;deselect' { + break + } + 'git-bug;label' { + [CompletionResult]::new('add', 'add', [CompletionResultType]::ParameterValue, 'Add a label to a bug.') + [CompletionResult]::new('rm', 'rm', [CompletionResultType]::ParameterValue, 'Remove a label from a bug.') + break + } + 'git-bug;label;add' { + break + } + 'git-bug;label;rm' { + break + } + 'git-bug;ls' { + [CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'Filter by status. Valid values are [open,closed]') + [CompletionResult]::new('--status', 'status', [CompletionResultType]::ParameterName, 'Filter by status. Valid values are [open,closed]') + [CompletionResult]::new('-a', 'a', [CompletionResultType]::ParameterName, 'Filter by author') + [CompletionResult]::new('--author', 'author', [CompletionResultType]::ParameterName, 'Filter by author') + [CompletionResult]::new('-p', 'p', [CompletionResultType]::ParameterName, 'Filter by participant') + [CompletionResult]::new('--participant', 'participant', [CompletionResultType]::ParameterName, 'Filter by participant') + [CompletionResult]::new('-A', 'A', [CompletionResultType]::ParameterName, 'Filter by actor') + [CompletionResult]::new('--actor', 'actor', [CompletionResultType]::ParameterName, 'Filter by actor') + [CompletionResult]::new('-l', 'l', [CompletionResultType]::ParameterName, 'Filter by label') + [CompletionResult]::new('--label', 'label', [CompletionResultType]::ParameterName, 'Filter by label') + [CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'Filter by title') + [CompletionResult]::new('--title', 'title', [CompletionResultType]::ParameterName, 'Filter by title') + [CompletionResult]::new('-n', 'n', [CompletionResultType]::ParameterName, 'Filter by absence of something. Valid values are [label]') + [CompletionResult]::new('--no', 'no', [CompletionResultType]::ParameterName, 'Filter by absence of something. Valid values are [label]') + [CompletionResult]::new('-b', 'b', [CompletionResultType]::ParameterName, 'Sort the results by a characteristic. Valid values are [id,creation,edit]') + [CompletionResult]::new('--by', 'by', [CompletionResultType]::ParameterName, 'Sort the results by a characteristic. Valid values are [id,creation,edit]') + [CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'Select the sorting direction. Valid values are [asc,desc]') + [CompletionResult]::new('--direction', 'direction', [CompletionResultType]::ParameterName, 'Select the sorting direction. Valid values are [asc,desc]') + break + } + 'git-bug;ls-id' { + break + } + 'git-bug;ls-label' { + break + } + 'git-bug;pull' { + break + } + 'git-bug;push' { + break + } + 'git-bug;select' { + break + } + 'git-bug;show' { + [CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'Select field to display. Valid values are [author,authorEmail,createTime,humanId,id,labels,shortId,status,title,actors,participants]') + [CompletionResult]::new('--field', 'field', [CompletionResultType]::ParameterName, 'Select field to display. Valid values are [author,authorEmail,createTime,humanId,id,labels,shortId,status,title,actors,participants]') + break + } + 'git-bug;status' { + [CompletionResult]::new('close', 'close', [CompletionResultType]::ParameterValue, 'Mark a bug as closed.') + [CompletionResult]::new('open', 'open', [CompletionResultType]::ParameterValue, 'Mark a bug as open.') + break + } + 'git-bug;status;close' { + break + } + 'git-bug;status;open' { + break + } + 'git-bug;termui' { + break + } + 'git-bug;title' { + [CompletionResult]::new('edit', 'edit', [CompletionResultType]::ParameterValue, 'Edit a title of a bug.') + break + } + 'git-bug;title;edit' { + [CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'Provide a title to describe the issue') + [CompletionResult]::new('--title', 'title', [CompletionResultType]::ParameterName, 'Provide a title to describe the issue') + break + } + 'git-bug;user' { + [CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'Select field to display. Valid values are [email,humanId,id,lastModification,lastModificationLamport,login,metadata,name]') + [CompletionResult]::new('--field', 'field', [CompletionResultType]::ParameterName, 'Select field to display. Valid values are [email,humanId,id,lastModification,lastModificationLamport,login,metadata,name]') + [CompletionResult]::new('adopt', 'adopt', [CompletionResultType]::ParameterValue, 'Adopt an existing identity as your own.') + [CompletionResult]::new('create', 'create', [CompletionResultType]::ParameterValue, 'Create a new identity.') + [CompletionResult]::new('ls', 'ls', [CompletionResultType]::ParameterValue, 'List identities.') + break + } + 'git-bug;user;adopt' { + break + } + 'git-bug;user;create' { + break + } + 'git-bug;user;ls' { + break + } + 'git-bug;version' { + [CompletionResult]::new('-n', 'n', [CompletionResultType]::ParameterName, 'Only show the version number') + [CompletionResult]::new('--number', 'number', [CompletionResultType]::ParameterName, 'Only show the version number') + [CompletionResult]::new('-c', 'c', [CompletionResultType]::ParameterName, 'Only show the commit hash') + [CompletionResult]::new('--commit', 'commit', [CompletionResultType]::ParameterName, 'Only show the commit hash') + [CompletionResult]::new('-a', 'a', [CompletionResultType]::ParameterName, 'Show all version informations') + [CompletionResult]::new('--all', 'all', [CompletionResultType]::ParameterName, 'Show all version informations') + break + } + 'git-bug;webui' { + [CompletionResult]::new('--open', 'open', [CompletionResultType]::ParameterName, 'Automatically open the web UI in the default browser') + [CompletionResult]::new('--no-open', 'no-open', [CompletionResultType]::ParameterName, 'Prevent the automatic opening of the web UI in the default browser') + [CompletionResult]::new('-p', 'p', [CompletionResultType]::ParameterName, 'Port to listen to (default is random)') + [CompletionResult]::new('--port', 'port', [CompletionResultType]::ParameterName, 'Port to listen to (default is random)') + break + } + }) + $completions.Where{ $_.CompletionText -like "$wordToComplete*" } | + Sort-Object -Property ListItemText +}
\ No newline at end of file diff --git a/misc/zsh_completion/git-bug b/misc/zsh_completion/git-bug index c2ed9872..ed676724 100644 --- a/misc/zsh_completion/git-bug +++ b/misc/zsh_completion/git-bug @@ -1,46 +1,396 @@ -#compdef git-bug - -_arguments \ - '1: :->level1' \ - '2: :->level2' \ - '3: :_files' -case $state in - level1) - case $words[1] in - git-bug) - _arguments '1: :(add bridge commands comment deselect label ls ls-id ls-label pull push select show status termui title user version webui)' - ;; - *) - _arguments '*: :_files' - ;; - esac - ;; - level2) - case $words[2] in - bridge) - _arguments '2: :(configure pull rm)' - ;; - comment) - _arguments '2: :(add)' - ;; - label) - _arguments '2: :(add rm)' - ;; - status) - _arguments '2: :(close open)' - ;; - title) - _arguments '2: :(edit)' - ;; - user) - _arguments '2: :(adopt create ls)' - ;; - *) - _arguments '*: :_files' - ;; - esac - ;; - *) - _arguments '*: :_files' - ;; -esac +#compdef _git-bug git-bug + + +function _git-bug { + local -a commands + + _arguments -C \ + "1: :->cmnds" \ + "*::arg:->args" + + case $state in + cmnds) + commands=( + "add:Create a new bug." + "bridge:Configure and use bridges to other bug trackers." + "commands:Display available commands." + "comment:Display or add comments to a bug." + "deselect:Clear the implicitly selected bug." + "label:Display, add or remove labels to/from a bug." + "ls:List bugs." + "ls-id:List bug identifiers." + "ls-label:List valid labels." + "pull:Pull bugs update from a git remote." + "push:Push bugs update to a git remote." + "select:Select a bug for implicit use in future commands." + "show:Display the details of a bug." + "status:Display or change a bug status." + "termui:Launch the terminal UI." + "title:Display or change a title of a bug." + "user:Display or change the user identity." + "version:Show git-bug version information." + "webui:Launch the web UI." + ) + _describe "command" commands + ;; + esac + + case "$words[1]" in + add) + _git-bug_add + ;; + bridge) + _git-bug_bridge + ;; + commands) + _git-bug_commands + ;; + comment) + _git-bug_comment + ;; + deselect) + _git-bug_deselect + ;; + label) + _git-bug_label + ;; + ls) + _git-bug_ls + ;; + ls-id) + _git-bug_ls-id + ;; + ls-label) + _git-bug_ls-label + ;; + pull) + _git-bug_pull + ;; + push) + _git-bug_push + ;; + select) + _git-bug_select + ;; + show) + _git-bug_show + ;; + status) + _git-bug_status + ;; + termui) + _git-bug_termui + ;; + title) + _git-bug_title + ;; + user) + _git-bug_user + ;; + version) + _git-bug_version + ;; + webui) + _git-bug_webui + ;; + esac +} + +function _git-bug_add { + _arguments \ + '(-t --title)'{-t,--title}'[Provide a title to describe the issue]:' \ + '(-m --message)'{-m,--message}'[Provide a message to describe the issue]:' \ + '(-F --file)'{-F,--file}'[Take the message from the given file. Use - to read the message from the standard input]:' +} + + +function _git-bug_bridge { + local -a commands + + _arguments -C \ + "1: :->cmnds" \ + "*::arg:->args" + + case $state in + cmnds) + commands=( + "configure:Configure a new bridge." + "pull:Pull updates." + "rm:Delete a configured bridge." + ) + _describe "command" commands + ;; + esac + + case "$words[1]" in + configure) + _git-bug_bridge_configure + ;; + pull) + _git-bug_bridge_pull + ;; + rm) + _git-bug_bridge_rm + ;; + esac +} + +function _git-bug_bridge_configure { + _arguments \ + '(-n --name)'{-n,--name}'[A distinctive name to identify the bridge]:' \ + '(-t --target)'{-t,--target}'[The target of the bridge. Valid values are [github,launchpad-preview]]:' \ + '(-u --url)'{-u,--url}'[The URL of the target repository]:' \ + '(-o --owner)'{-o,--owner}'[The owner of the target repository]:' \ + '(-T --token)'{-T,--token}'[The authentication token for the API]:' \ + '(-p --project)'{-p,--project}'[The name of the target repository]:' +} + +function _git-bug_bridge_pull { + _arguments +} + +function _git-bug_bridge_rm { + _arguments +} + +function _git-bug_commands { + _arguments \ + '(-p --pretty)'{-p,--pretty}'[Output the command description as well as Markdown compatible comment]' +} + + +function _git-bug_comment { + local -a commands + + _arguments -C \ + "1: :->cmnds" \ + "*::arg:->args" + + case $state in + cmnds) + commands=( + "add:Add a new comment to a bug." + ) + _describe "command" commands + ;; + esac + + case "$words[1]" in + add) + _git-bug_comment_add + ;; + esac +} + +function _git-bug_comment_add { + _arguments \ + '(-F --file)'{-F,--file}'[Take the message from the given file. Use - to read the message from the standard input]:' \ + '(-m --message)'{-m,--message}'[Provide the new message from the command line]:' +} + +function _git-bug_deselect { + _arguments +} + + +function _git-bug_label { + local -a commands + + _arguments -C \ + "1: :->cmnds" \ + "*::arg:->args" + + case $state in + cmnds) + commands=( + "add:Add a label to a bug." + "rm:Remove a label from a bug." + ) + _describe "command" commands + ;; + esac + + case "$words[1]" in + add) + _git-bug_label_add + ;; + rm) + _git-bug_label_rm + ;; + esac +} + +function _git-bug_label_add { + _arguments +} + +function _git-bug_label_rm { + _arguments +} + +function _git-bug_ls { + _arguments \ + '(*-s *--status)'{\*-s,\*--status}'[Filter by status. Valid values are [open,closed]]:' \ + '(*-a *--author)'{\*-a,\*--author}'[Filter by author]:' \ + '(*-p *--participant)'{\*-p,\*--participant}'[Filter by participant]:' \ + '(*-A *--actor)'{\*-A,\*--actor}'[Filter by actor]:' \ + '(*-l *--label)'{\*-l,\*--label}'[Filter by label]:' \ + '(*-t *--title)'{\*-t,\*--title}'[Filter by title]:' \ + '(*-n *--no)'{\*-n,\*--no}'[Filter by absence of something. Valid values are [label]]:' \ + '(-b --by)'{-b,--by}'[Sort the results by a characteristic. Valid values are [id,creation,edit]]:' \ + '(-d --direction)'{-d,--direction}'[Select the sorting direction. Valid values are [asc,desc]]:' +} + +function _git-bug_ls-id { + _arguments +} + +function _git-bug_ls-label { + _arguments +} + +function _git-bug_pull { + _arguments +} + +function _git-bug_push { + _arguments +} + +function _git-bug_select { + _arguments +} + +function _git-bug_show { + _arguments \ + '(-f --field)'{-f,--field}'[Select field to display. Valid values are [author,authorEmail,createTime,humanId,id,labels,shortId,status,title,actors,participants]]:' +} + + +function _git-bug_status { + local -a commands + + _arguments -C \ + "1: :->cmnds" \ + "*::arg:->args" + + case $state in + cmnds) + commands=( + "close:Mark a bug as closed." + "open:Mark a bug as open." + ) + _describe "command" commands + ;; + esac + + case "$words[1]" in + close) + _git-bug_status_close + ;; + open) + _git-bug_status_open + ;; + esac +} + +function _git-bug_status_close { + _arguments +} + +function _git-bug_status_open { + _arguments +} + +function _git-bug_termui { + _arguments +} + + +function _git-bug_title { + local -a commands + + _arguments -C \ + "1: :->cmnds" \ + "*::arg:->args" + + case $state in + cmnds) + commands=( + "edit:Edit a title of a bug." + ) + _describe "command" commands + ;; + esac + + case "$words[1]" in + edit) + _git-bug_title_edit + ;; + esac +} + +function _git-bug_title_edit { + _arguments \ + '(-t --title)'{-t,--title}'[Provide a title to describe the issue]:' +} + + +function _git-bug_user { + local -a commands + + _arguments -C \ + '(-f --field)'{-f,--field}'[Select field to display. Valid values are [email,humanId,id,lastModification,lastModificationLamport,login,metadata,name]]:' \ + "1: :->cmnds" \ + "*::arg:->args" + + case $state in + cmnds) + commands=( + "adopt:Adopt an existing identity as your own." + "create:Create a new identity." + "ls:List identities." + ) + _describe "command" commands + ;; + esac + + case "$words[1]" in + adopt) + _git-bug_user_adopt + ;; + create) + _git-bug_user_create + ;; + ls) + _git-bug_user_ls + ;; + esac +} + +function _git-bug_user_adopt { + _arguments +} + +function _git-bug_user_create { + _arguments +} + +function _git-bug_user_ls { + _arguments +} + +function _git-bug_version { + _arguments \ + '(-n --number)'{-n,--number}'[Only show the version number]' \ + '(-c --commit)'{-c,--commit}'[Only show the commit hash]' \ + '(-a --all)'{-a,--all}'[Show all version informations]' +} + +function _git-bug_webui { + _arguments \ + '--open[Automatically open the web UI in the default browser]' \ + '--no-open[Prevent the automatic opening of the web UI in the default browser]' \ + '(-p --port)'{-p,--port}'[Port to listen to (default is random)]:' +} + diff --git a/repository/git.go b/repository/git.go index 801504f2..c8c45d6a 100644 --- a/repository/git.go +++ b/repository/git.go @@ -261,7 +261,12 @@ func (repo *GitRepo) ReadConfigString(key string) (string, error) { // RmConfigs remove all key/value pair matching the key prefix func (repo *GitRepo) RmConfigs(keyPrefix string) error { + // try to remove key/value pair by key _, err := repo.runGitCommand("config", "--unset-all", keyPrefix) + if err != nil { + // try to remove section + _, err = repo.runGitCommand("config", "--remove-section", keyPrefix) + } return err } diff --git a/repository/git_test.go b/repository/git_test.go index 32634cfb..8bd3aa8e 100644 --- a/repository/git_test.go +++ b/repository/git_test.go @@ -35,7 +35,6 @@ func TestConfig(t *testing.T) { configs, err = repo.ReadConfigs("section") assert.NoError(t, err) - assert.Equal(t, configs, map[string]string{ "section.key": "value", }) @@ -43,9 +42,25 @@ func TestConfig(t *testing.T) { _, err = repo.ReadConfigBool("section.true") assert.Equal(t, ErrNoConfigEntry, err) + err = repo.RmConfigs("section.nonexistingkey") + assert.Error(t, err) + err = repo.RmConfigs("section.key") assert.NoError(t, err) _, err = repo.ReadConfigString("section.key") assert.Equal(t, ErrNoConfigEntry, err) + + err = repo.RmConfigs("nonexistingsection") + assert.Error(t, err) + + err = repo.RmConfigs("section") + assert.NoError(t, err) + + _, err = repo.ReadConfigString("section.key") + assert.Error(t, err) + + err = repo.RmConfigs("section.key") + assert.Error(t, err) + } diff --git a/repository/mock_repo.go b/repository/mock_repo.go index 2dc4868e..23534b89 100644 --- a/repository/mock_repo.go +++ b/repository/mock_repo.go @@ -103,6 +103,7 @@ func (r *mockRepoForTest) ReadConfigString(key string) (string, error) { return val, nil } +// RmConfigs remove all key/value pair matching the key prefix func (r *mockRepoForTest) RmConfigs(keyPrefix string) error { for key := range r.config { if strings.HasPrefix(key, keyPrefix) { diff --git a/vendor/github.com/spf13/cobra/.gitignore b/vendor/github.com/spf13/cobra/.gitignore index 1b8c7c26..3b053c59 100644 --- a/vendor/github.com/spf13/cobra/.gitignore +++ b/vendor/github.com/spf13/cobra/.gitignore @@ -34,3 +34,5 @@ tags *.exe cobra.test + +.idea/* diff --git a/vendor/github.com/spf13/cobra/README.md b/vendor/github.com/spf13/cobra/README.md index ff16e3f6..60c5a425 100644 --- a/vendor/github.com/spf13/cobra/README.md +++ b/vendor/github.com/spf13/cobra/README.md @@ -23,6 +23,7 @@ Many of the most widely used Go projects are built using Cobra, such as: [Istio](https://istio.io), [Prototool](https://github.com/uber/prototool), [mattermost-server](https://github.com/mattermost/mattermost-server), +[Gardener](https://github.com/gardener/gardenctl), etc. [![Build Status](https://travis-ci.org/spf13/cobra.svg "Travis CI status")](https://travis-ci.org/spf13/cobra) @@ -48,6 +49,7 @@ etc. * [Suggestions when "unknown command" happens](#suggestions-when-unknown-command-happens) * [Generating documentation for your command](#generating-documentation-for-your-command) * [Generating bash completions](#generating-bash-completions) + * [Generating zsh completions](#generating-zsh-completions) - [Contributing](#contributing) - [License](#license) @@ -336,7 +338,7 @@ rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose out A flag can also be assigned locally which will only apply to that specific command. ```go -rootCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from") +localCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from") ``` ### Local Flag on Parent Commands @@ -719,6 +721,11 @@ Cobra can generate documentation based on subcommands, flags, etc. in the follow Cobra can generate a bash-completion file. If you add more information to your command, these completions can be amazingly powerful and flexible. Read more about it in [Bash Completions](bash_completions.md). +## Generating zsh completions + +Cobra can generate zsh-completion file. Read more about it in +[Zsh Completions](zsh_completions.md). + # Contributing 1. Fork it diff --git a/vendor/github.com/spf13/cobra/bash_completions.go b/vendor/github.com/spf13/cobra/bash_completions.go index c3c1e501..57bb8e1b 100644 --- a/vendor/github.com/spf13/cobra/bash_completions.go +++ b/vendor/github.com/spf13/cobra/bash_completions.go @@ -545,51 +545,3 @@ func (c *Command) GenBashCompletionFile(filename string) error { return c.GenBashCompletion(outFile) } - -// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag if it exists, -// and causes your command to report an error if invoked without the flag. -func (c *Command) MarkFlagRequired(name string) error { - return MarkFlagRequired(c.Flags(), name) -} - -// MarkPersistentFlagRequired adds the BashCompOneRequiredFlag annotation to the named persistent flag if it exists, -// and causes your command to report an error if invoked without the flag. -func (c *Command) MarkPersistentFlagRequired(name string) error { - return MarkFlagRequired(c.PersistentFlags(), name) -} - -// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag if it exists, -// and causes your command to report an error if invoked without the flag. -func MarkFlagRequired(flags *pflag.FlagSet, name string) error { - return flags.SetAnnotation(name, BashCompOneRequiredFlag, []string{"true"}) -} - -// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag, if it exists. -// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided. -func (c *Command) MarkFlagFilename(name string, extensions ...string) error { - return MarkFlagFilename(c.Flags(), name, extensions...) -} - -// MarkFlagCustom adds the BashCompCustom annotation to the named flag, if it exists. -// Generated bash autocompletion will call the bash function f for the flag. -func (c *Command) MarkFlagCustom(name string, f string) error { - return MarkFlagCustom(c.Flags(), name, f) -} - -// MarkPersistentFlagFilename adds the BashCompFilenameExt annotation to the named persistent flag, if it exists. -// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided. -func (c *Command) MarkPersistentFlagFilename(name string, extensions ...string) error { - return MarkFlagFilename(c.PersistentFlags(), name, extensions...) -} - -// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag in the flag set, if it exists. -// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided. -func MarkFlagFilename(flags *pflag.FlagSet, name string, extensions ...string) error { - return flags.SetAnnotation(name, BashCompFilenameExt, extensions) -} - -// MarkFlagCustom adds the BashCompCustom annotation to the named flag in the flag set, if it exists. -// Generated bash autocompletion will call the bash function f for the flag. -func MarkFlagCustom(flags *pflag.FlagSet, name string, f string) error { - return flags.SetAnnotation(name, BashCompCustom, []string{f}) -} diff --git a/vendor/github.com/spf13/cobra/command.go b/vendor/github.com/spf13/cobra/command.go index b257f91b..c7e89830 100644 --- a/vendor/github.com/spf13/cobra/command.go +++ b/vendor/github.com/spf13/cobra/command.go @@ -177,8 +177,6 @@ type Command struct { // that we can use on every pflag set and children commands globNormFunc func(f *flag.FlagSet, name string) flag.NormalizedName - // output is an output writer defined by user. - output io.Writer // usageFunc is usage func defined by user. usageFunc func(*Command) error // usageTemplate is usage template defined by user. @@ -195,6 +193,13 @@ type Command struct { helpCommand *Command // versionTemplate is the version template defined by user. versionTemplate string + + // inReader is a reader defined by the user that replaces stdin + inReader io.Reader + // outWriter is a writer defined by the user that replaces stdout + outWriter io.Writer + // errWriter is a writer defined by the user that replaces stderr + errWriter io.Writer } // SetArgs sets arguments for the command. It is set to os.Args[1:] by default, if desired, can be overridden @@ -205,8 +210,28 @@ func (c *Command) SetArgs(a []string) { // SetOutput sets the destination for usage and error messages. // If output is nil, os.Stderr is used. +// Deprecated: Use SetOut and/or SetErr instead func (c *Command) SetOutput(output io.Writer) { - c.output = output + c.outWriter = output + c.errWriter = output +} + +// SetOut sets the destination for usage messages. +// If newOut is nil, os.Stdout is used. +func (c *Command) SetOut(newOut io.Writer) { + c.outWriter = newOut +} + +// SetErr sets the destination for error messages. +// If newErr is nil, os.Stderr is used. +func (c *Command) SetErr(newErr io.Writer) { + c.errWriter = newErr +} + +// SetOut sets the source for input data +// If newIn is nil, os.Stdin is used. +func (c *Command) SetIn(newIn io.Reader) { + c.inReader = newIn } // SetUsageFunc sets usage function. Usage can be defined by application. @@ -267,9 +292,19 @@ func (c *Command) OutOrStderr() io.Writer { return c.getOut(os.Stderr) } +// ErrOrStderr returns output to stderr +func (c *Command) ErrOrStderr() io.Writer { + return c.getErr(os.Stderr) +} + +// ErrOrStderr returns output to stderr +func (c *Command) InOrStdin() io.Reader { + return c.getIn(os.Stdin) +} + func (c *Command) getOut(def io.Writer) io.Writer { - if c.output != nil { - return c.output + if c.outWriter != nil { + return c.outWriter } if c.HasParent() { return c.parent.getOut(def) @@ -277,6 +312,26 @@ func (c *Command) getOut(def io.Writer) io.Writer { return def } +func (c *Command) getErr(def io.Writer) io.Writer { + if c.errWriter != nil { + return c.errWriter + } + if c.HasParent() { + return c.parent.getErr(def) + } + return def +} + +func (c *Command) getIn(def io.Reader) io.Reader { + if c.inReader != nil { + return c.inReader + } + if c.HasParent() { + return c.parent.getIn(def) + } + return def +} + // UsageFunc returns either the function set by SetUsageFunc for this command // or a parent, or it returns a default usage function. func (c *Command) UsageFunc() (f func(*Command) error) { @@ -329,13 +384,22 @@ func (c *Command) Help() error { return nil } -// UsageString return usage string. +// UsageString returns usage string. func (c *Command) UsageString() string { - tmpOutput := c.output + // Storing normal writers + tmpOutput := c.outWriter + tmpErr := c.errWriter + bb := new(bytes.Buffer) - c.SetOutput(bb) + c.outWriter = bb + c.errWriter = bb + c.Usage() - c.output = tmpOutput + + // Setting things back to normal + c.outWriter = tmpOutput + c.errWriter = tmpErr + return bb.String() } @@ -1068,6 +1132,21 @@ func (c *Command) Printf(format string, i ...interface{}) { c.Print(fmt.Sprintf(format, i...)) } +// PrintErr is a convenience method to Print to the defined Err output, fallback to Stderr if not set. +func (c *Command) PrintErr(i ...interface{}) { + fmt.Fprint(c.ErrOrStderr(), i...) +} + +// PrintErrln is a convenience method to Println to the defined Err output, fallback to Stderr if not set. +func (c *Command) PrintErrln(i ...interface{}) { + c.Print(fmt.Sprintln(i...)) +} + +// PrintErrf is a convenience method to Printf to the defined Err output, fallback to Stderr if not set. +func (c *Command) PrintErrf(format string, i ...interface{}) { + c.Print(fmt.Sprintf(format, i...)) +} + // CommandPath returns the full path to this command. func (c *Command) CommandPath() string { if c.HasParent() { diff --git a/vendor/github.com/spf13/cobra/powershell_completions.go b/vendor/github.com/spf13/cobra/powershell_completions.go new file mode 100644 index 00000000..756c61b9 --- /dev/null +++ b/vendor/github.com/spf13/cobra/powershell_completions.go @@ -0,0 +1,100 @@ +// PowerShell completions are based on the amazing work from clap: +// https://github.com/clap-rs/clap/blob/3294d18efe5f264d12c9035f404c7d189d4824e1/src/completions/powershell.rs +// +// The generated scripts require PowerShell v5.0+ (which comes Windows 10, but +// can be downloaded separately for windows 7 or 8.1). + +package cobra + +import ( + "bytes" + "fmt" + "io" + "os" + "strings" + + "github.com/spf13/pflag" +) + +var powerShellCompletionTemplate = `using namespace System.Management.Automation +using namespace System.Management.Automation.Language +Register-ArgumentCompleter -Native -CommandName '%s' -ScriptBlock { + param($wordToComplete, $commandAst, $cursorPosition) + $commandElements = $commandAst.CommandElements + $command = @( + '%s' + for ($i = 1; $i -lt $commandElements.Count; $i++) { + $element = $commandElements[$i] + if ($element -isnot [StringConstantExpressionAst] -or + $element.StringConstantType -ne [StringConstantType]::BareWord -or + $element.Value.StartsWith('-')) { + break + } + $element.Value + } + ) -join ';' + $completions = @(switch ($command) {%s + }) + $completions.Where{ $_.CompletionText -like "$wordToComplete*" } | + Sort-Object -Property ListItemText +}` + +func generatePowerShellSubcommandCases(out io.Writer, cmd *Command, previousCommandName string) { + var cmdName string + if previousCommandName == "" { + cmdName = cmd.Name() + } else { + cmdName = fmt.Sprintf("%s;%s", previousCommandName, cmd.Name()) + } + + fmt.Fprintf(out, "\n '%s' {", cmdName) + + cmd.Flags().VisitAll(func(flag *pflag.Flag) { + if nonCompletableFlag(flag) { + return + } + usage := escapeStringForPowerShell(flag.Usage) + if len(flag.Shorthand) > 0 { + fmt.Fprintf(out, "\n [CompletionResult]::new('-%s', '%s', [CompletionResultType]::ParameterName, '%s')", flag.Shorthand, flag.Shorthand, usage) + } + fmt.Fprintf(out, "\n [CompletionResult]::new('--%s', '%s', [CompletionResultType]::ParameterName, '%s')", flag.Name, flag.Name, usage) + }) + + for _, subCmd := range cmd.Commands() { + usage := escapeStringForPowerShell(subCmd.Short) + fmt.Fprintf(out, "\n [CompletionResult]::new('%s', '%s', [CompletionResultType]::ParameterValue, '%s')", subCmd.Name(), subCmd.Name(), usage) + } + + fmt.Fprint(out, "\n break\n }") + + for _, subCmd := range cmd.Commands() { + generatePowerShellSubcommandCases(out, subCmd, cmdName) + } +} + +func escapeStringForPowerShell(s string) string { + return strings.Replace(s, "'", "''", -1) +} + +// GenPowerShellCompletion generates PowerShell completion file and writes to the passed writer. +func (c *Command) GenPowerShellCompletion(w io.Writer) error { + buf := new(bytes.Buffer) + + var subCommandCases bytes.Buffer + generatePowerShellSubcommandCases(&subCommandCases, c, "") + fmt.Fprintf(buf, powerShellCompletionTemplate, c.Name(), c.Name(), subCommandCases.String()) + + _, err := buf.WriteTo(w) + return err +} + +// GenPowerShellCompletionFile generates PowerShell completion file. +func (c *Command) GenPowerShellCompletionFile(filename string) error { + outFile, err := os.Create(filename) + if err != nil { + return err + } + defer outFile.Close() + + return c.GenPowerShellCompletion(outFile) +} diff --git a/vendor/github.com/spf13/cobra/powershell_completions.md b/vendor/github.com/spf13/cobra/powershell_completions.md new file mode 100644 index 00000000..afed8024 --- /dev/null +++ b/vendor/github.com/spf13/cobra/powershell_completions.md @@ -0,0 +1,14 @@ +# Generating PowerShell Completions For Your Own cobra.Command + +Cobra can generate PowerShell completion scripts. Users need PowerShell version 5.0 or above, which comes with Windows 10 and can be downloaded separately for Windows 7 or 8.1. They can then write the completions to a file and source this file from their PowerShell profile, which is referenced by the `$Profile` environment variable. See `Get-Help about_Profiles` for more info about PowerShell profiles. + +# What's supported + +- Completion for subcommands using their `.Short` description +- Completion for non-hidden flags using their `.Name` and `.Shorthand` + +# What's not yet supported + +- Command aliases +- Required, filename or custom flags (they will work like normal flags) +- Custom completion scripts diff --git a/vendor/github.com/spf13/cobra/shell_completions.go b/vendor/github.com/spf13/cobra/shell_completions.go new file mode 100644 index 00000000..ba0af9cb --- /dev/null +++ b/vendor/github.com/spf13/cobra/shell_completions.go @@ -0,0 +1,85 @@ +package cobra + +import ( + "github.com/spf13/pflag" +) + +// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag if it exists, +// and causes your command to report an error if invoked without the flag. +func (c *Command) MarkFlagRequired(name string) error { + return MarkFlagRequired(c.Flags(), name) +} + +// MarkPersistentFlagRequired adds the BashCompOneRequiredFlag annotation to the named persistent flag if it exists, +// and causes your command to report an error if invoked without the flag. +func (c *Command) MarkPersistentFlagRequired(name string) error { + return MarkFlagRequired(c.PersistentFlags(), name) +} + +// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag if it exists, +// and causes your command to report an error if invoked without the flag. +func MarkFlagRequired(flags *pflag.FlagSet, name string) error { + return flags.SetAnnotation(name, BashCompOneRequiredFlag, []string{"true"}) +} + +// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag, if it exists. +// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided. +func (c *Command) MarkFlagFilename(name string, extensions ...string) error { + return MarkFlagFilename(c.Flags(), name, extensions...) +} + +// MarkFlagCustom adds the BashCompCustom annotation to the named flag, if it exists. +// Generated bash autocompletion will call the bash function f for the flag. +func (c *Command) MarkFlagCustom(name string, f string) error { + return MarkFlagCustom(c.Flags(), name, f) +} + +// MarkPersistentFlagFilename instructs the various shell completion +// implementations to limit completions for this persistent flag to the +// specified extensions (patterns). +// +// Shell Completion compatibility matrix: bash, zsh +func (c *Command) MarkPersistentFlagFilename(name string, extensions ...string) error { + return MarkFlagFilename(c.PersistentFlags(), name, extensions...) +} + +// MarkFlagFilename instructs the various shell completion implementations to +// limit completions for this flag to the specified extensions (patterns). +// +// Shell Completion compatibility matrix: bash, zsh +func MarkFlagFilename(flags *pflag.FlagSet, name string, extensions ...string) error { + return flags.SetAnnotation(name, BashCompFilenameExt, extensions) +} + +// MarkFlagCustom instructs the various shell completion implementations to +// limit completions for this flag to the specified extensions (patterns). +// +// Shell Completion compatibility matrix: bash, zsh +func MarkFlagCustom(flags *pflag.FlagSet, name string, f string) error { + return flags.SetAnnotation(name, BashCompCustom, []string{f}) +} + +// MarkFlagDirname instructs the various shell completion implementations to +// complete only directories with this named flag. +// +// Shell Completion compatibility matrix: zsh +func (c *Command) MarkFlagDirname(name string) error { + return MarkFlagDirname(c.Flags(), name) +} + +// MarkPersistentFlagDirname instructs the various shell completion +// implementations to complete only directories with this persistent named flag. +// +// Shell Completion compatibility matrix: zsh +func (c *Command) MarkPersistentFlagDirname(name string) error { + return MarkFlagDirname(c.PersistentFlags(), name) +} + +// MarkFlagDirname instructs the various shell completion implementations to +// complete only directories with this specified flag. +// +// Shell Completion compatibility matrix: zsh +func MarkFlagDirname(flags *pflag.FlagSet, name string) error { + zshPattern := "-(/)" + return flags.SetAnnotation(name, zshCompDirname, []string{zshPattern}) +} diff --git a/vendor/github.com/spf13/cobra/zsh_completions.go b/vendor/github.com/spf13/cobra/zsh_completions.go index e76d6071..12755482 100644 --- a/vendor/github.com/spf13/cobra/zsh_completions.go +++ b/vendor/github.com/spf13/cobra/zsh_completions.go @@ -1,14 +1,102 @@ package cobra import ( - "bytes" + "encoding/json" "fmt" "io" "os" "sort" "strings" + "text/template" + + "github.com/spf13/pflag" +) + +const ( + zshCompArgumentAnnotation = "cobra_annotations_zsh_completion_argument_annotation" + zshCompArgumentFilenameComp = "cobra_annotations_zsh_completion_argument_file_completion" + zshCompArgumentWordComp = "cobra_annotations_zsh_completion_argument_word_completion" + zshCompDirname = "cobra_annotations_zsh_dirname" +) + +var ( + zshCompFuncMap = template.FuncMap{ + "genZshFuncName": zshCompGenFuncName, + "extractFlags": zshCompExtractFlag, + "genFlagEntryForZshArguments": zshCompGenFlagEntryForArguments, + "extractArgsCompletions": zshCompExtractArgumentCompletionHintsForRendering, + } + zshCompletionText = ` +{{/* should accept Command (that contains subcommands) as parameter */}} +{{define "argumentsC" -}} +{{ $cmdPath := genZshFuncName .}} +function {{$cmdPath}} { + local -a commands + + _arguments -C \{{- range extractFlags .}} + {{genFlagEntryForZshArguments .}} \{{- end}} + "1: :->cmnds" \ + "*::arg:->args" + + case $state in + cmnds) + commands=({{range .Commands}}{{if not .Hidden}} + "{{.Name}}:{{.Short}}"{{end}}{{end}} + ) + _describe "command" commands + ;; + esac + + case "$words[1]" in {{- range .Commands}}{{if not .Hidden}} + {{.Name}}) + {{$cmdPath}}_{{.Name}} + ;;{{end}}{{end}} + esac +} +{{range .Commands}}{{if not .Hidden}} +{{template "selectCmdTemplate" .}} +{{- end}}{{end}} +{{- end}} + +{{/* should accept Command without subcommands as parameter */}} +{{define "arguments" -}} +function {{genZshFuncName .}} { +{{" _arguments"}}{{range extractFlags .}} \ + {{genFlagEntryForZshArguments . -}} +{{end}}{{range extractArgsCompletions .}} \ + {{.}}{{end}} +} +{{end}} + +{{/* dispatcher for commands with or without subcommands */}} +{{define "selectCmdTemplate" -}} +{{if .Hidden}}{{/* ignore hidden*/}}{{else -}} +{{if .Commands}}{{template "argumentsC" .}}{{else}}{{template "arguments" .}}{{end}} +{{- end}} +{{- end}} + +{{/* template entry point */}} +{{define "Main" -}} +#compdef _{{.Name}} {{.Name}} + +{{template "selectCmdTemplate" .}} +{{end}} +` ) +// zshCompArgsAnnotation is used to encode/decode zsh completion for +// arguments to/from Command.Annotations. +type zshCompArgsAnnotation map[int]zshCompArgHint + +type zshCompArgHint struct { + // Indicates the type of the completion to use. One of: + // zshCompArgumentFilenameComp or zshCompArgumentWordComp + Tipe string `json:"type"` + + // A value for the type above (globs for file completion or words) + Options []string `json:"options"` +} + // GenZshCompletionFile generates zsh completion file. func (c *Command) GenZshCompletionFile(filename string) error { outFile, err := os.Create(filename) @@ -20,116 +108,229 @@ func (c *Command) GenZshCompletionFile(filename string) error { return c.GenZshCompletion(outFile) } -// GenZshCompletion generates a zsh completion file and writes to the passed writer. +// GenZshCompletion generates a zsh completion file and writes to the passed +// writer. The completion always run on the root command regardless of the +// command it was called from. func (c *Command) GenZshCompletion(w io.Writer) error { - buf := new(bytes.Buffer) - - writeHeader(buf, c) - maxDepth := maxDepth(c) - writeLevelMapping(buf, maxDepth) - writeLevelCases(buf, maxDepth, c) + tmpl, err := template.New("Main").Funcs(zshCompFuncMap).Parse(zshCompletionText) + if err != nil { + return fmt.Errorf("error creating zsh completion template: %v", err) + } + return tmpl.Execute(w, c.Root()) +} - _, err := buf.WriteTo(w) - return err +// MarkZshCompPositionalArgumentFile marks the specified argument (first +// argument is 1) as completed by file selection. patterns (e.g. "*.txt") are +// optional - if not provided the completion will search for all files. +func (c *Command) MarkZshCompPositionalArgumentFile(argPosition int, patterns ...string) error { + if argPosition < 1 { + return fmt.Errorf("Invalid argument position (%d)", argPosition) + } + annotation, err := c.zshCompGetArgsAnnotations() + if err != nil { + return err + } + if c.zshcompArgsAnnotationnIsDuplicatePosition(annotation, argPosition) { + return fmt.Errorf("Duplicate annotation for positional argument at index %d", argPosition) + } + annotation[argPosition] = zshCompArgHint{ + Tipe: zshCompArgumentFilenameComp, + Options: patterns, + } + return c.zshCompSetArgsAnnotations(annotation) } -func writeHeader(w io.Writer, cmd *Command) { - fmt.Fprintf(w, "#compdef %s\n\n", cmd.Name()) +// MarkZshCompPositionalArgumentWords marks the specified positional argument +// (first argument is 1) as completed by the provided words. At east one word +// must be provided, spaces within words will be offered completion with +// "word\ word". +func (c *Command) MarkZshCompPositionalArgumentWords(argPosition int, words ...string) error { + if argPosition < 1 { + return fmt.Errorf("Invalid argument position (%d)", argPosition) + } + if len(words) == 0 { + return fmt.Errorf("Trying to set empty word list for positional argument %d", argPosition) + } + annotation, err := c.zshCompGetArgsAnnotations() + if err != nil { + return err + } + if c.zshcompArgsAnnotationnIsDuplicatePosition(annotation, argPosition) { + return fmt.Errorf("Duplicate annotation for positional argument at index %d", argPosition) + } + annotation[argPosition] = zshCompArgHint{ + Tipe: zshCompArgumentWordComp, + Options: words, + } + return c.zshCompSetArgsAnnotations(annotation) } -func maxDepth(c *Command) int { - if len(c.Commands()) == 0 { - return 0 +func zshCompExtractArgumentCompletionHintsForRendering(c *Command) ([]string, error) { + var result []string + annotation, err := c.zshCompGetArgsAnnotations() + if err != nil { + return nil, err } - maxDepthSub := 0 - for _, s := range c.Commands() { - subDepth := maxDepth(s) - if subDepth > maxDepthSub { - maxDepthSub = subDepth + for k, v := range annotation { + s, err := zshCompRenderZshCompArgHint(k, v) + if err != nil { + return nil, err } + result = append(result, s) } - return 1 + maxDepthSub + if len(c.ValidArgs) > 0 { + if _, positionOneExists := annotation[1]; !positionOneExists { + s, err := zshCompRenderZshCompArgHint(1, zshCompArgHint{ + Tipe: zshCompArgumentWordComp, + Options: c.ValidArgs, + }) + if err != nil { + return nil, err + } + result = append(result, s) + } + } + sort.Strings(result) + return result, nil } -func writeLevelMapping(w io.Writer, numLevels int) { - fmt.Fprintln(w, `_arguments \`) - for i := 1; i <= numLevels; i++ { - fmt.Fprintf(w, ` '%d: :->level%d' \`, i, i) - fmt.Fprintln(w) +func zshCompRenderZshCompArgHint(i int, z zshCompArgHint) (string, error) { + switch t := z.Tipe; t { + case zshCompArgumentFilenameComp: + var globs []string + for _, g := range z.Options { + globs = append(globs, fmt.Sprintf(`-g "%s"`, g)) + } + return fmt.Sprintf(`'%d: :_files %s'`, i, strings.Join(globs, " ")), nil + case zshCompArgumentWordComp: + var words []string + for _, w := range z.Options { + words = append(words, fmt.Sprintf("%q", w)) + } + return fmt.Sprintf(`'%d: :(%s)'`, i, strings.Join(words, " ")), nil + default: + return "", fmt.Errorf("Invalid zsh argument completion annotation: %s", t) } - fmt.Fprintf(w, ` '%d: :%s'`, numLevels+1, "_files") - fmt.Fprintln(w) } -func writeLevelCases(w io.Writer, maxDepth int, root *Command) { - fmt.Fprintln(w, "case $state in") - defer fmt.Fprintln(w, "esac") +func (c *Command) zshcompArgsAnnotationnIsDuplicatePosition(annotation zshCompArgsAnnotation, position int) bool { + _, dup := annotation[position] + return dup +} - for i := 1; i <= maxDepth; i++ { - fmt.Fprintf(w, " level%d)\n", i) - writeLevel(w, root, i) - fmt.Fprintln(w, " ;;") +func (c *Command) zshCompGetArgsAnnotations() (zshCompArgsAnnotation, error) { + annotation := make(zshCompArgsAnnotation) + annotationString, ok := c.Annotations[zshCompArgumentAnnotation] + if !ok { + return annotation, nil + } + err := json.Unmarshal([]byte(annotationString), &annotation) + if err != nil { + return annotation, fmt.Errorf("Error unmarshaling zsh argument annotation: %v", err) } - fmt.Fprintln(w, " *)") - fmt.Fprintln(w, " _arguments '*: :_files'") - fmt.Fprintln(w, " ;;") + return annotation, nil } -func writeLevel(w io.Writer, root *Command, i int) { - fmt.Fprintf(w, " case $words[%d] in\n", i) - defer fmt.Fprintln(w, " esac") - - commands := filterByLevel(root, i) - byParent := groupByParent(commands) +func (c *Command) zshCompSetArgsAnnotations(annotation zshCompArgsAnnotation) error { + jsn, err := json.Marshal(annotation) + if err != nil { + return fmt.Errorf("Error marshaling zsh argument annotation: %v", err) + } + if c.Annotations == nil { + c.Annotations = make(map[string]string) + } + c.Annotations[zshCompArgumentAnnotation] = string(jsn) + return nil +} - // sort the parents to keep a determinist order - parents := make([]string, len(byParent)) - j := 0 - for parent := range byParent { - parents[j] = parent - j++ +func zshCompGenFuncName(c *Command) string { + if c.HasParent() { + return zshCompGenFuncName(c.Parent()) + "_" + c.Name() } - sort.StringSlice(parents).Sort() + return "_" + c.Name() +} + +func zshCompExtractFlag(c *Command) []*pflag.Flag { + var flags []*pflag.Flag + c.LocalFlags().VisitAll(func(f *pflag.Flag) { + if !f.Hidden { + flags = append(flags, f) + } + }) + c.InheritedFlags().VisitAll(func(f *pflag.Flag) { + if !f.Hidden { + flags = append(flags, f) + } + }) + return flags +} - for _, parent := range parents { - names := names(byParent[parent]) - fmt.Fprintf(w, " %s)\n", parent) - fmt.Fprintf(w, " _arguments '%d: :(%s)'\n", i, strings.Join(names, " ")) - fmt.Fprintln(w, " ;;") +// zshCompGenFlagEntryForArguments returns an entry that matches _arguments +// zsh-completion parameters. It's too complicated to generate in a template. +func zshCompGenFlagEntryForArguments(f *pflag.Flag) string { + if f.Name == "" || f.Shorthand == "" { + return zshCompGenFlagEntryForSingleOptionFlag(f) } - fmt.Fprintln(w, " *)") - fmt.Fprintln(w, " _arguments '*: :_files'") - fmt.Fprintln(w, " ;;") + return zshCompGenFlagEntryForMultiOptionFlag(f) } -func filterByLevel(c *Command, l int) []*Command { - cs := make([]*Command, 0) - if l == 0 { - cs = append(cs, c) - return cs +func zshCompGenFlagEntryForSingleOptionFlag(f *pflag.Flag) string { + var option, multiMark, extras string + + if zshCompFlagCouldBeSpecifiedMoreThenOnce(f) { + multiMark = "*" } - for _, s := range c.Commands() { - cs = append(cs, filterByLevel(s, l-1)...) + + option = "--" + f.Name + if option == "--" { + option = "-" + f.Shorthand } - return cs + extras = zshCompGenFlagEntryExtras(f) + + return fmt.Sprintf(`'%s%s[%s]%s'`, multiMark, option, zshCompQuoteFlagDescription(f.Usage), extras) } -func groupByParent(commands []*Command) map[string][]*Command { - m := make(map[string][]*Command) - for _, c := range commands { - parent := c.Parent() - if parent == nil { - continue - } - m[parent.Name()] = append(m[parent.Name()], c) +func zshCompGenFlagEntryForMultiOptionFlag(f *pflag.Flag) string { + var options, parenMultiMark, curlyMultiMark, extras string + + if zshCompFlagCouldBeSpecifiedMoreThenOnce(f) { + parenMultiMark = "*" + curlyMultiMark = "\\*" } - return m + + options = fmt.Sprintf(`'(%s-%s %s--%s)'{%s-%s,%s--%s}`, + parenMultiMark, f.Shorthand, parenMultiMark, f.Name, curlyMultiMark, f.Shorthand, curlyMultiMark, f.Name) + extras = zshCompGenFlagEntryExtras(f) + + return fmt.Sprintf(`%s'[%s]%s'`, options, zshCompQuoteFlagDescription(f.Usage), extras) } -func names(commands []*Command) []string { - ns := make([]string, len(commands)) - for i, c := range commands { - ns[i] = c.Name() +func zshCompGenFlagEntryExtras(f *pflag.Flag) string { + if f.NoOptDefVal != "" { + return "" } - return ns + + extras := ":" // allow options for flag (even without assistance) + for key, values := range f.Annotations { + switch key { + case zshCompDirname: + extras = fmt.Sprintf(":filename:_files -g %q", values[0]) + case BashCompFilenameExt: + extras = ":filename:_files" + for _, pattern := range values { + extras = extras + fmt.Sprintf(` -g "%s"`, pattern) + } + } + } + + return extras +} + +func zshCompFlagCouldBeSpecifiedMoreThenOnce(f *pflag.Flag) bool { + return strings.Contains(f.Value.Type(), "Slice") || + strings.Contains(f.Value.Type(), "Array") +} + +func zshCompQuoteFlagDescription(s string) string { + return strings.Replace(s, "'", `'\''`, -1) } diff --git a/vendor/github.com/spf13/cobra/zsh_completions.md b/vendor/github.com/spf13/cobra/zsh_completions.md new file mode 100644 index 00000000..df9c2eac --- /dev/null +++ b/vendor/github.com/spf13/cobra/zsh_completions.md @@ -0,0 +1,39 @@ +## Generating Zsh Completion for your cobra.Command + +Cobra supports native Zsh completion generated from the root `cobra.Command`. +The generated completion script should be put somewhere in your `$fpath` named +`_<YOUR COMMAND>`. + +### What's Supported + +* Completion for all non-hidden subcommands using their `.Short` description. +* Completion for all non-hidden flags using the following rules: + * Filename completion works by marking the flag with `cmd.MarkFlagFilename...` + family of commands. + * The requirement for argument to the flag is decided by the `.NoOptDefVal` + flag value - if it's empty then completion will expect an argument. + * Flags of one of the various `*Array` and `*Slice` types supports multiple + specifications (with or without argument depending on the specific type). +* Completion of positional arguments using the following rules: + * Argument position for all options below starts at `1`. If argument position + `0` is requested it will raise an error. + * Use `command.MarkZshCompPositionalArgumentFile` to complete filenames. Glob + patterns (e.g. `"*.log"`) are optional - if not specified it will offer to + complete all file types. + * Use `command.MarkZshCompPositionalArgumentWords` to offer specific words for + completion. At least one word is required. + * It's possible to specify completion for some arguments and leave some + unspecified (e.g. offer words for second argument but nothing for first + argument). This will cause no completion for first argument but words + completion for second argument. + * If no argument completion was specified for 1st argument (but optionally was + specified for 2nd) and the command has `ValidArgs` it will be used as + completion options for 1st argument. + * Argument completions only offered for commands with no subcommands. + +### What's not yet Supported + +* Custom completion scripts are not supported yet (We should probably create zsh + specific one, doesn't make sense to re-use the bash one as the functions will + be different). +* Whatever other feature you're looking for and doesn't exist :) |