aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gopkg.lock6
-rw-r--r--Gopkg.toml2
-rw-r--r--bridge/bridges.go11
-rw-r--r--bridge/core/bridge.go80
-rw-r--r--bridge/github/config.go10
-rw-r--r--bridge/launchpad/config.go7
-rw-r--r--bug/bug.go8
-rw-r--r--bug/op_add_comment.go6
-rw-r--r--bug/op_create.go6
-rw-r--r--bug/op_label_change.go6
-rw-r--r--bug/op_set_status.go6
-rw-r--r--bug/op_set_title.go6
-rw-r--r--commands/bridge_pull.go2
-rw-r--r--commands/bridge_rm.go10
-rw-r--r--doc/man/git-bug-bridge-rm.12
-rw-r--r--doc/md/git-bug_bridge_rm.md2
-rw-r--r--git-bug.go1
-rw-r--r--identity/identity.go2
-rw-r--r--misc/gen_bash_completion.go2
-rw-r--r--misc/gen_powershell_completion.go24
-rw-r--r--misc/gen_zsh_completion.go2
-rw-r--r--misc/powershell_completion/git-bug207
-rw-r--r--misc/zsh_completion/git-bug442
-rw-r--r--repository/git.go5
-rw-r--r--repository/git_test.go17
-rw-r--r--repository/mock_repo.go1
-rw-r--r--vendor/github.com/spf13/cobra/.gitignore2
-rw-r--r--vendor/github.com/spf13/cobra/README.md9
-rw-r--r--vendor/github.com/spf13/cobra/bash_completions.go48
-rw-r--r--vendor/github.com/spf13/cobra/command.go97
-rw-r--r--vendor/github.com/spf13/cobra/powershell_completions.go100
-rw-r--r--vendor/github.com/spf13/cobra/powershell_completions.md14
-rw-r--r--vendor/github.com/spf13/cobra/shell_completions.go85
-rw-r--r--vendor/github.com/spf13/cobra/zsh_completions.go363
-rw-r--r--vendor/github.com/spf13/cobra/zsh_completions.md39
35 files changed, 1367 insertions, 263 deletions
diff --git a/Gopkg.lock b/Gopkg.lock
index a85bdedb..2bb58e6c 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -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"
diff --git a/Gopkg.toml b/Gopkg.toml
index 42fa3731..e9047507 100644
--- a/Gopkg.toml
+++ b/Gopkg.toml
@@ -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
}
diff --git a/bug/bug.go b/bug/bug.go
index 0604d11d..7e28954f 100644
--- a/bug/bug.go
+++ b/bug/bug.go
@@ -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
diff --git a/git-bug.go b/git-bug.go
index 20bf74bc..cf59182f 100644
--- a/git-bug.go
+++ b/git-bug.go
@@ -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 :)