2
2
mirror of https://github.com/octoleo/restic.git synced 2024-06-06 19:10:49 +00:00

Update vendored library github.com/spf13/cobra

This commit is contained in:
Alexander Neumann 2018-03-30 12:43:03 +02:00
parent 917cc542c9
commit 3473d73d0c
26 changed files with 2281 additions and 2266 deletions

4
Gopkg.lock generated
View File

@ -166,8 +166,8 @@
[[projects]] [[projects]]
name = "github.com/spf13/cobra" name = "github.com/spf13/cobra"
packages = [".","doc"] packages = [".","doc"]
revision = "7b2c5ac9fc04fc5efafb60700713d4fa609b777b" revision = "a1f051bc3eba734da4772d60e2d677f47cf93ef4"
version = "v0.0.1" version = "v0.0.2"
[[projects]] [[projects]]
name = "github.com/spf13/pflag" name = "github.com/spf13/pflag"

38
vendor/github.com/spf13/cobra/.circleci/config.yml generated vendored Normal file
View File

@ -0,0 +1,38 @@
workflows:
version: 2
main:
jobs:
- go-current
- go-previous
- go-latest
base: &base
working_directory: /go/src/github.com/spf13/cobra
steps:
- checkout
- run:
name: "All Commands"
command: |
mkdir -p bin
curl -Lso bin/shellcheck https://github.com/caarlos0/shellcheck-docker/releases/download/v0.4.3/shellcheck
chmod +x bin/shellcheck
go get -t -v ./...
PATH=$PATH:$PWD/bin go test -v ./...
go build
diff -u <(echo -n) <(gofmt -d -s .)
if [ -z $NOVET ]; then
diff -u <(echo -n) <(go tool vet . 2>&1 | grep -vE 'ExampleCommand|bash_completions.*Fprint');
fi
version: 2
jobs:
go-current:
docker:
- image: circleci/golang:1.10.0
<<: *base
go-previous:
docker:
- image: circleci/golang:1.9.4
<<: *base
go-latest:
docker:
- image: circleci/golang:latest
<<: *base

View File

@ -2,8 +2,8 @@ language: go
matrix: matrix:
include: include:
- go: 1.7.6 - go: 1.9.4
- go: 1.8.3 - go: 1.10.0
- go: tip - go: tip
allow_failures: allow_failures:
- go: tip - go: tip

View File

