Skip to content
This repository has been archived by the owner on Feb 16, 2023. It is now read-only.

Autocompletion for cobra: api paths #334

Draft
wants to merge 42 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
483605e
First prototype of transition to cobra
edif2008 Aug 5, 2020
2582da5
Small adjustment to flags + fullCommand works
edif2008 Aug 6, 2020
d35f8d1
environment functions
hculea Aug 6, 2020
570b3fd
Adjust all files to new version of env (cobra adapted)
edif2008 Aug 8, 2020
b78ebfe
Fix lint using copy instead of loop
edif2008 Aug 8, 2020
0597985
some of the functions implemented properly. tested on secrethub read,…
hculea Aug 10, 2020
b9699bf
implemented existing file var in progress and stringmapvar
hculea Aug 10, 2020
ba69bc7
changed to another flag structure
hculea Aug 11, 2020
7f24ee3
the transition began.
hculea Aug 12, 2020
365d03d
formatted around a quarter of the files
hculea Aug 12, 2020
69fb50d
linter, not that it isn't my last concern
hculea Aug 12, 2020
f12f3a3
about 12 files left to modify
hculea Aug 12, 2020
47b91b6
force flag and timestamp flag
hculea Aug 12, 2020
6638102
2 more fixed files, 8 more to go
hculea Aug 12, 2020
1486eda
something i did yesterday
hculea Aug 13, 2020
aeea1c6
something's compiling
hculea Aug 13, 2020
306d7f2
something working
hculea Aug 13, 2020
f0d79d7
last 2 commands
hculea Aug 13, 2020
0ddb221
Small changes to make tests work again
edif2008 Aug 13, 2020
1058d4e
Make tests work
edif2008 Aug 13, 2020
789f884
Fix lint
edif2008 Aug 13, 2020
6032908
solved almost all todos
hculea Aug 13, 2020
6b2a9bb
Add app version
edif2008 Aug 13, 2020
382c19a
Fix tests and lint
edif2008 Aug 13, 2020
1b81c0c
Rename PreRun to argumentRegister
edif2008 Aug 13, 2020
5d1cddf
Add completion functionality into CLI
edif2008 Aug 13, 2020
3143890
added one forgotten flag optionality
hculea Aug 13, 2020
0ef995e
env.go
hculea Aug 14, 2020
2ef2a75
solved conflicts
hculea Aug 14, 2020
0c12014
wrapper for cobra
hculea Aug 14, 2020
64d5d22
Remove all persistentFlag calls
edif2008 Aug 14, 2020
b1381cb
formatted the out structure
hculea Aug 14, 2020
0e5d1ea
Auto stash before merge of "feature/cobra-switch" and "origin/feature…
edif2008 Aug 14, 2020
d5e13cc
Remove .idea files and final adjustments flags
edif2008 Aug 14, 2020
80989ed
first try env
hculea Aug 14, 2020
a2b62a8
Make environmental variables work
edif2008 Aug 14, 2020
de0a964
some cleanup
hculea Aug 14, 2020
c16cb7c
Remove dynamic path autocompletion
edif2008 Aug 14, 2020
41e74c3
added autocompletion script
hculea Aug 14, 2020
f897ea3
Autocompletion for paths
edif2008 Aug 14, 2020
024bcbe
Fix lint
edif2008 Aug 14, 2020
331105d
Enhance autocompletion for commands with multiple arguments
edif2008 Aug 14, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/secrethub/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
)

func main() {
err := secrethub.NewApp().Version(secrethub.Version, secrethub.Commit).Run(os.Args[1:])
err := secrethub.NewApp().Version(secrethub.Version, secrethub.Commit).Run()
if err != nil {
handleError(err)
}
Expand Down
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/secrethub/demo-app v0.1.0
github.com/secrethub/secrethub-go v0.30.0
github.com/spf13/cobra v1.0.0
github.com/spf13/pflag v1.0.5
github.com/xhit/go-str2duration v1.2.0
github.com/zalando/go-keyring v0.0.0-20190208082241-fbe81aec3a07
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e
golang.org/x/text v0.3.2
google.golang.org/api v0.26.0
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84
gopkg.in/yaml.v2 v2.2.2
gotest.tools v2.2.0+incompatible
)
101 changes: 87 additions & 14 deletions go.sum