@ -20,6 +20,7 @@ Many of the most widely used Go projects are built using Cobra including:
* [Nanobox](https://github.com/nanobox-io/nanobox)/[Nanopack](https://github.com/nanopack) * [Nanobox](https://github.com/nanobox-io/nanobox)/[Nanopack](https://github.com/nanopack)
* [rclone](http://rclone.org/) * [rclone](http://rclone.org/)
* [nehm](https://github.com/bogem/nehm) * [nehm](https://github.com/bogem/nehm)
* [Pouch](https://github.com/alibaba/pouch)
[![Build Status](https://travis-ci.org/spf13/cobra.svg "Travis CI status")](https://travis-ci.org/spf13/cobra) [![Build Status](https://travis-ci.org/spf13/cobra.svg "Travis CI status")](https://travis-ci.org/spf13/cobra)
[![CircleCI status](https://circleci.com/gh/spf13/cobra.png?circle-token=:circle-token "CircleCI status")](https://circleci.com/gh/spf13/cobra) [![CircleCI status](https://circleci.com/gh/spf13/cobra.png?circle-token=:circle-token "CircleCI status")](https://circleci.com/gh/spf13/cobra)
@ -158,10 +159,7 @@ import (
) )
func main() { func main() {
if err := cmd.RootCmd.Execute(); err != nil { cmd.Execute()
fmt.Println(err)
os.Exit(1)
}
} }
``` ```
@ -174,7 +172,7 @@ commands you want. It's the easiest way to incorporate Cobra into your applicati
## Using the Cobra Library ## Using the Cobra Library
To manually implement Cobra you need to create a bare main.go file and a RootCmd file. To manually implement Cobra you need to create a bare main.go file and a rootCmd file.
You will optionally provide additional commands as you see fit. You will optionally provide additional commands as you see fit.
### Create rootCmd ### Create rootCmd
@ -184,7 +182,7 @@ Cobra doesn't require any special constructors. Simply create your commands.
Ideally you place this in app/cmd/root.go: Ideally you place this in app/cmd/root.go:
```go ```go
var RootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: "hugo", Use: "hugo",
Short: "Hugo is a very fast static site generator", Short: "Hugo is a very fast static site generator",
Long: `A Fast and Flexible Static Site Generator built with Long: `A Fast and Flexible Static Site Generator built with
@ -194,6 +192,13 @@ var RootCmd = &cobra.Command{
// Do Stuff Here // Do Stuff Here
}, },
} }
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
``` ```
You will additionally define flags and handle configuration in your init() function. You will additionally define flags and handle configuration in your init() function.
@ -212,22 +217,18 @@ import (
func init() { func init() {
cobra.OnInitialize(initConfig) cobra.OnInitialize(initConfig)
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)") rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)")
RootCmd.PersistentFlags().StringVarP(&projectBase, "projectbase", "b", "", "base project directory eg. github.com/spf13/") rootCmd.PersistentFlags().StringVarP(&projectBase, "projectbase", "b", "", "base project directory eg. github.com/spf13/")
RootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "Author name for copyright attribution") rootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "Author name for copyright attribution")
RootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "Name of license for the project (can provide `licensetext` in config)") rootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "Name of license for the project (can provide `licensetext` in config)")
RootCmd.PersistentFlags().Bool("viper", true, "Use Viper for configuration") rootCmd.PersistentFlags().Bool("viper", true, "Use Viper for configuration")
viper.BindPFlag("author", RootCmd.PersistentFlags().Lookup("author")) viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author"))
viper.BindPFlag("projectbase", RootCmd.PersistentFlags().Lookup("projectbase")) viper.BindPFlag("projectbase", rootCmd.PersistentFlags().Lookup("projectbase"))
viper.BindPFlag("useViper", RootCmd.PersistentFlags().Lookup("viper")) viper.BindPFlag("useViper", rootCmd.PersistentFlags().Lookup("viper"))
viper.SetDefault("author", "NAME HERE <EMAIL ADDRESS>") viper.SetDefault("author", "NAME HERE <EMAIL ADDRESS>")
viper.SetDefault("license", "apache") viper.SetDefault("license", "apache")
} }
func Execute() {
RootCmd.Execute()
}
func initConfig() { func initConfig() {
// Don't forget to read config either from cfgFile or from home directory! // Don't forget to read config either from cfgFile or from home directory!
if cfgFile != "" { if cfgFile != "" {
@ -271,10 +272,7 @@ import (
) )
func main() { func main() {
if err := cmd.RootCmd.Execute(); err != nil { cmd.Execute()
fmt.Println(err)
os.Exit(1)
}
} }
``` ```
@ -290,12 +288,13 @@ populate it with the following:
package cmd package cmd
import ( import (
"github.com/spf13/cobra"
"fmt" "fmt"
"github.com/spf13/cobra"
) )
func init() { func init() {
RootCmd.AddCommand(versionCmd) rootCmd.AddCommand(versionCmd)
} }
var versionCmd = &cobra.Command{ var versionCmd = &cobra.Command{
@ -332,7 +331,7 @@ command it's assigned to as well as every command under that command. For
global flags, assign a flag as a persistent flag on the root. global flags, assign a flag as a persistent flag on the root.
```go ```go
RootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output") rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")
``` ```
### Local Flags ### Local Flags
@ -340,13 +339,13 @@ RootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose out
A flag can also be assigned locally which will only apply to that specific command. A flag can also be assigned locally which will only apply to that specific command.
```go ```go
RootCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from") rootCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from")
``` ```
### Local Flag on Parent Commands ### Local Flag on Parent Commands
By default Cobra only parses local flags on the target command, any local flags on By default Cobra only parses local flags on the target command, any local flags on
parent commands are ignored. By enabling `Command.TraverseChildren` Cobra will parent commands are ignored. By enabling `Command.TraverseChildren` Cobra will
parse local flags on each command before executing the target command. parse local flags on each command before executing the target command.
```go ```go
@ -363,8 +362,8 @@ You can also bind your flags with [viper](https://github.com/spf13/viper):
var author string var author string
func init() { func init() {
RootCmd.PersistentFlags().StringVar(&author, "author", "YOUR NAME", "Author name for copyright attribution") rootCmd.PersistentFlags().StringVar(&author, "author", "YOUR NAME", "Author name for copyright attribution")
viper.BindPFlag("author", RootCmd.PersistentFlags().Lookup("author")) viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author"))
} }
``` ```
@ -374,6 +373,15 @@ when the `--author` flag is not provided by user.
More in [viper documentation](https://github.com/spf13/viper#working-with-flags). More in [viper documentation](https://github.com/spf13/viper#working-with-flags).
### Required flags
Flags are optional by default. If instead you wish your command to report an error
when a flag has not been set, mark it as required:
```go
rootCmd.Flags().StringVarP(&Region, "region", "r", "", "AWS region (required)")
rootCmd.MarkFlagRequired("region")
```
## Positional and Custom Arguments ## Positional and Custom Arguments
Validation of positional arguments can be specified using the `Args` field Validation of positional arguments can be specified using the `Args` field
@ -522,7 +530,7 @@ around it. In fact, you can provide your own if you want.
### Defining your own help ### Defining your own help
You can provide your own Help command or your own template for the default command to use You can provide your own Help command or your own template for the default command to use
with followind functions: with following functions:
```go ```go
cmd.SetHelpCommand(cmd *Command) cmd.SetHelpCommand(cmd *Command)
@ -569,6 +577,13 @@ cmd.SetUsageFunc(f func(*Command) error)
cmd.SetUsageTemplate(s string) cmd.SetUsageTemplate(s string)
``` ```
## Version Flag
Cobra adds a top-level '--version' flag if the Version field is set on the root command.
Running an application with the '--version' flag will print the version to stdout using
the version template. The template can be customized using the
`cmd.SetVersionTemplate(s string)` function.
## PreRun and PostRun Hooks ## PreRun and PostRun Hooks
It is possible to run functions before or after the main `Run` function of your command. The `PersistentPreRun` and `PreRun` functions will be executed before `Run`. `PersistentPostRun` and `PostRun` will be executed after `Run`. The `Persistent*Run` functions will be inherited by children if they do not declare their own. These functions are run in the following order: It is possible to run functions before or after the main `Run` function of your command. The `PersistentPreRun` and `PreRun` functions will be executed before `Run`. `PersistentPostRun` and `PostRun` will be executed after `Run`. The `Persistent*Run` functions will be inherited by children if they do not declare their own. These functions are run in the following order:

View File

@ -16,14 +16,14 @@ func legacyArgs(cmd *Command, args []string) error {
return nil return nil
} }
// root command with subcommands, do subcommand checking // root command with subcommands, do subcommand checking.
if !cmd.HasParent() && len(args) > 0 { if !cmd.HasParent() && len(args) > 0 {
return fmt.Errorf("unknown command %q for %q%s", args[0], cmd.CommandPath(), cmd.findSuggestions(args[0])) return fmt.Errorf("unknown command %q for %q%s", args[0], cmd.CommandPath(), cmd.findSuggestions(args[0]))
} }
return nil return nil
} }
// NoArgs returns an error if any args are included // NoArgs returns an error if any args are included.
func NoArgs(cmd *Command, args []string) error { func NoArgs(cmd *Command, args []string) error {
if len(args) > 0 { if len(args) > 0 {
return fmt.Errorf("unknown command %q for %q", args[0], cmd.CommandPath()) return fmt.Errorf("unknown command %q for %q", args[0], cmd.CommandPath())
@ -31,7 +31,7 @@ func NoArgs(cmd *Command, args []string) error {
return nil return nil
} }
// OnlyValidArgs returns an error if any args are not in the list of ValidArgs // OnlyValidArgs returns an error if any args are not in the list of ValidArgs.
func OnlyValidArgs(cmd *Command, args []string) error { func OnlyValidArgs(cmd *Command, args []string) error {
if len(cmd.ValidArgs) > 0 { if len(cmd.ValidArgs) > 0 {
for _, v := range args { for _, v := range args {
@ -43,21 +43,12 @@ func OnlyValidArgs(cmd *Command, args []string) error {
return nil return nil
} }
func stringInSlice(a string, list []string) bool { // ArbitraryArgs never returns an error.
for _, b := range list {
if b == a {
return true
}
}
return false
}
// ArbitraryArgs never returns an error
func ArbitraryArgs(cmd *Command, args []string) error { func ArbitraryArgs(cmd *Command, args []string) error {
return nil return nil
} }
// MinimumNArgs returns an error if there is not at least N args // MinimumNArgs returns an error if there is not at least N args.
func MinimumNArgs(n int) PositionalArgs { func MinimumNArgs(n int) PositionalArgs {
return func(cmd *Command, args []string) error { return func(cmd *Command, args []string) error {
if len(args) < n { if len(args) < n {
@ -67,7 +58,7 @@ func MinimumNArgs(n int) PositionalArgs {
} }
} }
// MaximumNArgs returns an error if there are more than N args // MaximumNArgs returns an error if there are more than N args.
func MaximumNArgs(n int) PositionalArgs { func MaximumNArgs(n int) PositionalArgs {
return func(cmd *Command, args []string) error { return func(cmd *Command, args []string) error {
if len(args) > n { if len(args) > n {
@ -77,7 +68,7 @@ func MaximumNArgs(n int) PositionalArgs {
} }
} }
// ExactArgs returns an error if there are not exactly n args // ExactArgs returns an error if there are not exactly n args.
func ExactArgs(n int) PositionalArgs { func ExactArgs(n int) PositionalArgs {
return func(cmd *Command, args []string) error { return func(cmd *Command, args []string) error {
if len(args) != n { if len(args) != n {
@ -87,7 +78,7 @@ func ExactArgs(n int) PositionalArgs {
} }
} }
// RangeArgs returns an error if the number of args is not within the expected range // RangeArgs returns an error if the number of args is not within the expected range.
func RangeArgs(min int, max int) PositionalArgs { func RangeArgs(min int, max int) PositionalArgs {
return func(cmd *Command, args []string) error { return func(cmd *Command, args []string) error {
if len(args) < min || len(args) > max { if len(args) < min || len(args) > max {

241
vendor/github.com/spf13/cobra/args_test.go generated vendored Normal file
View File

@ -0,0 +1,241 @@
package cobra
import (
"strings"
"testing"
)
func TestNoArgs(t *testing.T) {
c := &Command{Use: "c", Args: NoArgs, Run: emptyRun}
output, err := executeCommand(c)
if output != "" {
t.Errorf("Unexpected string: %v", output)
}
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
func TestNoArgsWithArgs(t *testing.T) {
c := &Command{Use: "c", Args: NoArgs, Run: emptyRun}
_, err := executeCommand(c, "illegal")
if err == nil {
t.Fatal("Expected an error")
}
got := err.Error()
expected := `unknown command "illegal" for "c"`
if got != expected {
t.Errorf("Expected: %q, got: %q", expected, got)
}
}
func TestOnlyValidArgs(t *testing.T) {
c := &Command{
Use: "c",
Args: OnlyValidArgs,
ValidArgs: []string{"one", "two"},
Run: emptyRun,
}
output, err := executeCommand(c, "one", "two")
if output != "" {
t.Errorf("Unexpected output: %v", output)
}
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
func TestOnlyValidArgsWithInvalidArgs(t *testing.T) {
c := &Command{
Use: "c",
Args: OnlyValidArgs,
ValidArgs: []string{"one", "two"},
Run: emptyRun,
}
_, err := executeCommand(c, "three")
if err == nil {
t.Fatal("Expected an error")
}
got := err.Error()
expected := `invalid argument "three" for "c"`
if got != expected {
t.Errorf("Expected: %q, got: %q", expected, got)
}
}
func TestArbitraryArgs(t *testing.T) {
c := &Command{Use: "c", Args: ArbitraryArgs, Run: emptyRun}
output, err := executeCommand(c, "a", "b")
if output != "" {
t.Errorf("Unexpected output: %v", output)
}
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
}
func TestMinimumNArgs(t *testing.T) {
c := &Command{Use: "c", Args: MinimumNArgs(2), Run: emptyRun}
output, err := executeCommand(c, "a", "b", "c")
if output != "" {
t.Errorf("Unexpected output: %v", output)
}
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
}
func TestMinimumNArgsWithLessArgs(t *testing.T) {
c := &Command{Use: "c", Args: MinimumNArgs(2), Run: emptyRun}
_, err := executeCommand(c, "a")
if err == nil {
t.Fatal("Expected an error")
}
got := err.Error()
expected := "requires at least 2 arg(s), only received 1"
if got != expected {
t.Fatalf("Expected %q, got %q", expected, got)
}
}
func TestMaximumNArgs(t *testing.T) {
c := &Command{Use: "c", Args: MaximumNArgs(3), Run: emptyRun}
output, err := executeCommand(c, "a", "b")
if output != "" {
t.Errorf("Unexpected output: %v", output)
}
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
}
func TestMaximumNArgsWithMoreArgs(t *testing.T) {
c := &Command{Use: "c", Args: MaximumNArgs(2), Run: emptyRun}
_, err := executeCommand(c, "a", "b", "c")
if err == nil {
t.Fatal("Expected an error")
}
got := err.Error()
expected := "accepts at most 2 arg(s), received 3"
if got != expected {
t.Fatalf("Expected %q, got %q", expected, got)
}
}
func TestExactArgs(t *testing.T) {
c := &Command{Use: "c", Args: ExactArgs(3), Run: emptyRun}
output, err := executeCommand(c, "a", "b", "c")
if output != "" {
t.Errorf("Unexpected output: %v", output)
}
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
}
func TestExactArgsWithInvalidCount(t *testing.T) {
c := &Command{Use: "c", Args: ExactArgs(2), Run: emptyRun}
_, err := executeCommand(c, "a", "b", "c")
if err == nil {
t.Fatal("Expected an error")
}
got := err.Error()
expected := "accepts 2 arg(s), received 3"
if got != expected {
t.Fatalf("Expected %q, got %q", expected, got)
}
}
func TestRangeArgs(t *testing.T) {
c := &Command{Use: "c", Args: RangeArgs(2, 4), Run: emptyRun}
output, err := executeCommand(c, "a", "b", "c")
if output != "" {
t.Errorf("Unexpected output: %v", output)
}
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
}
func TestRangeArgsWithInvalidCount(t *testing.T) {
c := &Command{Use: "c", Args: RangeArgs(2, 4), Run: emptyRun}
_, err := executeCommand(c, "a")
if err == nil {
t.Fatal("Expected an error")
}
got := err.Error()
expected := "accepts between 2 and 4 arg(s), received 1"
if got != expected {
t.Fatalf("Expected %q, got %q", expected, got)
}
}
func TestRootTakesNoArgs(t *testing.T) {
rootCmd := &Command{Use: "root", Run: emptyRun}
childCmd := &Command{Use: "child", Run: emptyRun}
rootCmd.AddCommand(childCmd)
_, err := executeCommand(rootCmd, "illegal", "args")
if err == nil {
t.Fatal("Expected an error")
}
got := err.Error()
expected := `unknown command "illegal" for "root"`
if !strings.Contains(got, expected) {
t.Errorf("expected %q, got %q", expected, got)
}
}
func TestRootTakesArgs(t *testing.T) {
rootCmd := &Command{Use: "root", Args: ArbitraryArgs, Run: emptyRun}
childCmd := &Command{Use: "child", Run: emptyRun}
rootCmd.AddCommand(childCmd)
_, err := executeCommand(rootCmd, "legal", "args")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
}
func TestChildTakesNoArgs(t *testing.T) {
rootCmd := &Command{Use: "root", Run: emptyRun}
childCmd := &Command{Use: "child", Args: NoArgs, Run: emptyRun}
rootCmd.AddCommand(childCmd)
_, err := executeCommand(rootCmd, "child", "illegal", "args")
if err == nil {
t.Fatal("Expected an error")
}
got := err.Error()
expected := `unknown command "illegal" for "root child"`
if !strings.Contains(got, expected) {
t.Errorf("expected %q, got %q", expected, got)
}
}
func TestChildTakesArgs(t *testing.T) {
rootCmd := &Command{Use: "root", Run: emptyRun}
childCmd := &Command{Use: "child", Args: ArbitraryArgs, Run: emptyRun}
rootCmd.AddCommand(childCmd)
_, err := executeCommand(rootCmd, "child", "legal", "args")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}

View File

@ -21,8 +21,8 @@ const (
func writePreamble(buf *bytes.Buffer, name string) { func writePreamble(buf *bytes.Buffer, name string) {
buf.WriteString(fmt.Sprintf("# bash completion for %-36s -*- shell-script -*-\n", name)) buf.WriteString(fmt.Sprintf("# bash completion for %-36s -*- shell-script -*-\n", name))
buf.WriteString(` buf.WriteString(fmt.Sprintf(`
__debug() __%[1]s_debug()
{ {
if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then
echo "$*" >> "${BASH_COMP_DEBUG_FILE}" echo "$*" >> "${BASH_COMP_DEBUG_FILE}"
@ -31,13 +31,13 @@ __debug()
# Homebrew on Macs have version 1.3 of bash-completion which doesn't include # Homebrew on Macs have version 1.3 of bash-completion which doesn't include
# _init_completion. This is a very minimal version of that function. # _init_completion. This is a very minimal version of that function.
__my_init_completion() __%[1]s_init_completion()
{ {
COMPREPLY=() COMPREPLY=()
_get_comp_words_by_ref "$@" cur prev words cword _get_comp_words_by_ref "$@" cur prev words cword
} }
__index_of_word() __%[1]s_index_of_word()
{ {
local w word=$1 local w word=$1
shift shift
@ -49,7 +49,7 @@ __index_of_word()
index=-1 index=-1
} }
__contains_word() __%[1]s_contains_word()
{ {
local w word=$1; shift local w word=$1; shift
for w in "$@"; do for w in "$@"; do
@ -58,9 +58,9 @@ __contains_word()
return 1 return 1
} }
__handle_reply() __%[1]s_handle_reply()
{ {
__debug "${FUNCNAME[0]}" __%[1]s_debug "${FUNCNAME[0]}"
case $cur in case $cur in
-*) -*)
if [[ $(type -t compopt) = "builtin" ]]; then if [[ $(type -t compopt) = "builtin" ]]; then
@ -85,7 +85,7 @@ __handle_reply()
local index flag local index flag
flag="${cur%%=*}" flag="${cur%%=*}"
__index_of_word "${flag}" "${flags_with_completion[@]}" __%[1]s_index_of_word "${flag}" "${flags_with_completion[@]}"
COMPREPLY=() COMPREPLY=()
if [[ ${index} -ge 0 ]]; then if [[ ${index} -ge 0 ]]; then
PREFIX="" PREFIX=""
@ -103,7 +103,7 @@ __handle_reply()
# check if we are handling a flag with special work handling # check if we are handling a flag with special work handling
local index local index
__index_of_word "${prev}" "${flags_with_completion[@]}" __%[1]s_index_of_word "${prev}" "${flags_with_completion[@]}"
if [[ ${index} -ge 0 ]]; then if [[ ${index} -ge 0 ]]; then
${flags_completion[${index}]} ${flags_completion[${index}]}
return return
@ -136,24 +136,30 @@ __handle_reply()
if declare -F __ltrim_colon_completions >/dev/null; then if declare -F __ltrim_colon_completions >/dev/null; then
__ltrim_colon_completions "$cur" __ltrim_colon_completions "$cur"
fi fi
# If there is only 1 completion and it is a flag with an = it will be completed
# but we don't want a space after the =
if [[ "${#COMPREPLY[@]}" -eq "1" ]] && [[ $(type -t compopt) = "builtin" ]] && [[ "${COMPREPLY[0]}" == --*= ]]; then
compopt -o nospace
fi
} }
# The arguments should be in the form "ext1|ext2|extn" # The arguments should be in the form "ext1|ext2|extn"
__handle_filename_extension_flag() __%[1]s_handle_filename_extension_flag()
{ {
local ext="$1" local ext="$1"
_filedir "@(${ext})" _filedir "@(${ext})"
} }
__handle_subdirs_in_dir_flag() __%[1]s_handle_subdirs_in_dir_flag()
{ {
local dir="$1" local dir="$1"
pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1
} }
__handle_flag() __%[1]s_handle_flag()
{ {
__debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
# if a command required a flag, and we found it, unset must_have_one_flag() # if a command required a flag, and we found it, unset must_have_one_flag()
local flagname=${words[c]} local flagname=${words[c]}
@ -164,27 +170,30 @@ __handle_flag()
flagname=${flagname%%=*} # strip everything after the = flagname=${flagname%%=*} # strip everything after the =
flagname="${flagname}=" # but put the = back flagname="${flagname}=" # but put the = back
fi fi
__debug "${FUNCNAME[0]}: looking for ${flagname}" __%[1]s_debug "${FUNCNAME[0]}: looking for ${flagname}"
if __contains_word "${flagname}" "${must_have_one_flag[@]}"; then if __%[1]s_contains_word "${flagname}" "${must_have_one_flag[@]}"; then
must_have_one_flag=() must_have_one_flag=()
fi fi
# if you set a flag which only applies to this command, don't show subcommands # if you set a flag which only applies to this command, don't show subcommands
if __contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then if __%[1]s_contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then
commands=() commands=()
fi fi
# keep flag value with flagname as flaghash # keep flag value with flagname as flaghash
if [ -n "${flagvalue}" ] ; then # flaghash variable is an associative array which is only supported in bash > 3.
flaghash[${flagname}]=${flagvalue} if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then
elif [ -n "${words[ $((c+1)) ]}" ] ; then if [ -n "${flagvalue}" ] ; then
flaghash[${flagname}]=${words[ $((c+1)) ]} flaghash[${flagname}]=${flagvalue}
else elif [ -n "${words[ $((c+1)) ]}" ] ; then
flaghash[${flagname}]="true" # pad "true" for bool flag flaghash[${flagname}]=${words[ $((c+1)) ]}
else
flaghash[${flagname}]="true" # pad "true" for bool flag
fi
fi fi
# skip the argument to a two word flag # skip the argument to a two word flag
if __contains_word "${words[c]}" "${two_word_flags[@]}"; then if __%[1]s_contains_word "${words[c]}" "${two_word_flags[@]}"; then
c=$((c+1)) c=$((c+1))
# if we are looking for a flags value, don't show commands # if we are looking for a flags value, don't show commands
if [[ $c -eq $cword ]]; then if [[ $c -eq $cword ]]; then
@ -196,13 +205,13 @@ __handle_flag()
} }
__handle_noun() __%[1]s_handle_noun()
{ {
__debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
if __contains_word "${words[c]}" "${must_have_one_noun[@]}"; then if __%[1]s_contains_word "${words[c]}" "${must_have_one_noun[@]}"; then
must_have_one_noun=() must_have_one_noun=()
elif __contains_word "${words[c]}" "${noun_aliases[@]}"; then elif __%[1]s_contains_word "${words[c]}" "${noun_aliases[@]}"; then
must_have_one_noun=() must_have_one_noun=()
fi fi
@ -210,45 +219,45 @@ __handle_noun()
c=$((c+1)) c=$((c+1))
} }
__handle_command() __%[1]s_handle_command()
{ {
__debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
local next_command local next_command
if [[ -n ${last_command} ]]; then if [[ -n ${last_command} ]]; then
next_command="_${last_command}_${words[c]//:/__}" next_command="_${last_command}_${words[c]//:/__}"
else else
if [[ $c -eq 0 ]]; then if [[ $c -eq 0 ]]; then
next_command="_$(basename "${words[c]//:/__}")" next_command="_%[1]s_root_command"
else else
next_command="_${words[c]//:/__}" next_command="_${words[c]//:/__}"
fi fi
fi fi
c=$((c+1)) c=$((c+1))
__debug "${FUNCNAME[0]}: looking for ${next_command}" __%[1]s_debug "${FUNCNAME[0]}: looking for ${next_command}"
declare -F "$next_command" >/dev/null && $next_command declare -F "$next_command" >/dev/null && $next_command
} }
__handle_word() __%[1]s_handle_word()
{ {
if [[ $c -ge $cword ]]; then if [[ $c -ge $cword ]]; then
__handle_reply __%[1]s_handle_reply
return return
fi fi
__debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
if [[ "${words[c]}" == -* ]]; then if [[ "${words[c]}" == -* ]]; then
__handle_flag __%[1]s_handle_flag
elif __contains_word "${words[c]}" "${commands[@]}"; then elif __%[1]s_contains_word "${words[c]}" "${commands[@]}"; then
__handle_command __%[1]s_handle_command
elif [[ $c -eq 0 ]] && __contains_word "$(basename "${words[c]}")" "${commands[@]}"; then elif [[ $c -eq 0 ]]; then
__handle_command __%[1]s_handle_command
else else
__handle_noun __%[1]s_handle_noun
fi fi
__handle_word __%[1]s_handle_word
} }
`) `, name))
} }
func writePostscript(buf *bytes.Buffer, name string) { func writePostscript(buf *bytes.Buffer, name string) {
@ -260,7 +269,7 @@ func writePostscript(buf *bytes.Buffer, name string) {
if declare -F _init_completion >/dev/null 2>&1; then if declare -F _init_completion >/dev/null 2>&1; then
_init_completion -s || return _init_completion -s || return
else else
__my_init_completion -n "=" || return __%[1]s_init_completion -n "=" || return
fi fi
local c=0 local c=0
@ -269,13 +278,13 @@ func writePostscript(buf *bytes.Buffer, name string) {
local local_nonpersistent_flags=() local local_nonpersistent_flags=()
local flags_with_completion=() local flags_with_completion=()
local flags_completion=() local flags_completion=()
local commands=("%s") local commands=("%[1]s")
local must_have_one_flag=() local must_have_one_flag=()
local must_have_one_noun=() local must_have_one_noun=()
local last_command local last_command
local nouns=() local nouns=()
__handle_word __%[1]s_handle_word
} }
`, name)) `, name))
@ -300,7 +309,7 @@ func writeCommands(buf *bytes.Buffer, cmd *Command) {
buf.WriteString("\n") buf.WriteString("\n")
} }
func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]string) { func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]string, cmd *Command) {
for key, value := range annotations { for key, value := range annotations {
switch key { switch key {
case BashCompFilenameExt: case BashCompFilenameExt:
@ -308,7 +317,7 @@ func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]s
var ext string var ext string
if len(value) > 0 { if len(value) > 0 {
ext = "__handle_filename_extension_flag " + strings.Join(value, "|") ext = fmt.Sprintf("__%s_handle_filename_extension_flag ", cmd.Root().Name()) + strings.Join(value, "|")
} else { } else {
ext = "_filedir" ext = "_filedir"
} }
@ -326,7 +335,7 @@ func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]s
var ext string var ext string
if len(value) == 1 { if len(value) == 1 {
ext = "__handle_subdirs_in_dir_flag " + value[0] ext = fmt.Sprintf("__%s_handle_subdirs_in_dir_flag ", cmd.Root().Name()) + value[0]
} else { } else {
ext = "_filedir -d" ext = "_filedir -d"
} }
@ -335,7 +344,7 @@ func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]s
} }
} }
func writeShortFlag(buf *bytes.Buffer, flag *pflag.Flag) { func writeShortFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) {
name := flag.Shorthand name := flag.Shorthand
format := " " format := " "
if len(flag.NoOptDefVal) == 0 { if len(flag.NoOptDefVal) == 0 {
@ -343,10 +352,10 @@ func writeShortFlag(buf *bytes.Buffer, flag *pflag.Flag) {
} }
format += "flags+=(\"-%s\")\n" format += "flags+=(\"-%s\")\n"
buf.WriteString(fmt.Sprintf(format, name)) buf.WriteString(fmt.Sprintf(format, name))
writeFlagHandler(buf, "-"+name, flag.Annotations) writeFlagHandler(buf, "-"+name, flag.Annotations, cmd)
} }
func writeFlag(buf *bytes.Buffer, flag *pflag.Flag) { func writeFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) {
name := flag.Name name := flag.Name
format := " flags+=(\"--%s" format := " flags+=(\"--%s"
if len(flag.NoOptDefVal) == 0 { if len(flag.NoOptDefVal) == 0 {
@ -354,7 +363,7 @@ func writeFlag(buf *bytes.Buffer, flag *pflag.Flag) {
} }
format += "\")\n" format += "\")\n"
buf.WriteString(fmt.Sprintf(format, name)) buf.WriteString(fmt.Sprintf(format, name))
writeFlagHandler(buf, "--"+name, flag.Annotations) writeFlagHandler(buf, "--"+name, flag.Annotations, cmd)
} }
func writeLocalNonPersistentFlag(buf *bytes.Buffer, flag *pflag.Flag) { func writeLocalNonPersistentFlag(buf *bytes.Buffer, flag *pflag.Flag) {
@ -380,9 +389,9 @@ func writeFlags(buf *bytes.Buffer, cmd *Command) {
if nonCompletableFlag(flag) { if nonCompletableFlag(flag) {
return return
} }
writeFlag(buf, flag) writeFlag(buf, flag, cmd)
if len(flag.Shorthand) > 0 { if len(flag.Shorthand) > 0 {
writeShortFlag(buf, flag) writeShortFlag(buf, flag, cmd)
} }
if localNonPersistentFlags.Lookup(flag.Name) != nil { if localNonPersistentFlags.Lookup(flag.Name) != nil {
writeLocalNonPersistentFlag(buf, flag) writeLocalNonPersistentFlag(buf, flag)
@ -392,9 +401,9 @@ func writeFlags(buf *bytes.Buffer, cmd *Command) {
if nonCompletableFlag(flag) { if nonCompletableFlag(flag) {
return return
} }
writeFlag(buf, flag) writeFlag(buf, flag, cmd)
if len(flag.Shorthand) > 0 { if len(flag.Shorthand) > 0 {
writeShortFlag(buf, flag) writeShortFlag(buf, flag, cmd)
} }
}) })
@ -452,7 +461,13 @@ func gen(buf *bytes.Buffer, cmd *Command) {
commandName := cmd.CommandPath() commandName := cmd.CommandPath()
commandName = strings.Replace(commandName, " ", "_", -1) commandName = strings.Replace(commandName, " ", "_", -1)
commandName = strings.Replace(commandName, ":", "__", -1) commandName = strings.Replace(commandName, ":", "__", -1)
buf.WriteString(fmt.Sprintf("_%s()\n{\n", commandName))
if cmd.Root() == cmd {
buf.WriteString(fmt.Sprintf("_%s_root_command()\n{\n", commandName))
} else {
buf.WriteString(fmt.Sprintf("_%s()\n{\n", commandName))
}
buf.WriteString(fmt.Sprintf(" last_command=%q\n", commandName)) buf.WriteString(fmt.Sprintf(" last_command=%q\n", commandName))
writeCommands(buf, cmd) writeCommands(buf, cmd)
writeFlags(buf, cmd) writeFlags(buf, cmd)
@ -491,17 +506,20 @@ func (c *Command) GenBashCompletionFile(filename string) error {
return c.GenBashCompletion(outFile) return c.GenBashCompletion(outFile)
} }
// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag, if it exists. // 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 { func (c *Command) MarkFlagRequired(name string) error {
return MarkFlagRequired(c.Flags(), name) return MarkFlagRequired(c.Flags(), name)
} }
// MarkPersistentFlagRequired adds the BashCompOneRequiredFlag annotation to the named persistent flag, if it exists. // 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 { func (c *Command) MarkPersistentFlagRequired(name string) error {
return MarkFlagRequired(c.PersistentFlags(), name) return MarkFlagRequired(c.PersistentFlags(), name)
} }
// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag in the flag set, if it exists. // 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 { func MarkFlagRequired(flags *pflag.FlagSet, name string) error {
return flags.SetAnnotation(name, BashCompOneRequiredFlag, []string{"true"}) return flags.SetAnnotation(name, BashCompOneRequiredFlag, []string{"true"})
} }

View File

@ -6,15 +6,16 @@ Generating bash completions from a cobra command is incredibly easy. An actual p
package main package main
import ( import (
"io/ioutil" "io/ioutil"
"os" "os"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd" "k8s.io/kubernetes/pkg/kubectl/cmd"
"k8s.io/kubernetes/pkg/kubectl/cmd/util"
) )
func main() { func main() {
kubectl := cmd.NewFactory(nil).NewKubectlCommand(os.Stdin, ioutil.Discard, ioutil.Discard) kubectl := cmd.NewKubectlCommand(util.NewFactory(nil), os.Stdin, ioutil.Discard, ioutil.Discard)
kubectl.GenBashCompletionFile("out.sh") kubectl.GenBashCompletionFile("out.sh")
} }
``` ```
@ -173,9 +174,9 @@ hello.yml test.json
So while there are many other files in the CWD it only shows me subdirs and those with valid extensions. So while there are many other files in the CWD it only shows me subdirs and those with valid extensions.
# Specifiy custom flag completion # Specify custom flag completion
Similar to the filename completion and filtering using cobra.BashCompFilenameExt, you can specifiy Similar to the filename completion and filtering using cobra.BashCompFilenameExt, you can specify
a custom flag completion function with cobra.BashCompCustom: a custom flag completion function with cobra.BashCompCustom:
```go ```go
@ -204,3 +205,17 @@ __kubectl_get_namespaces()
fi fi
} }
``` ```
# Using bash aliases for commands
You can also configure the `bash aliases` for the commands and they will also support completions.
```bash
alias aliasname=origcommand
complete -o default -F __start_origcommand aliasname
# and now when you run `aliasname` completion will make
# suggestions as it did for `origcommand`.
$) aliasname <tab><tab>
completion firstcommand secondcommand
```

View File

@ -2,21 +2,33 @@ package cobra
import ( import (
"bytes" "bytes"
"fmt"
"os" "os"
"os/exec" "os/exec"
"regexp"
"strings" "strings"
"testing" "testing"
) )
func checkOmit(t *testing.T, found, unexpected string) { func checkOmit(t *testing.T, found, unexpected string) {
if strings.Contains(found, unexpected) { if strings.Contains(found, unexpected) {
t.Errorf("Unexpected response.\nGot: %q\nBut should not have!\n", unexpected) t.Errorf("Got: %q\nBut should not have!\n", unexpected)
} }
} }
func check(t *testing.T, found, expected string) { func check(t *testing.T, found, expected string) {
if !strings.Contains(found, expected) { if !strings.Contains(found, expected) {
t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found) t.Errorf("Expecting to contain: \n %q\nGot:\n %q\n", expected, found)
}
}
func checkRegex(t *testing.T, found, pattern string) {
matched, err := regexp.MatchString(pattern, found)
if err != nil {
t.Errorf("Error thrown performing MatchString: \n %s\n", err)
}
if !matched {
t.Errorf("Expecting to match: \n %q\nGot:\n %q\n", pattern, found)
} }
} }
@ -33,162 +45,173 @@ func runShellCheck(s string) error {
return err return err
} }
go func() { go func() {
defer stdin.Close()
stdin.Write([]byte(s)) stdin.Write([]byte(s))
stdin.Close()
}() }()
return cmd.Run() return cmd.Run()
} }
// World worst custom function, just keep telling you to enter hello! // World worst custom function, just keep telling you to enter hello!
const ( const bashCompletionFunc = `__custom_func() {
bashCompletionFunc = `__custom_func() { COMPREPLY=( "hello" )
COMPREPLY=( "hello" )
} }
` `
)
func TestBashCompletions(t *testing.T) { func TestBashCompletions(t *testing.T) {
c := initializeWithRootCmd() rootCmd := &Command{
cmdEcho.AddCommand(cmdTimes) Use: "root",
c.AddCommand(cmdEcho, cmdPrint, cmdDeprecated, cmdColon) ArgAliases: []string{"pods", "nodes", "services", "replicationcontrollers", "po", "no", "svc", "rc"},
ValidArgs: []string{"pod", "node", "service", "replicationcontroller"},
BashCompletionFunction: bashCompletionFunc,
Run: emptyRun,
}
rootCmd.Flags().IntP("introot", "i", -1, "help message for flag introot")
rootCmd.MarkFlagRequired("introot")
// custom completion function // Filename.
c.BashCompletionFunction = bashCompletionFunc rootCmd.Flags().String("filename", "", "Enter a filename")
rootCmd.MarkFlagFilename("filename", "json", "yaml", "yml")
// required flag // Persistent filename.
c.MarkFlagRequired("introot") rootCmd.PersistentFlags().String("persistent-filename", "", "Enter a filename")
rootCmd.MarkPersistentFlagFilename("persistent-filename")
rootCmd.MarkPersistentFlagRequired("persistent-filename")
// valid nouns // Filename extensions.
validArgs := []string{"pod", "node", "service", "replicationcontroller"} rootCmd.Flags().String("filename-ext", "", "Enter a filename (extension limited)")
c.ValidArgs = validArgs rootCmd.MarkFlagFilename("filename-ext")
rootCmd.Flags().String("custom", "", "Enter a filename (extension limited)")
rootCmd.MarkFlagCustom("custom", "__complete_custom")
// noun aliases // Subdirectories in a given directory.
argAliases := []string{"pods", "nodes", "services", "replicationcontrollers", "po", "no", "svc", "rc"} rootCmd.Flags().String("theme", "", "theme to use (located in /themes/THEMENAME/)")
c.ArgAliases = argAliases rootCmd.Flags().SetAnnotation("theme", BashCompSubdirsInDir, []string{"themes"})
// filename echoCmd := &Command{
var flagval string Use: "echo [string to echo]",
c.Flags().StringVar(&flagval, "filename", "", "Enter a filename") Aliases: []string{"say"},
c.MarkFlagFilename("filename", "json", "yaml", "yml") Short: "Echo anything to the screen",
Long: "an utterly useless command for testing.",
Example: "Just run cobra-test echo",
Run: emptyRun,
}
// persistent filename echoCmd.Flags().String("filename", "", "Enter a filename")
var flagvalPersistent string echoCmd.MarkFlagFilename("filename", "json", "yaml", "yml")
c.PersistentFlags().StringVar(&flagvalPersistent, "persistent-filename", "", "Enter a filename") echoCmd.Flags().String("config", "", "config to use (located in /config/PROFILE/)")
c.MarkPersistentFlagFilename("persistent-filename") echoCmd.Flags().SetAnnotation("config", BashCompSubdirsInDir, []string{"config"})
c.MarkPersistentFlagRequired("persistent-filename")
// filename extensions printCmd := &Command{
var flagvalExt string Use: "print [string to print]",
c.Flags().StringVar(&flagvalExt, "filename-ext", "", "Enter a filename (extension limited)") Args: MinimumNArgs(1),
c.MarkFlagFilename("filename-ext") Short: "Print anything to the screen",
Long: "an absolutely utterly useless command for testing.",
Run: emptyRun,
}
// filename extensions deprecatedCmd := &Command{
var flagvalCustom string Use: "deprecated [can't do anything here]",
c.Flags().StringVar(&flagvalCustom, "custom", "", "Enter a filename (extension limited)") Args: NoArgs,
c.MarkFlagCustom("custom", "__complete_custom") Short: "A command which is deprecated",
Long: "an absolutely utterly useless command for testing deprecation!.",
Deprecated: "Please use echo instead",
Run: emptyRun,
}
// subdirectories in a given directory colonCmd := &Command{
var flagvalTheme string Use: "cmd:colon",
c.Flags().StringVar(&flagvalTheme, "theme", "", "theme to use (located in /themes/THEMENAME/)") Run: emptyRun,
c.Flags().SetAnnotation("theme", BashCompSubdirsInDir, []string{"themes"}) }
out := new(bytes.Buffer) timesCmd := &Command{
c.GenBashCompletion(out) Use: "times [# times] [string to echo]",
str := out.String() SuggestFor: []string{"counts"},
Args: OnlyValidArgs,
ValidArgs: []string{"one", "two", "three", "four"},
Short: "Echo anything to the screen more times",
Long: "a slightly useless command for testing.",
Run: emptyRun,
}
check(t, str, "_cobra-test") echoCmd.AddCommand(timesCmd)
check(t, str, "_cobra-test_echo") rootCmd.AddCommand(echoCmd, printCmd, deprecatedCmd, colonCmd)
check(t, str, "_cobra-test_echo_times")
check(t, str, "_cobra-test_print") buf := new(bytes.Buffer)
check(t, str, "_cobra-test_cmd__colon") rootCmd.GenBashCompletion(buf)
output := buf.String()
check(t, output, "_root")
check(t, output, "_root_echo")
check(t, output, "_root_echo_times")
check(t, output, "_root_print")
check(t, output, "_root_cmd__colon")
// check for required flags // check for required flags
check(t, str, `must_have_one_flag+=("--introot=")`) check(t, output, `must_have_one_flag+=("--introot=")`)
check(t, str, `must_have_one_flag+=("--persistent-filename=")`) check(t, output, `must_have_one_flag+=("--persistent-filename=")`)
// check for custom completion function // check for custom completion function
check(t, str, `COMPREPLY=( "hello" )`) check(t, output, `COMPREPLY=( "hello" )`)
// check for required nouns // check for required nouns
check(t, str, `must_have_one_noun+=("pod")`) check(t, output, `must_have_one_noun+=("pod")`)
// check for noun aliases // check for noun aliases
check(t, str, `noun_aliases+=("pods")`) check(t, output, `noun_aliases+=("pods")`)
check(t, str, `noun_aliases+=("rc")`) check(t, output, `noun_aliases+=("rc")`)
checkOmit(t, str, `must_have_one_noun+=("pods")`) checkOmit(t, output, `must_have_one_noun+=("pods")`)
// check for filename extension flags // check for filename extension flags
check(t, str, `flags_completion+=("_filedir")`) check(t, output, `flags_completion+=("_filedir")`)
// check for filename extension flags // check for filename extension flags
check(t, str, `must_have_one_noun+=("three")`) check(t, output, `must_have_one_noun+=("three")`)
// check for filename extension flags // check for filename extension flags
check(t, str, `flags_completion+=("__handle_filename_extension_flag json|yaml|yml")`) check(t, output, fmt.Sprintf(`flags_completion+=("__%s_handle_filename_extension_flag json|yaml|yml")`, rootCmd.Name()))
// check for filename extension flags in a subcommand
checkRegex(t, output, fmt.Sprintf(`_root_echo\(\)\n{[^}]*flags_completion\+=\("__%s_handle_filename_extension_flag json\|yaml\|yml"\)`, rootCmd.Name()))
// check for custom flags // check for custom flags
check(t, str, `flags_completion+=("__complete_custom")`) check(t, output, `flags_completion+=("__complete_custom")`)
// check for subdirs_in_dir flags // check for subdirs_in_dir flags
check(t, str, `flags_completion+=("__handle_subdirs_in_dir_flag themes")`) check(t, output, fmt.Sprintf(`flags_completion+=("__%s_handle_subdirs_in_dir_flag themes")`, rootCmd.Name()))
// check for subdirs_in_dir flags in a subcommand
checkRegex(t, output, fmt.Sprintf(`_root_echo\(\)\n{[^}]*flags_completion\+=\("__%s_handle_subdirs_in_dir_flag config"\)`, rootCmd.Name()))
checkOmit(t, str, cmdDeprecated.Name()) checkOmit(t, output, deprecatedCmd.Name())
// if available, run shellcheck against the script // If available, run shellcheck against the script.
if err := exec.Command("which", "shellcheck").Run(); err != nil { if err := exec.Command("which", "shellcheck").Run(); err != nil {
return return
} }
err := runShellCheck(str) if err := runShellCheck(output); err != nil {
if err != nil {
t.Fatalf("shellcheck failed: %v", err) t.Fatalf("shellcheck failed: %v", err)
} }
} }
func TestBashCompletionHiddenFlag(t *testing.T) { func TestBashCompletionHiddenFlag(t *testing.T) {
var cmdTrue = &Command{ c := &Command{Use: "c", Run: emptyRun}
Use: "does nothing",
Run: func(cmd *Command, args []string) {},
}
const flagName = "hidden-foo-bar-baz" const flagName = "hiddenFlag"
c.Flags().Bool(flagName, false, "")
c.Flags().MarkHidden(flagName)
var flagValue bool buf := new(bytes.Buffer)
cmdTrue.Flags().BoolVar(&flagValue, flagName, false, "hidden flag") c.GenBashCompletion(buf)
cmdTrue.Flags().MarkHidden(flagName) output := buf.String()
out := new(bytes.Buffer) if strings.Contains(output, flagName) {
cmdTrue.GenBashCompletion(out) t.Errorf("Expected completion to not include %q flag: Got %v", flagName, output)
bashCompletion := out.String()
if strings.Contains(bashCompletion, flagName) {
t.Errorf("expected completion to not include %q flag: Got %v", flagName, bashCompletion)
} }
} }
func TestBashCompletionDeprecatedFlag(t *testing.T) { func TestBashCompletionDeprecatedFlag(t *testing.T) {
var cmdTrue = &Command{ c := &Command{Use: "c", Run: emptyRun}
Use: "does nothing",
Run: func(cmd *Command, args []string) {},
}
const flagName = "deprecated-foo-bar-baz" const flagName = "deprecated-flag"
c.Flags().Bool(flagName, false, "")
var flagValue bool c.Flags().MarkDeprecated(flagName, "use --not-deprecated instead")
cmdTrue.Flags().BoolVar(&flagValue, flagName, false, "hidden flag")
cmdTrue.Flags().MarkDeprecated(flagName, "use --does-not-exist instead")
out := new(bytes.Buffer)
cmdTrue.GenBashCompletion(out)
bashCompletion := out.String()
if strings.Contains(bashCompletion, flagName) {
t.Errorf("expected completion to not include %q flag: Got %v", flagName, bashCompletion)
}
}
func BenchmarkBashCompletion(b *testing.B) {
c := initializeWithRootCmd()
cmdEcho.AddCommand(cmdTimes)
c.AddCommand(cmdEcho, cmdPrint, cmdDeprecated, cmdColon)
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
c.GenBashCompletion(buf)
output := buf.String()
b.ResetTimer() if strings.Contains(output, flagName) {
for i := 0; i < b.N; i++ { t.Errorf("expected completion to not include %q flag: Got %v", flagName, output)
buf.Reset()
if err := c.GenBashCompletion(buf); err != nil {
b.Fatal(err)
}
} }
} }

View File

@ -70,7 +70,8 @@ func AddTemplateFuncs(tmplFuncs template.FuncMap) {
} }
} }
// OnInitialize takes a series of func() arguments and appends them to a slice of func(). // OnInitialize sets the passed functions to be run when each command's
// Execute method is called.
func OnInitialize(y ...func()) { func OnInitialize(y ...func()) {
initializers = append(initializers, y...) initializers = append(initializers, y...)
} }
@ -188,3 +189,12 @@ func ld(s, t string, ignoreCase bool) int {
} }
return d[len(s)][len(t)] return d[len(s)][len(t)]
} }
func stringInSlice(a string, list []string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}

View File

@ -24,7 +24,7 @@ import (
func init() { func init() {
addCmd.Flags().StringVarP(&packageName, "package", "t", "", "target package name (e.g. github.com/spf13/hugo)") addCmd.Flags().StringVarP(&packageName, "package", "t", "", "target package name (e.g. github.com/spf13/hugo)")
addCmd.Flags().StringVarP(&parentName, "parent", "p", "RootCmd", "variable name of parent command for this command") addCmd.Flags().StringVarP(&parentName, "parent", "p", "rootCmd", "variable name of parent command for this command")
} }
var packageName, parentName string var packageName, parentName string
@ -35,7 +35,7 @@ var addCmd = &cobra.Command{
Short: "Add a command to a Cobra Application", Short: "Add a command to a Cobra Application",
Long: `Add (cobra add) will create a new command, with a license and Long: `Add (cobra add) will create a new command, with a license and
the appropriate structure for a Cobra-based CLI application, the appropriate structure for a Cobra-based CLI application,
and register it to its parent (default RootCmd). and register it to its parent (default rootCmd).
If you want your command to be public, pass in the command name If you want your command to be public, pass in the command name
with an initial uppercase letter. with an initial uppercase letter.

View File

@ -24,7 +24,6 @@ import (
"text/template" "text/template"
) )
var cmdDirs = [...]string{"cmd", "cmds", "command", "commands"}
var srcPaths []string var srcPaths []string
func init() { func init() {
@ -128,8 +127,6 @@ func writeStringToFile(path string, s string) error {
// writeToFile writes r to file with path only // writeToFile writes r to file with path only
// if file/directory on given path doesn't exist. // if file/directory on given path doesn't exist.
// If file/directory exists on given path, then
// it terminates app and prints an appropriate error.
func writeToFile(path string, r io.Reader) error { func writeToFile(path string, r io.Reader) error {
if exists(path) { if exists(path) {
return fmt.Errorf("%v already exists", path) return fmt.Errorf("%v already exists", path)

View File

@ -150,8 +150,8 @@ import (
var cfgFile string{{end}} var cfgFile string{{end}}
// RootCmd represents the base command when called without any subcommands // rootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: "{{.appName}}", Use: "{{.appName}}",
Short: "A brief description of your application", Short: "A brief description of your application",
Long: ` + "`" + `A longer description that spans multiple lines and likely contains Long: ` + "`" + `A longer description that spans multiple lines and likely contains
@ -168,24 +168,24 @@ to quickly create a Cobra application.` + "`" + `,
// Execute adds all child commands to the root command and sets flags appropriately. // Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd. // This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() { func Execute() {
if err := RootCmd.Execute(); err != nil { if err := rootCmd.Execute(); err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
} }
} }
func init() { {{if .viper}} func init() { {{- if .viper}}
cobra.OnInitialize(initConfig) cobra.OnInitialize(initConfig)
{{end}} {{end}}
// Here you will define your flags and configuration settings. // Here you will define your flags and configuration settings.
// Cobra supports persistent flags, which, if defined here, // Cobra supports persistent flags, which, if defined here,
// will be global for your application.{{ if .viper }} // will be global for your application.{{ if .viper }}
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.{{ .appName }}.yaml)"){{ else }} rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.{{ .appName }}.yaml)"){{ else }}
// RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.{{ .appName }}.yaml)"){{ end }} // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.{{ .appName }}.yaml)"){{ end }}
// Cobra also supports local flags, which will only run // Cobra also supports local flags, which will only run
// when this action is called directly. // when this action is called directly.
RootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}{{ if .viper }} }{{ if .viper }}
// initConfig reads in config file and ENV variables if set. // initConfig reads in config file and ENV variables if set.

View File

@ -17,10 +17,9 @@ type Project struct {
} }
// NewProject returns Project with specified project name. // NewProject returns Project with specified project name.
// If projectName is blank string, it returns nil.
func NewProject(projectName string) *Project { func NewProject(projectName string) *Project {
if projectName == "" { if projectName == "" {
return nil er("can't create project with blank name")
} }
p := new(Project) p := new(Project)
@ -54,8 +53,6 @@ func NewProject(projectName string) *Project {
} }
// findPackage returns full path to existing go package in GOPATHs. // findPackage returns full path to existing go package in GOPATHs.
// findPackage returns "", if it can't find path.
// If packageName is "", findPackage returns "".
func findPackage(packageName string) string { func findPackage(packageName string) string {
if packageName == "" { if packageName == "" {
return "" return ""
@ -73,16 +70,29 @@ func findPackage(packageName string) string {
// NewProjectFromPath returns Project with specified absolute path to // NewProjectFromPath returns Project with specified absolute path to
// package. // package.
// If absPath is blank string or if absPath is not actually absolute,
// it returns nil.
func NewProjectFromPath(absPath string) *Project { func NewProjectFromPath(absPath string) *Project {
if absPath == "" || !filepath.IsAbs(absPath) { if absPath == "" {
return nil er("can't create project: absPath can't be blank")
}
if !filepath.IsAbs(absPath) {
er("can't create project: absPath is not absolute")
}
// If absPath is symlink, use its destination.
fi, err := os.Lstat(absPath)
if err != nil {
er("can't read path info: " + err.Error())
}
if fi.Mode()&os.ModeSymlink != 0 {
path, err := os.Readlink(absPath)
if err != nil {
er("can't read the destination of symlink: " + err.Error())
}
absPath = path
} }
p := new(Project) p := new(Project)
p.absPath = absPath p.absPath = strings.TrimSuffix(absPath, findCmdDir(absPath))
p.absPath = strings.TrimSuffix(p.absPath, findCmdDir(p.absPath))
p.name = filepath.ToSlash(trimSrcPath(p.absPath, p.SrcPath())) p.name = filepath.ToSlash(trimSrcPath(p.absPath, p.SrcPath()))
return p return p
} }
@ -91,7 +101,7 @@ func NewProjectFromPath(absPath string) *Project {
func trimSrcPath(absPath, srcPath string) string { func trimSrcPath(absPath, srcPath string) string {
relPath, err := filepath.Rel(srcPath, absPath) relPath, err := filepath.Rel(srcPath, absPath)
if err != nil { if err != nil {
er("Cobra supports project only within $GOPATH: " + err.Error()) er(err)
} }
return relPath return relPath
} }
@ -101,7 +111,6 @@ func (p *Project) License() License {
if p.license.Text == "" && p.license.Name != "None" { if p.license.Text == "" && p.license.Name != "None" {
p.license = getLicense() p.license = getLicense()
} }
return p.license return p.license
} }
@ -111,8 +120,6 @@ func (p Project) Name() string {
} }
// CmdPath returns absolute path to directory, where all commands are located. // CmdPath returns absolute path to directory, where all commands are located.
//
// CmdPath returns blank string, only if p.AbsPath() is a blank string.
func (p *Project) CmdPath() string { func (p *Project) CmdPath() string {
if p.absPath == "" { if p.absPath == "" {
return "" return ""
@ -125,8 +132,6 @@ func (p *Project) CmdPath() string {
// findCmdDir checks if base of absPath is cmd dir and returns it or // findCmdDir checks if base of absPath is cmd dir and returns it or
// looks for existing cmd dir in absPath. // looks for existing cmd dir in absPath.
// If the cmd dir doesn't exist, empty, or cannot be found,
// it returns "cmd".
func findCmdDir(absPath string) string { func findCmdDir(absPath string) string {
if !exists(absPath) || isEmpty(absPath) { if !exists(absPath) || isEmpty(absPath) {
return "cmd" return "cmd"
@ -149,7 +154,7 @@ func findCmdDir(absPath string) string {
// isCmdDir checks if base of name is one of cmdDir. // isCmdDir checks if base of name is one of cmdDir.
func isCmdDir(name string) bool { func isCmdDir(name string) bool {
name = filepath.Base(name) name = filepath.Base(name)
for _, cmdDir := range cmdDirs { for _, cmdDir := range []string{"cmd", "cmds", "command", "commands"} {
if name == cmdDir { if name == cmdDir {
return true return true
} }

View File

@ -25,8 +25,8 @@ import (
var cfgFile string var cfgFile string
// RootCmd represents the base command when called without any subcommands // rootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: "testproject", Use: "testproject",
Short: "A brief description of your application", Short: "A brief description of your application",
Long: `A longer description that spans multiple lines and likely contains Long: `A longer description that spans multiple lines and likely contains
@ -43,23 +43,23 @@ to quickly create a Cobra application.`,
// Execute adds all child commands to the root command and sets flags appropriately. // Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd. // This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() { func Execute() {
if err := RootCmd.Execute(); err != nil { if err := rootCmd.Execute(); err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
} }
} }
func init() { func init() {
cobra.OnInitialize(initConfig) cobra.OnInitialize(initConfig)
// Here you will define your flags and configuration settings. // Here you will define your flags and configuration settings.
// Cobra supports persistent flags, which, if defined here, // Cobra supports persistent flags, which, if defined here,
// will be global for your application. // will be global for your application.
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.testproject.yaml)") rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.testproject.yaml)")
// Cobra also supports local flags, which will only run // Cobra also supports local flags, which will only run
// when this action is called directly. // when this action is called directly.
RootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
} }
// initConfig reads in config file and ENV variables if set. // initConfig reads in config file and ENV variables if set.

View File

@ -36,7 +36,7 @@ to quickly create a Cobra application.`,
} }
func init() { func init() {
RootCmd.AddCommand(testCmd) rootCmd.AddCommand(testCmd)
// Here you will define your flags and configuration settings. // Here you will define your flags and configuration settings.

File diff suppressed because it is too large Load Diff

View File

@ -75,6 +75,11 @@ type Command struct {
// group commands. // group commands.
Annotations map[string]string Annotations map[string]string
// Version defines the version for this command. If this value is non-empty and the command does not
// define a "version" flag, a "version" boolean flag will be added to the command and, if specified,
// will print content of the "Version" variable.
Version string
// The *Run functions are executed in the following order: // The *Run functions are executed in the following order:
// * PersistentPreRun() // * PersistentPreRun()
// * PreRun() // * PreRun()
@ -118,6 +123,10 @@ type Command struct {
// will be printed by generating docs for this command. // will be printed by generating docs for this command.
DisableAutoGenTag bool DisableAutoGenTag bool
// DisableFlagsInUseLine will disable the addition of [flags] to the usage
// line of a command when printing help or generating docs
DisableFlagsInUseLine bool
// DisableSuggestions disables the suggestions based on Levenshtein distance // DisableSuggestions disables the suggestions based on Levenshtein distance
// that go along with 'unknown command' messages. // that go along with 'unknown command' messages.
DisableSuggestions bool DisableSuggestions bool
@ -138,6 +147,11 @@ type Command struct {
commandsMaxNameLen int commandsMaxNameLen int
// commandsAreSorted defines, if command slice are sorted or not. // commandsAreSorted defines, if command slice are sorted or not.
commandsAreSorted bool commandsAreSorted bool
// commandCalledAs is the name or alias value used to call this command.
commandCalledAs struct {
name string
called bool
}
// args is actual args parsed from flags. // args is actual args parsed from flags.
args []string args []string
@ -173,6 +187,8 @@ type Command struct {
// helpCommand is command with usage 'help'. If it's not defined by user, // helpCommand is command with usage 'help'. If it's not defined by user,
// cobra uses default help command. // cobra uses default help command.
helpCommand *Command helpCommand *Command
// versionTemplate is the version template defined by user.
versionTemplate string
} }
// SetArgs sets arguments for the command. It is set to os.Args[1:] by default, if desired, can be overridden // SetArgs sets arguments for the command. It is set to os.Args[1:] by default, if desired, can be overridden
@ -218,6 +234,11 @@ func (c *Command) SetHelpTemplate(s string) {
c.helpTemplate = s c.helpTemplate = s
} }
// SetVersionTemplate sets version template to be used. Application can use it to set custom template.
func (c *Command) SetVersionTemplate(s string) {
c.versionTemplate = s
}
// SetGlobalNormalizationFunc sets a normalization function to all flag sets and also to child commands. // SetGlobalNormalizationFunc sets a normalization function to all flag sets and also to child commands.
// The user should not have a cyclic dependency on commands. // The user should not have a cyclic dependency on commands.
func (c *Command) SetGlobalNormalizationFunc(n func(f *flag.FlagSet, name string) flag.NormalizedName) { func (c *Command) SetGlobalNormalizationFunc(n func(f *flag.FlagSet, name string) flag.NormalizedName) {
@ -407,6 +428,19 @@ func (c *Command) HelpTemplate() string {
{{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}` {{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`
} }
// VersionTemplate return version template for the command.
func (c *Command) VersionTemplate() string {
if c.versionTemplate != "" {
return c.versionTemplate
}
if c.HasParent() {
return c.parent.VersionTemplate()
}
return `{{with .Name}}{{printf "%s " .}}{{end}}{{printf "version %s" .Version}}
`
}
func hasNoOptDefVal(name string, fs *flag.FlagSet) bool { func hasNoOptDefVal(name string, fs *flag.FlagSet) bool {
flag := fs.Lookup(name) flag := fs.Lookup(name)
if flag == nil { if flag == nil {
@ -441,6 +475,9 @@ Loop:
s := args[0] s := args[0]
args = args[1:] args = args[1:]
switch { switch {
case s == "--":
// "--" terminates the flags
break Loop
case strings.HasPrefix(s, "--") && !strings.Contains(s, "=") && !hasNoOptDefVal(s[2:], flags): case strings.HasPrefix(s, "--") && !strings.Contains(s, "=") && !hasNoOptDefVal(s[2:], flags):
// If '--flag arg' then // If '--flag arg' then
// delete arg from args. // delete arg from args.
@ -528,6 +565,7 @@ func (c *Command) findNext(next string) *Command {
matches := make([]*Command, 0) matches := make([]*Command, 0)
for _, cmd := range c.commands { for _, cmd := range c.commands {
if cmd.Name() == next || cmd.HasAlias(next) { if cmd.Name() == next || cmd.HasAlias(next) {
cmd.commandCalledAs.name = next
return cmd return cmd
} }
if EnablePrefixMatching && cmd.hasNameOrAliasPrefix(next) { if EnablePrefixMatching && cmd.hasNameOrAliasPrefix(next) {
@ -538,6 +576,7 @@ func (c *Command) findNext(next string) *Command {
if len(matches) == 1 { if len(matches) == 1 {
return matches[0] return matches[0]
} }
return nil return nil
} }
@ -621,10 +660,8 @@ func (c *Command) Root() *Command {
return c return c
} }
// ArgsLenAtDash will return the length of f.Args at the moment when a -- was // ArgsLenAtDash will return the length of c.Flags().Args at the moment
// found during arg parsing. This allows your program to know which args were // when a -- was found during args parsing.
// before the -- and which came after. (Description from
// https://godoc.org/github.com/spf13/pflag#FlagSet.ArgsLenAtDash).
func (c *Command) ArgsLenAtDash() int { func (c *Command) ArgsLenAtDash() int {
return c.Flags().ArgsLenAtDash() return c.Flags().ArgsLenAtDash()
} }
@ -638,9 +675,10 @@ func (c *Command) execute(a []string) (err error) {
c.Printf("Command %q is deprecated, %s\n", c.Name(), c.Deprecated) c.Printf("Command %q is deprecated, %s\n", c.Name(), c.Deprecated)
} }
// initialize help flag as the last point possible to allow for user // initialize help and version flag at the last point possible to allow for user
// overriding // overriding
c.InitDefaultHelpFlag() c.InitDefaultHelpFlag()
c.InitDefaultVersionFlag()
err = c.ParseFlags(a) err = c.ParseFlags(a)
if err != nil { if err != nil {
@ -657,7 +695,27 @@ func (c *Command) execute(a []string) (err error) {
return err return err
} }
if helpVal || !c.Runnable() { if helpVal {
return flag.ErrHelp
}
// for back-compat, only add version flag behavior if version is defined
if c.Version != "" {
versionVal, err := c.Flags().GetBool("version")
if err != nil {
c.Println("\"version\" flag declared as non-bool. Please correct your code")
return err
}
if versionVal {
err := tmpl(c.OutOrStdout(), c.VersionTemplate(), c)
if err != nil {
c.Println(err)
}
return err
}
}
if !c.Runnable() {
return flag.ErrHelp return flag.ErrHelp
} }
@ -780,6 +838,11 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
return c, err return c, err
} }
cmd.commandCalledAs.called = true
if cmd.commandCalledAs.name == "" {
cmd.commandCalledAs.name = cmd.Name()
}
err = cmd.execute(flags) err = cmd.execute(flags)
if err != nil { if err != nil {
// Always show help if requested, even if SilenceErrors is in // Always show help if requested, even if SilenceErrors is in
@ -825,7 +888,7 @@ func (c *Command) validateRequiredFlags() error {
}) })
if len(missingFlagNames) > 0 { if len(missingFlagNames) > 0 {
return fmt.Errorf(`Required flag(s) "%s" have/has not been set`, strings.Join(missingFlagNames, `", "`)) return fmt.Errorf(`required flag(s) "%s" not set`, strings.Join(missingFlagNames, `", "`))
} }
return nil return nil
} }
@ -846,6 +909,27 @@ func (c *Command) InitDefaultHelpFlag() {
} }
} }
// InitDefaultVersionFlag adds default version flag to c.
// It is called automatically by executing the c.
// If c already has a version flag, it will do nothing.
// If c.Version is empty, it will do nothing.
func (c *Command) InitDefaultVersionFlag() {
if c.Version == "" {
return
}
c.mergePersistentFlags()
if c.Flags().Lookup("version") == nil {
usage := "version for "
if c.Name() == "" {
usage += "this command"
} else {
usage += c.Name()
}
c.Flags().Bool("version", false, usage)
}
}
// InitDefaultHelpCmd adds default help command to c. // InitDefaultHelpCmd adds default help command to c.
// It is called automatically by executing the c or by calling help and usage. // It is called automatically by executing the c or by calling help and usage.
// If c already has help command or c has no subcommands, it will do nothing. // If c already has help command or c has no subcommands, it will do nothing.
@ -877,7 +961,7 @@ Simply type ` + c.Name() + ` help [path to command] for full details.`,
c.AddCommand(c.helpCommand) c.AddCommand(c.helpCommand)
} }
// ResetCommands used for testing. // ResetCommands delete parent, subcommand and help command from c.
func (c *Command) ResetCommands() { func (c *Command) ResetCommands() {
c.parent = nil c.parent = nil
c.commands = nil c.commands = nil
@ -996,6 +1080,9 @@ func (c *Command) UseLine() string {
} else { } else {
useline = c.Use useline = c.Use
} }
if c.DisableFlagsInUseLine {
return useline
}
if c.HasAvailableFlags() && !strings.Contains(useline, "[flags]") { if c.HasAvailableFlags() && !strings.Contains(useline, "[flags]") {
useline += " [flags]" useline += " [flags]"
} }
@ -1063,14 +1150,25 @@ func (c *Command) HasAlias(s string) bool {
return false return false
} }
// CalledAs returns the command name or alias that was used to invoke
// this command or an empty string if the command has not been called.
func (c *Command) CalledAs() string {
if c.commandCalledAs.called {
return c.commandCalledAs.name
}
return ""
}
// hasNameOrAliasPrefix returns true if the Name or any of aliases start // hasNameOrAliasPrefix returns true if the Name or any of aliases start
// with prefix // with prefix
func (c *Command) hasNameOrAliasPrefix(prefix string) bool { func (c *Command) hasNameOrAliasPrefix(prefix string) bool {
if strings.HasPrefix(c.Name(), prefix) { if strings.HasPrefix(c.Name(), prefix) {
c.commandCalledAs.name = c.Name()
return true return true
} }
for _, alias := range c.Aliases { for _, alias := range c.Aliases {
if strings.HasPrefix(alias, prefix) { if strings.HasPrefix(alias, prefix) {
c.commandCalledAs.name = alias
return true return true
} }
} }
@ -1163,7 +1261,7 @@ func (c *Command) HasAvailableSubCommands() bool {
} }
} }
// the command either has no sub comamnds, or no available (non deprecated/help/hidden) // the command either has no sub commands, or no available (non deprecated/help/hidden)
// sub commands // sub commands
return false return false
} }
@ -1173,7 +1271,7 @@ func (c *Command) HasParent() bool {
return c.parent != nil return c.parent != nil
} }
// GlobalNormalizationFunc returns the global normalization function or nil if doesn't exists. // GlobalNormalizationFunc returns the global normalization function or nil if it doesn't exist.
func (c *Command) GlobalNormalizationFunc() func(f *flag.FlagSet, name string) flag.NormalizedName { func (c *Command) GlobalNormalizationFunc() func(f *flag.FlagSet, name string) flag.NormalizedName {
return c.globNormFunc return c.globNormFunc
} }
@ -1273,7 +1371,7 @@ func (c *Command) PersistentFlags() *flag.FlagSet {
return c.pflags return c.pflags
} }
// ResetFlags is used in testing. // ResetFlags deletes all flags from command.
func (c *Command) ResetFlags() { func (c *Command) ResetFlags() {
c.flagErrorBuf = new(bytes.Buffer) c.flagErrorBuf = new(bytes.Buffer)
c.flagErrorBuf.Reset() c.flagErrorBuf.Reset()

File diff suppressed because it is too large Load Diff

View File

@ -1,145 +1,86 @@
package doc package doc
import ( import (
"bytes"
"fmt"
"runtime"
"strings" "strings"
"testing" "testing"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var flagb1, flagb2, flagb3, flagbr, flagbp bool func emptyRun(*cobra.Command, []string) {}
var flags1, flags2a, flags2b, flags3 string
var flagi1, flagi2, flagi3, flagir int
const strtwoParentHelp = "help message for parent flag strtwo" func init() {
const strtwoChildHelp = "help message for child flag strtwo" rootCmd.PersistentFlags().StringP("rootflag", "r", "two", "")
rootCmd.PersistentFlags().StringP("strtwo", "t", "two", "help message for parent flag strtwo")
var cmdEcho = &cobra.Command{ echoCmd.PersistentFlags().StringP("strone", "s", "one", "help message for flag strone")
echoCmd.PersistentFlags().BoolP("persistentbool", "p", false, "help message for flag persistentbool")
echoCmd.Flags().IntP("intone", "i", 123, "help message for flag intone")
echoCmd.Flags().BoolP("boolone", "b", true, "help message for flag boolone")
timesCmd.PersistentFlags().StringP("strtwo", "t", "2", "help message for child flag strtwo")
timesCmd.Flags().IntP("inttwo", "j", 234, "help message for flag inttwo")
timesCmd.Flags().BoolP("booltwo", "c", false, "help message for flag booltwo")
printCmd.PersistentFlags().StringP("strthree", "s", "three", "help message for flag strthree")
printCmd.Flags().IntP("intthree", "i", 345, "help message for flag intthree")
printCmd.Flags().BoolP("boolthree", "b", true, "help message for flag boolthree")
echoCmd.AddCommand(timesCmd, echoSubCmd, deprecatedCmd)
rootCmd.AddCommand(printCmd, echoCmd)
}
var rootCmd = &cobra.Command{
Use: "root",
Short: "Root short description",
Long: "Root long description",
Run: emptyRun,
}
var echoCmd = &cobra.Command{
Use: "echo [string to echo]", Use: "echo [string to echo]",
Aliases: []string{"say"}, Aliases: []string{"say"},
Short: "Echo anything to the screen", Short: "Echo anything to the screen",
Long: `an utterly useless command for testing.`, Long: "an utterly useless command for testing",
Example: "Just run cobra-test echo", Example: "Just run cobra-test echo",
} }
var cmdEchoSub = &cobra.Command{ var echoSubCmd = &cobra.Command{
Use: "echosub [string to print]", Use: "echosub [string to print]",
Short: "second sub command for echo", Short: "second sub command for echo",
Long: `an absolutely utterly useless command for testing gendocs!.`, Long: "an absolutely utterly useless command for testing gendocs!.",
Run: func(cmd *cobra.Command, args []string) {}, Run: emptyRun,
} }
var cmdDeprecated = &cobra.Command{ var timesCmd = &cobra.Command{
Use: "times [# times] [string to echo]",
SuggestFor: []string{"counts"},
Short: "Echo anything to the screen more times",
Long: `a slightly useless command for testing.`,
Run: emptyRun,
}
var deprecatedCmd = &cobra.Command{
Use: "deprecated [can't do anything here]", Use: "deprecated [can't do anything here]",
Short: "A command which is deprecated", Short: "A command which is deprecated",
Long: `an absolutely utterly useless command for testing deprecation!.`, Long: `an absolutely utterly useless command for testing deprecation!.`,
Deprecated: "Please use echo instead", Deprecated: "Please use echo instead",
} }
var cmdTimes = &cobra.Command{ var printCmd = &cobra.Command{
Use: "times [# times] [string to echo]",
SuggestFor: []string{"counts"},
Short: "Echo anything to the screen more times",
Long: `a slightly useless command for testing.`,
PersistentPreRun: func(cmd *cobra.Command, args []string) {},
Run: func(cmd *cobra.Command, args []string) {},
}
var cmdPrint = &cobra.Command{
Use: "print [string to print]", Use: "print [string to print]",
Short: "Print anything to the screen", Short: "Print anything to the screen",
Long: `an absolutely utterly useless command for testing.`, Long: `an absolutely utterly useless command for testing.`,
} }
var cmdRootNoRun = &cobra.Command{ func checkStringContains(t *testing.T, got, expected string) {
Use: "cobra-test", if !strings.Contains(got, expected) {
Short: "The root can run its own function", t.Errorf("Expected to contain: \n %v\nGot:\n %v\n", expected, got)
Long: "The root description for help",
}
var cmdRootSameName = &cobra.Command{
Use: "print",
Short: "Root with the same name as a subcommand",
Long: "The root description for help",
}
var cmdRootWithRun = &cobra.Command{
Use: "cobra-test",
Short: "The root can run its own function",
Long: "The root description for help",
}
var cmdSubNoRun = &cobra.Command{
Use: "subnorun",
Short: "A subcommand without a Run function",
Long: "A long output about a subcommand without a Run function",
}
var cmdVersion1 = &cobra.Command{
Use: "version",
Short: "Print the version number",
Long: `First version of the version command`,
}
var cmdVersion2 = &cobra.Command{
Use: "version",
Short: "Print the version number",
Long: `Second version of the version command`,
}
func flagInit() {
cmdEcho.ResetFlags()
cmdPrint.ResetFlags()
cmdTimes.ResetFlags()
cmdRootNoRun.ResetFlags()
cmdRootSameName.ResetFlags()
cmdRootWithRun.ResetFlags()
cmdSubNoRun.ResetFlags()
cmdRootNoRun.PersistentFlags().StringVarP(&flags2a, "strtwo", "t", "two", strtwoParentHelp)
cmdEcho.Flags().IntVarP(&flagi1, "intone", "i", 123, "help message for flag intone")
cmdTimes.Flags().IntVarP(&flagi2, "inttwo", "j", 234, "help message for flag inttwo")
cmdPrint.Flags().IntVarP(&flagi3, "intthree", "i", 345, "help message for flag intthree")
cmdEcho.PersistentFlags().StringVarP(&flags1, "strone", "s", "one", "help message for flag strone")
cmdEcho.PersistentFlags().BoolVarP(&flagbp, "persistentbool", "p", false, "help message for flag persistentbool")
cmdTimes.PersistentFlags().StringVarP(&flags2b, "strtwo", "t", "2", strtwoChildHelp)
cmdPrint.PersistentFlags().StringVarP(&flags3, "strthree", "s", "three", "help message for flag strthree")
cmdEcho.Flags().BoolVarP(&flagb1, "boolone", "b", true, "help message for flag boolone")
cmdTimes.Flags().BoolVarP(&flagb2, "booltwo", "c", false, "help message for flag booltwo")
cmdPrint.Flags().BoolVarP(&flagb3, "boolthree", "b", true, "help message for flag boolthree")
cmdVersion1.ResetFlags()
cmdVersion2.ResetFlags()
}
func initializeWithRootCmd() *cobra.Command {
cmdRootWithRun.ResetCommands()
flagInit()
cmdRootWithRun.Flags().BoolVarP(&flagbr, "boolroot", "b", false, "help message for flag boolroot")
cmdRootWithRun.Flags().IntVarP(&flagir, "introot", "i", 321, "help message for flag introot")
return cmdRootWithRun
}
func checkStringContains(t *testing.T, found, expected string) {
if !strings.Contains(found, expected) {
logErr(t, found, expected)
} }
} }
func checkStringOmits(t *testing.T, found, expected string) { func checkStringOmits(t *testing.T, got, expected string) {
if strings.Contains(found, expected) { if strings.Contains(got, expected) {
logErr(t, found, expected) t.Errorf("Expected to not contain: \n %v\nGot: %v", expected, got)
} }
} }
func logErr(t *testing.T, found, expected string) {
out := new(bytes.Buffer)
_, _, line, ok := runtime.Caller(2)
if ok {
fmt.Fprintf(out, "Line: %d ", line)
}
fmt.Fprintf(out, "Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
t.Errorf(out.String())
}

View File

@ -18,135 +18,97 @@ func translate(in string) string {
} }
func TestGenManDoc(t *testing.T) { func TestGenManDoc(t *testing.T) {
c := initializeWithRootCmd()
// Need two commands to run the command alphabetical sort
cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated)
c.AddCommand(cmdPrint, cmdEcho)
cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp)
out := new(bytes.Buffer)
header := &GenManHeader{ header := &GenManHeader{
Title: "Project", Title: "Project",
Section: "2", Section: "2",
} }
// We generate on a subcommand so we have both subcommands and parents // We generate on a subcommand so we have both subcommands and parents
if err := GenMan(cmdEcho, header, out); err != nil { buf := new(bytes.Buffer)
if err := GenMan(echoCmd, header, buf); err != nil {
t.Fatal(err) t.Fatal(err)
} }
found := out.String() output := buf.String()
// Make sure parent has - in CommandPath() in SEE ALSO: // Make sure parent has - in CommandPath() in SEE ALSO:
parentPath := cmdEcho.Parent().CommandPath() parentPath := echoCmd.Parent().CommandPath()
dashParentPath := strings.Replace(parentPath, " ", "-", -1) dashParentPath := strings.Replace(parentPath, " ", "-", -1)
expected := translate(dashParentPath) expected := translate(dashParentPath)
expected = expected + "(" + header.Section + ")" expected = expected + "(" + header.Section + ")"
checkStringContains(t, found, expected) checkStringContains(t, output, expected)
// Our description checkStringContains(t, output, translate(echoCmd.Name()))
expected = translate(cmdEcho.Name()) checkStringContains(t, output, translate(echoCmd.Name()))
checkStringContains(t, found, expected) checkStringContains(t, output, "boolone")
checkStringContains(t, output, "rootflag")
// Better have our example checkStringContains(t, output, translate(rootCmd.Name()))
expected = translate(cmdEcho.Name()) checkStringContains(t, output, translate(echoSubCmd.Name()))
checkStringContains(t, found, expected) checkStringOmits(t, output, translate(deprecatedCmd.Name()))
checkStringContains(t, output, translate("Auto generated"))
// A local flag
expected = "boolone"
checkStringContains(t, found, expected)
// persistent flag on parent
expected = "rootflag"
checkStringContains(t, found, expected)
// We better output info about our parent
expected = translate(cmdRootWithRun.Name())
checkStringContains(t, found, expected)
// And about subcommands
expected = translate(cmdEchoSub.Name())
checkStringContains(t, found, expected)
unexpected := translate(cmdDeprecated.Name())
checkStringOmits(t, found, unexpected)
// auto generated
expected = translate("Auto generated")
checkStringContains(t, found, expected)
} }
func TestGenManNoGenTag(t *testing.T) { func TestGenManNoGenTag(t *testing.T) {
c := initializeWithRootCmd() echoCmd.DisableAutoGenTag = true
// Need two commands to run the command alphabetical sort defer func() { echoCmd.DisableAutoGenTag = false }()
cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated)
c.AddCommand(cmdPrint, cmdEcho)
cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp)
cmdEcho.DisableAutoGenTag = true
out := new(bytes.Buffer)
header := &GenManHeader{ header := &GenManHeader{
Title: "Project", Title: "Project",
Section: "2", Section: "2",
} }
// We generate on a subcommand so we have both subcommands and parents // We generate on a subcommand so we have both subcommands and parents
if err := GenMan(cmdEcho, header, out); err != nil { buf := new(bytes.Buffer)
if err := GenMan(echoCmd, header, buf); err != nil {
t.Fatal(err) t.Fatal(err)
} }
found := out.String() output := buf.String()
unexpected := translate("#HISTORY") unexpected := translate("#HISTORY")
checkStringOmits(t, found, unexpected) checkStringOmits(t, output, unexpected)
} }
func TestGenManSeeAlso(t *testing.T) { func TestGenManSeeAlso(t *testing.T) {
noop := func(cmd *cobra.Command, args []string) {} rootCmd := &cobra.Command{Use: "root", Run: emptyRun}
aCmd := &cobra.Command{Use: "aaa", Run: emptyRun, Hidden: true} // #229
bCmd := &cobra.Command{Use: "bbb", Run: emptyRun}
cCmd := &cobra.Command{Use: "ccc", Run: emptyRun}
rootCmd.AddCommand(aCmd, bCmd, cCmd)
top := &cobra.Command{Use: "top", Run: noop} buf := new(bytes.Buffer)
aaa := &cobra.Command{Use: "aaa", Run: noop, Hidden: true} // #229
bbb := &cobra.Command{Use: "bbb", Run: noop}
ccc := &cobra.Command{Use: "ccc", Run: noop}
top.AddCommand(aaa, bbb, ccc)
out := new(bytes.Buffer)
header := &GenManHeader{} header := &GenManHeader{}
if err := GenMan(top, header, out); err != nil { if err := GenMan(rootCmd, header, buf); err != nil {
t.Fatal(err) t.Fatal(err)
} }
scanner := bufio.NewScanner(buf)
scanner := bufio.NewScanner(out) if err := assertLineFound(scanner, ".SH SEE ALSO"); err != nil {
t.Fatalf("Couldn't find SEE ALSO section header: %v", err)
if err := AssertLineFound(scanner, ".SH SEE ALSO"); err != nil {
t.Fatal(fmt.Errorf("Couldn't find SEE ALSO section header: %s", err.Error()))
} }
if err := assertNextLineEquals(scanner, ".PP"); err != nil {
if err := AssertNextLineEquals(scanner, ".PP"); err != nil { t.Fatalf("First line after SEE ALSO wasn't break-indent: %v", err)
t.Fatal(fmt.Errorf("First line after SEE ALSO wasn't break-indent: %s", err.Error()))
} }
if err := assertNextLineEquals(scanner, `\fBroot\-bbb(1)\fP, \fBroot\-ccc(1)\fP`); err != nil {
if err := AssertNextLineEquals(scanner, `\fBtop\-bbb(1)\fP, \fBtop\-ccc(1)\fP`); err != nil { t.Fatalf("Second line after SEE ALSO wasn't correct: %v", err)
t.Fatal(fmt.Errorf("Second line after SEE ALSO wasn't correct: %s", err.Error()))
} }
} }
func TestManPrintFlagsHidesShortDeperecated(t *testing.T) { func TestManPrintFlagsHidesShortDeperecated(t *testing.T) {
cmd := &cobra.Command{} c := &cobra.Command{}
flags := cmd.Flags() c.Flags().StringP("foo", "f", "default", "Foo flag")
flags.StringP("foo", "f", "default", "Foo flag") c.Flags().MarkShorthandDeprecated("foo", "don't use it no more")
flags.MarkShorthandDeprecated("foo", "don't use it no more")
out := new(bytes.Buffer) buf := new(bytes.Buffer)
manPrintFlags(out, flags) manPrintFlags(buf, c.Flags())
got := buf.String()
expected := "**--foo**=\"default\"\n\tFoo flag\n\n" expected := "**--foo**=\"default\"\n\tFoo flag\n\n"
if out.String() != expected { if got != expected {
t.Fatalf("Expected %s, but got %s", expected, out.String()) t.Errorf("Expected %v, got %v", expected, got)
} }
} }
func TestGenManTree(t *testing.T) { func TestGenManTree(t *testing.T) {
cmd := &cobra.Command{ c := &cobra.Command{Use: "do [OPTIONS] arg1 arg2"}
Use: "do [OPTIONS] arg1 arg2",
}
header := &GenManHeader{Section: "2"} header := &GenManHeader{Section: "2"}
tmpdir, err := ioutil.TempDir("", "test-gen-man-tree") tmpdir, err := ioutil.TempDir("", "test-gen-man-tree")
if err != nil { if err != nil {
@ -154,7 +116,7 @@ func TestGenManTree(t *testing.T) {
} }
defer os.RemoveAll(tmpdir) defer os.RemoveAll(tmpdir)
if err := GenManTree(cmd, header, tmpdir); err != nil { if err := GenManTree(c, header, tmpdir); err != nil {
t.Fatalf("GenManTree failed: %s", err.Error()) t.Fatalf("GenManTree failed: %s", err.Error())
} }
@ -167,7 +129,7 @@ func TestGenManTree(t *testing.T) {
} }
} }
func AssertLineFound(scanner *bufio.Scanner, expectedLine string) error { func assertLineFound(scanner *bufio.Scanner, expectedLine string) error {
for scanner.Scan() { for scanner.Scan() {
line := scanner.Text() line := scanner.Text()
if line == expectedLine { if line == expectedLine {
@ -176,30 +138,29 @@ func AssertLineFound(scanner *bufio.Scanner, expectedLine string) error {
} }
if err := scanner.Err(); err != nil { if err := scanner.Err(); err != nil {
return fmt.Errorf("AssertLineFound: scan failed: %s", err.Error()) return fmt.Errorf("scan failed: %s", err)
} }
return fmt.Errorf("AssertLineFound: hit EOF before finding %#v", expectedLine) return fmt.Errorf("hit EOF before finding %v", expectedLine)
} }
func AssertNextLineEquals(scanner *bufio.Scanner, expectedLine string) error { func assertNextLineEquals(scanner *bufio.Scanner, expectedLine string) error {
if scanner.Scan() { if scanner.Scan() {
line := scanner.Text() line := scanner.Text()
if line == expectedLine { if line == expectedLine {
return nil return nil
} }
return fmt.Errorf("AssertNextLineEquals: got %#v, not %#v", line, expectedLine) return fmt.Errorf("got %v, not %v", line, expectedLine)
} }
if err := scanner.Err(); err != nil { if err := scanner.Err(); err != nil {
return fmt.Errorf("AssertNextLineEquals: scan failed: %s", err.Error()) return fmt.Errorf("scan failed: %v", err)
} }
return fmt.Errorf("AssertNextLineEquals: hit EOF before finding %#v", expectedLine) return fmt.Errorf("hit EOF before finding %v", expectedLine)
} }
func BenchmarkGenManToFile(b *testing.B) { func BenchmarkGenManToFile(b *testing.B) {
c := initializeWithRootCmd()
file, err := ioutil.TempFile("", "") file, err := ioutil.TempFile("", "")
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
@ -209,7 +170,7 @@ func BenchmarkGenManToFile(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
if err := GenMan(c, nil, file); err != nil { if err := GenMan(rootCmd, nil, file); err != nil {
b.Fatal(err) b.Fatal(err)
} }
} }

View File

@ -67,7 +67,7 @@ func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string)
buf.WriteString("## " + name + "\n\n") buf.WriteString("## " + name + "\n\n")
buf.WriteString(short + "\n\n") buf.WriteString(short + "\n\n")
buf.WriteString("### Synopsis\n\n") buf.WriteString("### Synopsis\n\n")
buf.WriteString("\n" + long + "\n\n") buf.WriteString(long + "\n\n")
if cmd.Runnable() { if cmd.Runnable() {
buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.UseLine())) buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.UseLine()))
@ -82,7 +82,7 @@ func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string)
return err return err
} }
if hasSeeAlso(cmd) { if hasSeeAlso(cmd) {
buf.WriteString("### SEE ALSO\n") buf.WriteString("### SEE ALSO\n\n")
if cmd.HasParent() { if cmd.HasParent() {
parent := cmd.Parent() parent := cmd.Parent()
pname := parent.CommandPath() pname := parent.CommandPath()

View File

@ -5,100 +5,51 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func TestGenMdDoc(t *testing.T) { func TestGenMdDoc(t *testing.T) {
c := initializeWithRootCmd() // We generate on subcommand so we have both subcommands and parents.
// Need two commands to run the command alphabetical sort buf := new(bytes.Buffer)
cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated) if err := GenMarkdown(echoCmd, buf); err != nil {
c.AddCommand(cmdPrint, cmdEcho)
cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp)
out := new(bytes.Buffer)
// We generate on s subcommand so we have both subcommands and parents
if err := GenMarkdown(cmdEcho, out); err != nil {
t.Fatal(err) t.Fatal(err)
} }
found := out.String() output := buf.String()
// Our description checkStringContains(t, output, echoCmd.Long)
expected := cmdEcho.Long checkStringContains(t, output, echoCmd.Example)
if !strings.Contains(found, expected) { checkStringContains(t, output, "boolone")
t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found) checkStringContains(t, output, "rootflag")
} checkStringContains(t, output, rootCmd.Short)
checkStringContains(t, output, echoSubCmd.Short)
// Better have our example checkStringOmits(t, output, deprecatedCmd.Short)
expected = cmdEcho.Example
if !strings.Contains(found, expected) {
t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
}
// A local flag
expected = "boolone"
if !strings.Contains(found, expected) {
t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
}
// persistent flag on parent
expected = "rootflag"
if !strings.Contains(found, expected) {
t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
}
// We better output info about our parent
expected = cmdRootWithRun.Short
if !strings.Contains(found, expected) {
t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
}
// And about subcommands
expected = cmdEchoSub.Short
if !strings.Contains(found, expected) {
t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
}
unexpected := cmdDeprecated.Short
if strings.Contains(found, unexpected) {
t.Errorf("Unexpected response.\nFound: %v\nBut should not have!!\n", unexpected)
}
} }
func TestGenMdNoTag(t *testing.T) { func TestGenMdNoTag(t *testing.T) {
c := initializeWithRootCmd() rootCmd.DisableAutoGenTag = true
// Need two commands to run the command alphabetical sort defer func() { rootCmd.DisableAutoGenTag = false }()
cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated)
c.AddCommand(cmdPrint, cmdEcho)
c.DisableAutoGenTag = true
cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp)
out := new(bytes.Buffer)
if err := GenMarkdown(c, out); err != nil { buf := new(bytes.Buffer)
if err := GenMarkdown(rootCmd, buf); err != nil {
t.Fatal(err) t.Fatal(err)
} }
found := out.String() output := buf.String()
unexpected := "Auto generated"
checkStringOmits(t, found, unexpected)
checkStringOmits(t, output, "Auto generated")
} }
func TestGenMdTree(t *testing.T) { func TestGenMdTree(t *testing.T) {
cmd := &cobra.Command{ c := &cobra.Command{Use: "do [OPTIONS] arg1 arg2"}
Use: "do [OPTIONS] arg1 arg2",
}
tmpdir, err := ioutil.TempDir("", "test-gen-md-tree") tmpdir, err := ioutil.TempDir("", "test-gen-md-tree")
if err != nil { if err != nil {
t.Fatalf("Failed to create tmpdir: %s", err.Error()) t.Fatalf("Failed to create tmpdir: %v", err)
} }
defer os.RemoveAll(tmpdir) defer os.RemoveAll(tmpdir)
if err := GenMarkdownTree(cmd, tmpdir); err != nil { if err := GenMarkdownTree(c, tmpdir); err != nil {
t.Fatalf("GenMarkdownTree failed: %s", err.Error()) t.Fatalf("GenMarkdownTree failed: %v", err)
} }
if _, err := os.Stat(filepath.Join(tmpdir, "do.md")); err != nil { if _, err := os.Stat(filepath.Join(tmpdir, "do.md")); err != nil {
@ -107,7 +58,6 @@ func TestGenMdTree(t *testing.T) {
} }
func BenchmarkGenMarkdownToFile(b *testing.B) { func BenchmarkGenMarkdownToFile(b *testing.B) {
c := initializeWithRootCmd()
file, err := ioutil.TempFile("", "") file, err := ioutil.TempFile("", "")
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
@ -117,7 +67,7 @@ func BenchmarkGenMarkdownToFile(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
if err := GenMarkdown(c, file); err != nil { if err := GenMarkdown(rootCmd, file); err != nil {
b.Fatal(err) b.Fatal(err)
} }
} }

View File

@ -5,99 +5,52 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func TestGenRSTDoc(t *testing.T) { func TestGenRSTDoc(t *testing.T) {
c := initializeWithRootCmd() // We generate on a subcommand so we have both subcommands and parents
// Need two commands to run the command alphabetical sort buf := new(bytes.Buffer)
cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated) if err := GenReST(echoCmd, buf); err != nil {
c.AddCommand(cmdPrint, cmdEcho)
cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp)
out := new(bytes.Buffer)
// We generate on s subcommand so we have both subcommands and parents
if err := GenReST(cmdEcho, out); err != nil {
t.Fatal(err) t.Fatal(err)
} }
found := out.String() output := buf.String()
// Our description checkStringContains(t, output, echoCmd.Long)
expected := cmdEcho.Long checkStringContains(t, output, echoCmd.Example)
if !strings.Contains(found, expected) { checkStringContains(t, output, "boolone")
t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found) checkStringContains(t, output, "rootflag")
} checkStringContains(t, output, rootCmd.Short)
checkStringContains(t, output, echoSubCmd.Short)
// Better have our example checkStringOmits(t, output, deprecatedCmd.Short)
expected = cmdEcho.Example
if !strings.Contains(found, expected) {
t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
}
// A local flag
expected = "boolone"
if !strings.Contains(found, expected) {
t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
}
// persistent flag on parent
expected = "rootflag"
if !strings.Contains(found, expected) {
t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
}
// We better output info about our parent
expected = cmdRootWithRun.Short
if !strings.Contains(found, expected) {
t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
}
// And about subcommands
expected = cmdEchoSub.Short
if !strings.Contains(found, expected) {
t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
}
unexpected := cmdDeprecated.Short
if strings.Contains(found, unexpected) {
t.Errorf("Unexpected response.\nFound: %v\nBut should not have!!\n", unexpected)
}
} }
func TestGenRSTNoTag(t *testing.T) { func TestGenRSTNoTag(t *testing.T) {
c := initializeWithRootCmd() rootCmd.DisableAutoGenTag = true
// Need two commands to run the command alphabetical sort defer func() { rootCmd.DisableAutoGenTag = false }()
cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated)
c.AddCommand(cmdPrint, cmdEcho)
c.DisableAutoGenTag = true
cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp)
out := new(bytes.Buffer)
if err := GenReST(c, out); err != nil { buf := new(bytes.Buffer)
if err := GenReST(rootCmd, buf); err != nil {
t.Fatal(err) t.Fatal(err)
} }
found := out.String() output := buf.String()
unexpected := "Auto generated" unexpected := "Auto generated"
checkStringOmits(t, found, unexpected) checkStringOmits(t, output, unexpected)
} }
func TestGenRSTTree(t *testing.T) { func TestGenRSTTree(t *testing.T) {
cmd := &cobra.Command{ c := &cobra.Command{Use: "do [OPTIONS] arg1 arg2"}
Use: "do [OPTIONS] arg1 arg2",
}
tmpdir, err := ioutil.TempDir("", "test-gen-rst-tree") tmpdir, err := ioutil.TempDir("", "test-gen-rst-tree")
if err != nil { if err != nil {
t.Fatalf("Failed to create tmpdir: %s", err.Error()) t.Fatalf("Failed to create tmpdir: %s", err.Error())
} }
defer os.RemoveAll(tmpdir) defer os.RemoveAll(tmpdir)
if err := GenReSTTree(cmd, tmpdir); err != nil { if err := GenReSTTree(c, tmpdir); err != nil {
t.Fatalf("GenReSTTree failed: %s", err.Error()) t.Fatalf("GenReSTTree failed: %s", err.Error())
} }
@ -107,7 +60,6 @@ func TestGenRSTTree(t *testing.T) {
} }
func BenchmarkGenReSTToFile(b *testing.B) { func BenchmarkGenReSTToFile(b *testing.B) {
c := initializeWithRootCmd()
file, err := ioutil.TempFile("", "") file, err := ioutil.TempFile("", "")
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
@ -117,7 +69,7 @@ func BenchmarkGenReSTToFile(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
if err := GenReST(c, file); err != nil { if err := GenReST(rootCmd, file); err != nil {
b.Fatal(err) b.Fatal(err)
} }
} }

View File

@ -5,92 +5,42 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func TestGenYamlDoc(t *testing.T) { func TestGenYamlDoc(t *testing.T) {
c := initializeWithRootCmd()
// Need two commands to run the command alphabetical sort
cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated)
c.AddCommand(cmdPrint, cmdEcho)
cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp)
out := new(bytes.Buffer)
// We generate on s subcommand so we have both subcommands and parents // We generate on s subcommand so we have both subcommands and parents
if err := GenYaml(cmdEcho, out); err != nil { buf := new(bytes.Buffer)
if err := GenYaml(echoCmd, buf); err != nil {
t.Fatal(err) t.Fatal(err)
} }
found := out.String() output := buf.String()
// Our description checkStringContains(t, output, echoCmd.Long)
expected := cmdEcho.Long checkStringContains(t, output, echoCmd.Example)
if !strings.Contains(found, expected) { checkStringContains(t, output, "boolone")
t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found) checkStringContains(t, output, "rootflag")
} checkStringContains(t, output, rootCmd.Short)
checkStringContains(t, output, echoSubCmd.Short)
// Better have our example
expected = cmdEcho.Example
if !strings.Contains(found, expected) {
t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
}
// A local flag
expected = "boolone"
if !strings.Contains(found, expected) {
t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
}
// persistent flag on parent
expected = "rootflag"
if !strings.Contains(found, expected) {
t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
}
// We better output info about our parent
expected = cmdRootWithRun.Short
if !strings.Contains(found, expected) {
t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
}
// And about subcommands
expected = cmdEchoSub.Short
if !strings.Contains(found, expected) {
t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
}
unexpected := cmdDeprecated.Short
if strings.Contains(found, unexpected) {
t.Errorf("Unexpected response.\nFound: %v\nBut should not have!!\n", unexpected)
}
} }
func TestGenYamlNoTag(t *testing.T) { func TestGenYamlNoTag(t *testing.T) {
c := initializeWithRootCmd() rootCmd.DisableAutoGenTag = true
// Need two commands to run the command alphabetical sort defer func() { rootCmd.DisableAutoGenTag = false }()
cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated)
c.AddCommand(cmdPrint, cmdEcho)
c.DisableAutoGenTag = true
cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp)
out := new(bytes.Buffer)
if err := GenYaml(c, out); err != nil { buf := new(bytes.Buffer)
if err := GenYaml(rootCmd, buf); err != nil {
t.Fatal(err) t.Fatal(err)
} }
found := out.String() output := buf.String()
unexpected := "Auto generated"
checkStringOmits(t, found, unexpected)
checkStringOmits(t, output, "Auto generated")
} }
func TestGenYamlTree(t *testing.T) { func TestGenYamlTree(t *testing.T) {
cmd := &cobra.Command{ c := &cobra.Command{Use: "do [OPTIONS] arg1 arg2"}
Use: "do [OPTIONS] arg1 arg2",
}
tmpdir, err := ioutil.TempDir("", "test-gen-yaml-tree") tmpdir, err := ioutil.TempDir("", "test-gen-yaml-tree")
if err != nil { if err != nil {
@ -98,7 +48,7 @@ func TestGenYamlTree(t *testing.T) {
} }
defer os.RemoveAll(tmpdir) defer os.RemoveAll(tmpdir)
if err := GenYamlTree(cmd, tmpdir); err != nil { if err := GenYamlTree(c, tmpdir); err != nil {
t.Fatalf("GenYamlTree failed: %s", err.Error()) t.Fatalf("GenYamlTree failed: %s", err.Error())
} }
@ -108,7 +58,6 @@ func TestGenYamlTree(t *testing.T) {
} }
func BenchmarkGenYamlToFile(b *testing.B) { func BenchmarkGenYamlToFile(b *testing.B) {
c := initializeWithRootCmd()
file, err := ioutil.TempFile("", "") file, err := ioutil.TempFile("", "")
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
@ -118,7 +67,7 @@ func BenchmarkGenYamlToFile(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
if err := GenYaml(c, file); err != nil { if err := GenYaml(rootCmd, file); err != nil {
b.Fatal(err) b.Fatal(err)
} }
} }

View File

@ -77,10 +77,11 @@ func TestZshCompletion(t *testing.T) {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
tc.root.GenZshCompletion(buf) tc.root.GenZshCompletion(buf)
completion := buf.String() output := buf.String()
for _, expectedExpression := range tc.expectedExpressions { for _, expectedExpression := range tc.expectedExpressions {
if !strings.Contains(completion, expectedExpression) { if !strings.Contains(output, expectedExpression) {
t.Errorf("expected completion to contain '%v' somewhere; got '%v'", expectedExpression, completion) t.Errorf("Expected completion to contain %q somewhere; got %q", expectedExpression, output)
} }
} }
}) })