Large diffs are not rendered by default.

272 changes: 208 additions & 64 deletions internals/cli/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import (
"os"
"strings"
"text/tabwriter"
"time"

"bitbucket.org/zombiezen/cardcpx/natsort"
"github.com/alecthomas/kingpin"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)

var (
Expand All @@ -21,55 +23,46 @@ var (
// App represents a command-line application that wraps the
// kingpin library and adds additional functionality.
type App struct {
*kingpin.Application

Application cobra.Command
name string
delimiters []string
separator string
knownEnvVars map[string]struct{}
extraEnvVarFuncs [](func(key string) bool)
extraEnvVarFuncs []func(key string) bool
}

// NewApp defines a new command-line application.
func NewApp(name, help string) *App {
return &App{
Application: kingpin.New(name, help),
Application: cobra.Command{Use: name, Short: help, SilenceErrors: true, SilenceUsage: true},
name: formatName(name, "", DefaultEnvSeparator, DefaultCommandDelimiters...),
delimiters: DefaultCommandDelimiters,
separator: DefaultEnvSeparator,
knownEnvVars: make(map[string]struct{}),
extraEnvVarFuncs: [](func(string) bool){},
extraEnvVarFuncs: []func(string) bool{},
}
}

// Command defines a new top-level command with the given name and help text.
func (a *App) Command(name, help string) *CommandClause {
func (a *App) CreateCommand(name, help string) *CommandClause {
return &CommandClause{
CmdClause: a.Application.Command(name, help),
name: name,
app: a,
Command: func() *cobra.Command {
newCommand := &cobra.Command{Use: name, Short: help, SilenceErrors: true, SilenceUsage: true}
a.Application.AddCommand(newCommand)
return newCommand
}(),
name: name,
App: a,
}
}

//
// Version adds a flag for displaying the application version number.
func (a *App) Version(version string) *App {
a.Application = a.Application.Version(version)
a.Application.Version = version
return a
}

// Flag defines a new flag with the given long name and help text,
// adding an environment variable default configurable by APP_FLAG_NAME.
func (a *App) Flag(name, help string) *Flag {
envVar := formatName(name, a.name, a.separator, a.delimiters...)
a.registerEnvVar(envVar)
flag := a.Application.Flag(name, help).Envar(envVar)
return &Flag{
FlagClause: flag,
app: a,
envVar: envVar,
}
}

// registerEnvVar ensures the App recognizes an environment variable.
func (a *App) registerEnvVar(name string) {
a.knownEnvVars[strings.ToUpper(name)] = struct{}{}
Expand Down Expand Up @@ -98,7 +91,7 @@ func (a *App) isExtraEnvVar(key string) bool {
return false
}

// PrintEnv reads all environment variables starting with the app name and writes
// PrintEnv reads all environment variables starting with the App name and writes
// a table with the keys and their status: set, empty, unrecognized. The value
// of environment variables are not printed out for security reasons. The list
// is limited to variables that are actually set in the environment. Setting
Expand Down Expand Up @@ -148,7 +141,7 @@ func (a *App) PrintEnv(w io.Writer, verbose bool, osEnv func() []string) error {
return nil
}

// CheckStrictEnv checks that every environment variable that starts with the app name is recognized by the application.
// CheckStrictEnv checks that every environment variable that starts with the App name is recognized by the application.
func (a *App) CheckStrictEnv() error {
for _, envVar := range os.Environ() {
key, _, match := splitVar(a.name, a.separator, envVar)
Expand All @@ -165,47 +158,211 @@ func (a *App) CheckStrictEnv() error {

// CommandClause represents a command clause in a command0-line application.
type CommandClause struct {
*kingpin.CmdClause
*cobra.Command

name string
app *App
App *App
}

func (cmd *CommandClause) BoolVarP(reference *bool, name, shorthand string, def bool, usage string, hasEnv bool, persistent bool) {
if persistent {
cmd.PersistentFlags().BoolVarP(reference, name, shorthand, def, usage)
} else {
cmd.Flags().BoolVarP(reference, name, shorthand, def, usage)
}

if hasEnv {
cmd.FlagRegister(name, usage)
}
}

func (cmd *CommandClause) IntVarP(reference *int, name, shorthand string, def int, usage string, hasEnv bool, persistent bool) {
if persistent {
cmd.PersistentFlags().IntVarP(reference, name, shorthand, def, usage)
} else {
cmd.Flags().IntVarP(reference, name, shorthand, def, usage)
}

if hasEnv {
cmd.FlagRegister(name, usage)
}
}

func (cmd *CommandClause) StringVarP(reference *string, name, shorthand string, def string, usage string, hasEnv bool, persistent bool) {
if persistent {
cmd.PersistentFlags().StringVarP(reference, name, shorthand, def, usage)
} else {
cmd.Flags().StringVarP(reference, name, shorthand, def, usage)
}

if hasEnv {
cmd.FlagRegister(name, usage)
}
}

func (cmd *CommandClause) DurationVarP(reference *time.Duration, name, shorthand string, def time.Duration, usage string, hasEnv bool, persistent bool) {
if persistent {
cmd.PersistentFlags().DurationVarP(reference, name, shorthand, def, usage)
} else {
cmd.Flags().DurationVarP(reference, name, shorthand, def, usage)
}

if hasEnv {
cmd.FlagRegister(name, usage)
}
}

func (cmd *CommandClause) BoolVar(reference *bool, name string, def bool, usage string, hasEnv bool, persistent bool) {
if persistent {
cmd.PersistentFlags().BoolVar(reference, name, def, usage)
} else {
cmd.Flags().BoolVar(reference, name, def, usage)
}

if hasEnv {
cmd.FlagRegister(name, usage)
}
}

func (cmd *CommandClause) IntVar(reference *int, name string, def int, usage string, hasEnv bool, persistent bool) {
if persistent {
cmd.PersistentFlags().IntVar(reference, name, def, usage)
} else {
cmd.Flags().IntVar(reference, name, def, usage)
}

if hasEnv {
cmd.FlagRegister(name, usage)
}
}

func (cmd *CommandClause) StringVar(reference *string, name string, def string, usage string, hasEnv bool, persistent bool) {
if persistent {
cmd.PersistentFlags().StringVar(reference, name, def, usage)
} else {
cmd.Flags().StringVar(reference, name, def, usage)
}

if hasEnv {
cmd.FlagRegister(name, usage)
}
}

func (cmd *CommandClause) DurationVar(reference *time.Duration, name string, def time.Duration, usage string, hasEnv bool, persistent bool) {
if persistent {
cmd.PersistentFlags().DurationVar(reference, name, def, usage)
} else {
cmd.Flags().DurationVar(reference, name, def, usage)
}

if hasEnv {
cmd.FlagRegister(name, usage)
}
}

func (cmd *CommandClause) VarP(reference pflag.Value, name string, shorthand string, usage string, hasEnv bool, persistent bool) {
if persistent {
cmd.PersistentFlags().VarP(reference, name, shorthand, usage)
} else {
cmd.Flags().VarP(reference, name, shorthand, usage)
}

if hasEnv {
cmd.FlagRegister(name, usage)
}
}

func (cmd *CommandClause) Var(reference pflag.Value, name string, usage string, hasEnv bool, persistent bool) {
if persistent {
cmd.PersistentFlags().Var(reference, name, usage)
} else {
cmd.Flags().Var(reference, name, usage)
}

if hasEnv {
cmd.FlagRegister(name, usage)
}
}

func (cmd *CommandClause) VarPF(reference pflag.Value, name string, shorthand string, usage string, hasEnv bool, persistent bool) *pflag.Flag {
var flag *pflag.Flag
if persistent {
flag = cmd.PersistentFlags().VarPF(reference, name, shorthand, usage)
} else {
flag = cmd.Flags().VarPF(reference, name, shorthand, usage)
}

if hasEnv {
cmd.FlagRegister(name, usage)
}
return flag
}

// Command adds a new subcommand to this command.
func (cmd *CommandClause) Command(name, help string) *CommandClause {
return &CommandClause{
CmdClause: cmd.CmdClause.Command(name, help),
name: name,
app: cmd.app,
func (cmd *CommandClause) CreateCommand(name, help string) *CommandClause {
clause := &CommandClause{
Command: func() *cobra.Command {
newCommand := &cobra.Command{Use: name, Short: help}
return newCommand
}(),
name: name,
App: cmd.App,
}
cmd.AddCommand(clause.Command)
return clause
}

// Hidden hides the command in help texts.
func (cmd *CommandClause) Hidden() *CommandClause {
cmd.CmdClause = cmd.CmdClause.Hidden()
cmd.Command.Hidden = true
return cmd
}

func (cmd *CommandClause) FullCommand() string {
if cmd.Use == cmd.Root().Use {
return ""
}
out := []string{cmd.Use}
for p := cmd.Parent(); p != nil; p = p.Parent() {
if p.Use != cmd.Root().Use {
out = append([]string{p.Use}, out...)
}
}
return strings.Join(out, " ")
}

func (cmd *CommandClause) HelpLong(helpLong string) {
cmd.Long = helpLong
}

func (cmd *CommandClause) Alias(alias string) {
if cmd.Aliases == nil {
cmd.Aliases = []string{alias}
} else {
cmd.Aliases = append(cmd.Aliases, alias)
}
}

// Flag defines a new flag with the given long name and help text,
// adding an environment variable default configurable by APP_COMMAND_FLAG_NAME.
// The help text is suffixed with a description of secrthe environment variable default.
func (cmd *CommandClause) Flag(name, help string) *Flag {
fullCmd := strings.Replace(cmd.FullCommand(), " ", cmd.app.separator, -1)
prefix := formatName(fullCmd, cmd.app.name, cmd.app.separator, cmd.app.delimiters...)
envVar := formatName(name, prefix, cmd.app.separator, cmd.app.delimiters...)

cmd.app.registerEnvVar(envVar)
flag := cmd.CmdClause.Flag(name, help).Envar(envVar)
return &Flag{
FlagClause: flag,
app: cmd.app,
envVar: envVar,
}
func (cmd *CommandClause) FlagRegister(name, help string) *Flag {
fullCmd := strings.Replace(cmd.FullCommand(), " ", cmd.App.separator, -1)
prefix := formatName(fullCmd, cmd.App.name, cmd.App.separator, cmd.App.delimiters...)
envVar := formatName(name, prefix, cmd.App.separator, cmd.App.delimiters...)

cmd.App.registerEnvVar(envVar)
flag := cmd.Flag(name)
return (&Flag{
Flag: flag,
app: cmd.App,
envVar: envVar,
}).Envar(envVar)
}

// Flag represents a command-line flag.
type Flag struct {
*kingpin.FlagClause
*pflag.Flag

envVar string
app *App
Expand All @@ -220,29 +377,16 @@ func (f *Flag) Envar(name string) *Flag {
}
f.app.registerEnvVar(name)
f.envVar = name
f.FlagClause = f.FlagClause.Envar(f.envVar)
return f
}

// NoEnvar forces environment variable defaults to be disabled for this flag.
func (f *Flag) NoEnvar() *Flag {
if f.envVar != "" {
f.app.unregisterEnvVar(f.envVar)
}
f.envVar = ""
f.FlagClause = f.FlagClause.NoEnvar()
return f
}

// Hidden hides the flag in help texts.
func (f *Flag) Hidden() *Flag {
f.FlagClause = f.FlagClause.Hidden()
f.Flag.DefValue = os.Getenv(f.envVar)
return f
}

// formatName takes a name and converts it to an uppercased name,
// joined by the given separator and prefixed with the given prefix.
func formatName(name, prefix, separator string, delimiters ...string) string {
if name == "" {
return strings.ToUpper(prefix)
}
for _, delim := range delimiters {
name = strings.Replace(name, delim, separator, -1)
}
Expand Down
Loading