Update cobra godep

This commit is contained in:
Dr. Stefan Schimanski 2016-03-20 19:09:12 +01:00
parent b1b58c4165
commit 8af3be87e4
18 changed files with 714 additions and 411 deletions

2
Godeps/Godeps.json generated
View File

@ -980,7 +980,7 @@
}, },
{ {
"ImportPath": "github.com/spf13/cobra", "ImportPath": "github.com/spf13/cobra",
"Rev": "1c44ec8d3f1552cac48999f9306da23c4d8a288b" "Rev": "4c05eb1145f16d0e6bb4a3e1b6d769f4713cb41f"
}, },
{ {
"ImportPath": "github.com/spf13/pflag", "ImportPath": "github.com/spf13/pflag",

View File

@ -4,6 +4,10 @@ go:
- 1.4.2 - 1.4.2
- 1.5.1 - 1.5.1
- tip - tip
before_install:
- mkdir -p bin
- curl -Lso bin/shellcheck https://github.com/caarlos0/shellcheck-docker/releases/download/v0.4.3/shellcheck
- chmod +x bin/shellcheck
script: script:
- go test -v ./... - PATH=$PATH:$PWD/bin go test -v ./...
- go build - go build

View File

@ -7,6 +7,7 @@ Many of the most widely used Go projects are built using Cobra including:
* [Kubernetes](http://kubernetes.io/) * [Kubernetes](http://kubernetes.io/)
* [Hugo](http://gohugo.io) * [Hugo](http://gohugo.io)
* [rkt](https://github.com/coreos/rkt) * [rkt](https://github.com/coreos/rkt)
* [etcd](https://github.com/coreos/etcd)
* [Docker (distribution)](https://github.com/docker/distribution) * [Docker (distribution)](https://github.com/docker/distribution)
* [OpenShift](https://www.openshift.com/) * [OpenShift](https://www.openshift.com/)
* [Delve](https://github.com/derekparker/delve) * [Delve](https://github.com/derekparker/delve)
@ -15,6 +16,8 @@ Many of the most widely used Go projects are built using Cobra including:
* [Bleve](http://www.blevesearch.com/) * [Bleve](http://www.blevesearch.com/)
* [ProjectAtomic (enterprise)](http://www.projectatomic.io/) * [ProjectAtomic (enterprise)](http://www.projectatomic.io/)
* [Parse (CLI)](https://parse.com/) * [Parse (CLI)](https://parse.com/)
* [GiantSwarm's swarm](https://github.com/giantswarm/cli)
* [Nanobox](https://github.com/nanobox-io/nanobox)/[Nanopack](https://github.com/nanopack)
[![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)
@ -74,7 +77,7 @@ A few good real world examples may better illustrate this point.
In the following example, 'server' is a command, and 'port' is a flag: In the following example, 'server' is a command, and 'port' is a flag:
> hugo serve --port=1313 > hugo server --port=1313
In this command we are telling Git to clone the url bare. In this command we are telling Git to clone the url bare.
@ -199,7 +202,7 @@ cobra add config
cobra add create -p 'configCmd' cobra add create -p 'configCmd'
``` ```
Once you have run these four commands you would have an app structure that would look like: Once you have run these three commands you would have an app structure that would look like:
``` ```
▾ app/ ▾ app/
@ -533,7 +536,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 you 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.
The default help command is The default help command is
@ -710,7 +713,8 @@ func main() {
## Alternative Error Handling ## Alternative Error Handling
Cobra also has functions where the return signature is an error. This allows for errors to bubble up to the top, providing a way to handle the errors in one location. The current list of functions that return an error is: Cobra also has functions where the return signature is an error. This allows for errors to bubble up to the top,
providing a way to handle the errors in one location. The current list of functions that return an error is:
* PersistentPreRunE * PersistentPreRunE
* PreRunE * PreRunE
@ -718,6 +722,10 @@ Cobra also has functions where the return signature is an error. This allows for
* PostRunE * PostRunE
* PersistentPostRunE * PersistentPostRunE
If you would like to silence the default `error` and `usage` output in favor of your own, you can set `SilenceUsage`
and `SilenceErrors` to `false` on the command. A child command respects these flags if they are set on the parent
command.
**Example Usage using RunE:** **Example Usage using RunE:**
```go ```go
@ -791,11 +799,11 @@ Run 'kubectl help' for usage.
## Generating Markdown-formatted documentation for your command ## Generating Markdown-formatted documentation for your command
Cobra can generate a Markdown-formatted document based on the subcommands, flags, etc. A simple example of how to do this for your command can be found in [Markdown Docs](md_docs.md). Cobra can generate a Markdown-formatted document based on the subcommands, flags, etc. A simple example of how to do this for your command can be found in [Markdown Docs](doc/md_docs.md).
## Generating man pages for your command ## Generating man pages for your command
Cobra can generate a man page based on the subcommands, flags, etc. A simple example of how to do this for your command can be found in [Man Docs](man_docs.md). Cobra can generate a man page based on the subcommands, flags, etc. A simple example of how to do this for your command can be found in [Man Docs](doc/man_docs.md).
## Generating bash completions for your command ## Generating bash completions for your command

View File

@ -1,8 +1,8 @@
package cobra package cobra
import ( import (
"bytes"
"fmt" "fmt"
"io"
"os" "os"
"sort" "sort"
"strings" "strings"
@ -12,13 +12,17 @@ import (
const ( const (
BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extentions" BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extentions"
BashCompCustom = "cobra_annotation_bash_completion_custom"
BashCompOneRequiredFlag = "cobra_annotation_bash_completion_one_required_flag" BashCompOneRequiredFlag = "cobra_annotation_bash_completion_one_required_flag"
BashCompSubdirsInDir = "cobra_annotation_bash_completion_subdirs_in_dir" BashCompSubdirsInDir = "cobra_annotation_bash_completion_subdirs_in_dir"
) )
func preamble(out *bytes.Buffer) { func preamble(out io.Writer, name string) error {
fmt.Fprintf(out, `#!/bin/bash _, err := fmt.Fprintf(out, "# bash completion for %-36s -*- shell-script -*-\n", name)
if err != nil {
return err
}
_, err = fmt.Fprint(out, `
__debug() __debug()
{ {
if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then
@ -31,7 +35,7 @@ __debug()
__my_init_completion() __my_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() __index_of_word()
@ -57,7 +61,7 @@ __contains_word()
__handle_reply() __handle_reply()
{ {
__debug "${FUNCNAME}" __debug "${FUNCNAME[0]}"
case $cur in case $cur in
-*) -*)
if [[ $(type -t compopt) = "builtin" ]]; then if [[ $(type -t compopt) = "builtin" ]]; then
@ -71,7 +75,28 @@ __handle_reply()
fi fi
COMPREPLY=( $(compgen -W "${allflags[*]}" -- "$cur") ) COMPREPLY=( $(compgen -W "${allflags[*]}" -- "$cur") )
if [[ $(type -t compopt) = "builtin" ]]; then if [[ $(type -t compopt) = "builtin" ]]; then
[[ $COMPREPLY == *= ]] || compopt +o nospace [[ "${COMPREPLY[0]}" == *= ]] || compopt +o nospace
fi
# complete after --flag=abc
if [[ $cur == *=* ]]; then
if [[ $(type -t compopt) = "builtin" ]]; then
compopt +o nospace
fi
local index flag
flag="${cur%%=*}"
__index_of_word "${flag}" "${flags_with_completion[@]}"
if [[ ${index} -ge 0 ]]; then
COMPREPLY=()
PREFIX=""
cur="${cur#*=}"
${flags_completion[${index}]}
if [ -n "${ZSH_VERSION}" ]; then
# zfs completion needs --flag= prefix
eval "COMPREPLY=( \"\${COMPREPLY[@]/#/${flag}=}\" )"
fi
fi
fi fi
return 0; return 0;
;; ;;
@ -100,9 +125,15 @@ __handle_reply()
fi fi
COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") ) COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") )
if [[ ${#COMPREPLY[@]} -eq 0 && ${#noun_aliases[@]} -gt 0 && ${#must_have_one_noun[@]} -ne 0 ]]; then
COMPREPLY=( $(compgen -W "${noun_aliases[*]}" -- "$cur") )
fi
if [[ ${#COMPREPLY[@]} -eq 0 ]]; then if [[ ${#COMPREPLY[@]} -eq 0 ]]; then
declare -F __custom_func >/dev/null && __custom_func declare -F __custom_func >/dev/null && __custom_func
fi fi
__ltrim_colon_completions "$cur"
} }
# The arguments should be in the form "ext1|ext2|extn" # The arguments should be in the form "ext1|ext2|extn"
@ -120,20 +151,31 @@ __handle_subdirs_in_dir_flag()
__handle_flag() __handle_flag()
{ {
__debug "${FUNCNAME}: c is $c words[c] is ${words[c]}" __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]}
local flagvalue
# if the word contained an = # if the word contained an =
if [[ ${words[c]} == *"="* ]]; then if [[ ${words[c]} == *"="* ]]; then
flagvalue=${flagname#*=} # take in as flagvalue after the =
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}: looking for ${flagname}" __debug "${FUNCNAME[0]}: looking for ${flagname}"
if __contains_word "${flagname}" "${must_have_one_flag[@]}"; then if __contains_word "${flagname}" "${must_have_one_flag[@]}"; then
must_have_one_flag=() must_have_one_flag=()
fi fi
# keep flag value with flagname as flaghash
if [ -n "${flagvalue}" ] ; then
flaghash[${flagname}]=${flagvalue}
elif [ -n "${words[ $((c+1)) ]}" ] ; then
flaghash[${flagname}]=${words[ $((c+1)) ]}
else
flaghash[${flagname}]="true" # pad "true" for bool flag
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 __contains_word "${words[c]}" "${two_word_flags[@]}"; then
c=$((c+1)) c=$((c+1))
@ -143,17 +185,18 @@ __handle_flag()
fi fi
fi fi
# skip the flag itself
c=$((c+1)) c=$((c+1))
} }
__handle_noun() __handle_noun()
{ {
__debug "${FUNCNAME}: c is $c words[c] is ${words[c]}" __debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
if __contains_word "${words[c]}" "${must_have_one_noun[@]}"; then if __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
must_have_one_noun=()
fi fi
nouns+=("${words[c]}") nouns+=("${words[c]}")
@ -162,16 +205,20 @@ __handle_noun()
__handle_command() __handle_command()
{ {
__debug "${FUNCNAME}: c is $c words[c] is ${words[c]}" __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
next_command="_${words[c]}" if [[ $c -eq 0 ]]; then
next_command="_$(basename "${words[c]//:/__}")"
else
next_command="_${words[c]//:/__}"
fi
fi fi
c=$((c+1)) c=$((c+1))
__debug "${FUNCNAME}: looking for ${next_command}" __debug "${FUNCNAME[0]}: looking for ${next_command}"
declare -F $next_command >/dev/null && $next_command declare -F $next_command >/dev/null && $next_command
} }
@ -181,11 +228,13 @@ __handle_word()
__handle_reply __handle_reply
return return
fi fi
__debug "${FUNCNAME}: c is $c words[c] is ${words[c]}" __debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
if [[ "${words[c]}" == -* ]]; then if [[ "${words[c]}" == -* ]]; then
__handle_flag __handle_flag
elif __contains_word "${words[c]}" "${commands[@]}"; then elif __contains_word "${words[c]}" "${commands[@]}"; then
__handle_command __handle_command
elif [[ $c -eq 0 ]] && __contains_word "$(basename "${words[c]}")" "${commands[@]}"; then
__handle_command
else else
__handle_noun __handle_noun
fi fi
@ -193,16 +242,22 @@ __handle_word()
} }
`) `)
return err
} }
func postscript(out *bytes.Buffer, name string) { func postscript(w io.Writer, name string) error {
fmt.Fprintf(out, "__start_%s()\n", name) name = strings.Replace(name, ":", "__", -1)
fmt.Fprintf(out, `{ _, err := fmt.Fprintf(w, "__start_%s()\n", name)
if err != nil {
return err
}
_, err = fmt.Fprintf(w, `{
local cur prev words cword local cur prev words cword
declare -A flaghash 2>/dev/null || :
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 || return __my_init_completion -n "=" || return
fi fi
local c=0 local c=0
@ -220,55 +275,91 @@ func postscript(out *bytes.Buffer, name string) {
} }
`, name) `, name)
fmt.Fprintf(out, `if [[ $(type -t compopt) = "builtin" ]]; then if err != nil {
complete -F __start_%s %s return err
}
_, err = fmt.Fprintf(w, `if [[ $(type -t compopt) = "builtin" ]]; then
complete -o default -F __start_%s %s
else else
complete -o nospace -F __start_%s %s complete -o default -o nospace -F __start_%s %s
fi fi
`, name, name, name, name) `, name, name, name, name)
fmt.Fprintf(out, "# ex: ts=4 sw=4 et filetype=sh\n") if err != nil {
return err
}
_, err = fmt.Fprintf(w, "# ex: ts=4 sw=4 et filetype=sh\n")
return err
} }
func writeCommands(cmd *Command, out *bytes.Buffer) { func writeCommands(cmd *Command, w io.Writer) error {
fmt.Fprintf(out, " commands=()\n") if _, err := fmt.Fprintf(w, " commands=()\n"); err != nil {
return err
}
for _, c := range cmd.Commands() { for _, c := range cmd.Commands() {
if !c.IsAvailableCommand() || c == cmd.helpCommand { if !c.IsAvailableCommand() || c == cmd.helpCommand {
continue continue
} }
fmt.Fprintf(out, " commands+=(%q)\n", c.Name()) if _, err := fmt.Fprintf(w, " commands+=(%q)\n", c.Name()); err != nil {
return err
}
} }
fmt.Fprintf(out, "\n") _, err := fmt.Fprintf(w, "\n")
return err
} }
func writeFlagHandler(name string, annotations map[string][]string, out *bytes.Buffer) { func writeFlagHandler(name string, annotations map[string][]string, w io.Writer) error {
for key, value := range annotations { for key, value := range annotations {
switch key { switch key {
case BashCompFilenameExt: case BashCompFilenameExt:
fmt.Fprintf(out, " flags_with_completion+=(%q)\n", name) _, err := fmt.Fprintf(w, " flags_with_completion+=(%q)\n", name)
if err != nil {
return err
}
if len(value) > 0 { if len(value) > 0 {
ext := "__handle_filename_extension_flag " + strings.Join(value, "|") ext := "__handle_filename_extension_flag " + strings.Join(value, "|")
fmt.Fprintf(out, " flags_completion+=(%q)\n", ext) _, err = fmt.Fprintf(w, " flags_completion+=(%q)\n", ext)
} else { } else {
ext := "_filedir" ext := "_filedir"
fmt.Fprintf(out, " flags_completion+=(%q)\n", ext) _, err = fmt.Fprintf(w, " flags_completion+=(%q)\n", ext)
}
if err != nil {
return err
}
case BashCompCustom:
_, err := fmt.Fprintf(w, " flags_with_completion+=(%q)\n", name)
if err != nil {
return err
}
if len(value) > 0 {
handlers := strings.Join(value, "; ")
_, err = fmt.Fprintf(w, " flags_completion+=(%q)\n", handlers)
} else {
_, err = fmt.Fprintf(w, " flags_completion+=(:)\n")
}
if err != nil {
return err
} }
case BashCompSubdirsInDir: case BashCompSubdirsInDir:
fmt.Fprintf(out, " flags_with_completion+=(%q)\n", name) _, err := fmt.Fprintf(w, " flags_with_completion+=(%q)\n", name)
if len(value) == 1 { if len(value) == 1 {
ext := "__handle_subdirs_in_dir_flag " + value[0] ext := "__handle_subdirs_in_dir_flag " + value[0]
fmt.Fprintf(out, " flags_completion+=(%q)\n", ext) _, err = fmt.Fprintf(w, " flags_completion+=(%q)\n", ext)
} else { } else {
ext := "_filedir -d" ext := "_filedir -d"
fmt.Fprintf(out, " flags_completion+=(%q)\n", ext) _, err = fmt.Fprintf(w, " flags_completion+=(%q)\n", ext)
}
if err != nil {
return err
} }
} }
} }
return nil
} }
func writeShortFlag(flag *pflag.Flag, out *bytes.Buffer) { func writeShortFlag(flag *pflag.Flag, w io.Writer) error {
b := (flag.Value.Type() == "bool") b := (flag.Value.Type() == "bool")
name := flag.Shorthand name := flag.Shorthand
format := " " format := " "
@ -276,11 +367,13 @@ func writeShortFlag(flag *pflag.Flag, out *bytes.Buffer) {
format += "two_word_" format += "two_word_"
} }
format += "flags+=(\"-%s\")\n" format += "flags+=(\"-%s\")\n"
fmt.Fprintf(out, format, name) if _, err := fmt.Fprintf(w, format, name); err != nil {
writeFlagHandler("-"+name, flag.Annotations, out) return err
}
return writeFlagHandler("-"+name, flag.Annotations, w)
} }
func writeFlag(flag *pflag.Flag, out *bytes.Buffer) { func writeFlag(flag *pflag.Flag, w io.Writer) error {
b := (flag.Value.Type() == "bool") b := (flag.Value.Type() == "bool")
name := flag.Name name := flag.Name
format := " flags+=(\"--%s" format := " flags+=(\"--%s"
@ -288,36 +381,64 @@ func writeFlag(flag *pflag.Flag, out *bytes.Buffer) {
format += "=" format += "="
} }
format += "\")\n" format += "\")\n"
fmt.Fprintf(out, format, name) if _, err := fmt.Fprintf(w, format, name); err != nil {
writeFlagHandler("--"+name, flag.Annotations, out) return err
}
return writeFlagHandler("--"+name, flag.Annotations, w)
} }
func writeFlags(cmd *Command, out *bytes.Buffer) { func writeFlags(cmd *Command, w io.Writer) error {
fmt.Fprintf(out, ` flags=() _, err := fmt.Fprintf(w, ` flags=()
two_word_flags=() two_word_flags=()
flags_with_completion=() flags_with_completion=()
flags_completion=() flags_completion=()
`) `)
if err != nil {
return err
}
var visitErr error
cmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) { cmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
writeFlag(flag, out) if err := writeFlag(flag, w); err != nil {
visitErr = err
return
}
if len(flag.Shorthand) > 0 { if len(flag.Shorthand) > 0 {
writeShortFlag(flag, out) if err := writeShortFlag(flag, w); err != nil {
visitErr = err
return
}
} }
}) })
if visitErr != nil {
return visitErr
}
cmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) { cmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
writeFlag(flag, out) if err := writeFlag(flag, w); err != nil {
visitErr = err
return
}
if len(flag.Shorthand) > 0 { if len(flag.Shorthand) > 0 {
writeShortFlag(flag, out) if err := writeShortFlag(flag, w); err != nil {
visitErr = err
return
}
} }
}) })
if visitErr != nil {
return visitErr
}
fmt.Fprintf(out, "\n") _, err = fmt.Fprintf(w, "\n")
return err
} }
func writeRequiredFlag(cmd *Command, out *bytes.Buffer) { func writeRequiredFlag(cmd *Command, w io.Writer) error {
fmt.Fprintf(out, " must_have_one_flag=()\n") if _, err := fmt.Fprintf(w, " must_have_one_flag=()\n"); err != nil {
return err
}
flags := cmd.NonInheritedFlags() flags := cmd.NonInheritedFlags()
var visitErr error
flags.VisitAll(func(flag *pflag.Flag) { flags.VisitAll(func(flag *pflag.Flag) {
for key := range flag.Annotations { for key := range flag.Annotations {
switch key { switch key {
@ -328,67 +449,111 @@ func writeRequiredFlag(cmd *Command, out *bytes.Buffer) {
format += "=" format += "="
} }
format += "\")\n" format += "\")\n"
fmt.Fprintf(out, format, flag.Name) if _, err := fmt.Fprintf(w, format, flag.Name); err != nil {
visitErr = err
return
}
if len(flag.Shorthand) > 0 { if len(flag.Shorthand) > 0 {
fmt.Fprintf(out, " must_have_one_flag+=(\"-%s\")\n", flag.Shorthand) if _, err := fmt.Fprintf(w, " must_have_one_flag+=(\"-%s\")\n", flag.Shorthand); err != nil {
visitErr = err
return
}
} }
} }
} }
}) })
return visitErr
} }
func writeRequiredNoun(cmd *Command, out *bytes.Buffer) { func writeRequiredNouns(cmd *Command, w io.Writer) error {
fmt.Fprintf(out, " must_have_one_noun=()\n") if _, err := fmt.Fprintf(w, " must_have_one_noun=()\n"); err != nil {
return err
}
sort.Sort(sort.StringSlice(cmd.ValidArgs)) sort.Sort(sort.StringSlice(cmd.ValidArgs))
for _, value := range cmd.ValidArgs { for _, value := range cmd.ValidArgs {
fmt.Fprintf(out, " must_have_one_noun+=(%q)\n", value) if _, err := fmt.Fprintf(w, " must_have_one_noun+=(%q)\n", value); err != nil {
return err
}
} }
return nil
} }
func gen(cmd *Command, out *bytes.Buffer) { func writeArgAliases(cmd *Command, w io.Writer) error {
if _, err := fmt.Fprintf(w, " noun_aliases=()\n"); err != nil {
return err
}
sort.Sort(sort.StringSlice(cmd.ArgAliases))
for _, value := range cmd.ArgAliases {
if _, err := fmt.Fprintf(w, " noun_aliases+=(%q)\n", value); err != nil {
return err
}
}
return nil
}
func gen(cmd *Command, w io.Writer) error {
for _, c := range cmd.Commands() { for _, c := range cmd.Commands() {
if !c.IsAvailableCommand() || c == cmd.helpCommand { if !c.IsAvailableCommand() || c == cmd.helpCommand {
continue continue
} }
gen(c, out) if err := gen(c, w); err != nil {
return err
}
} }
commandName := cmd.CommandPath() commandName := cmd.CommandPath()
commandName = strings.Replace(commandName, " ", "_", -1) commandName = strings.Replace(commandName, " ", "_", -1)
fmt.Fprintf(out, "_%s()\n{\n", commandName) commandName = strings.Replace(commandName, ":", "__", -1)
fmt.Fprintf(out, " last_command=%q\n", commandName) if _, err := fmt.Fprintf(w, "_%s()\n{\n", commandName); err != nil {
writeCommands(cmd, out) return err
writeFlags(cmd, out) }
writeRequiredFlag(cmd, out) if _, err := fmt.Fprintf(w, " last_command=%q\n", commandName); err != nil {
writeRequiredNoun(cmd, out) return err
fmt.Fprintf(out, "}\n\n") }
if err := writeCommands(cmd, w); err != nil {
return err
}
if err := writeFlags(cmd, w); err != nil {
return err
}
if err := writeRequiredFlag(cmd, w); err != nil {
return err
}
if err := writeRequiredNouns(cmd, w); err != nil {
return err
}
if err := writeArgAliases(cmd, w); err != nil {
return err
}
if _, err := fmt.Fprintf(w, "}\n\n"); err != nil {
return err
}
return nil
} }
func (cmd *Command) GenBashCompletion(out *bytes.Buffer) { func (cmd *Command) GenBashCompletion(w io.Writer) error {
preamble(out) if err := preamble(w, cmd.Name()); err != nil {
if len(cmd.BashCompletionFunction) > 0 { return err
fmt.Fprintf(out, "%s\n", cmd.BashCompletionFunction)
} }
gen(cmd, out) if len(cmd.BashCompletionFunction) > 0 {
postscript(out, cmd.Name()) if _, err := fmt.Fprintf(w, "%s\n", cmd.BashCompletionFunction); err != nil {
return err
}
}
if err := gen(cmd, w); err != nil {
return err
}
return postscript(w, cmd.Name())
} }
func (cmd *Command) GenBashCompletionFile(filename string) error { func (cmd *Command) GenBashCompletionFile(filename string) error {
out := new(bytes.Buffer)
cmd.GenBashCompletion(out)
outFile, err := os.Create(filename) outFile, err := os.Create(filename)
if err != nil { if err != nil {
return err return err
} }
defer outFile.Close() defer outFile.Close()
_, err = outFile.Write(out.Bytes()) return cmd.GenBashCompletion(outFile)
if err != nil {
return err
}
return nil
} }
// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag, if it exists. // MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag, if it exists.
@ -412,6 +577,12 @@ func (cmd *Command) MarkFlagFilename(name string, extensions ...string) error {
return MarkFlagFilename(cmd.Flags(), name, extensions...) return MarkFlagFilename(cmd.Flags(), name, extensions...)
} }
// MarkFlagCustom adds the BashCompCustom annotation to the named flag, if it exists.
// Generated bash autocompletion will call the bash function f for the flag.
func (cmd *Command) MarkFlagCustom(name string, f string) error {
return MarkFlagCustom(cmd.Flags(), name, f)
}
// MarkPersistentFlagFilename adds the BashCompFilenameExt annotation to the named persistent flag, if it exists. // MarkPersistentFlagFilename adds the BashCompFilenameExt annotation to the named persistent flag, if it exists.
// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided. // Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided.
func (cmd *Command) MarkPersistentFlagFilename(name string, extensions ...string) error { func (cmd *Command) MarkPersistentFlagFilename(name string, extensions ...string) error {
@ -423,3 +594,9 @@ func (cmd *Command) MarkPersistentFlagFilename(name string, extensions ...string
func MarkFlagFilename(flags *pflag.FlagSet, name string, extensions ...string) error { func MarkFlagFilename(flags *pflag.FlagSet, name string, extensions ...string) error {
return flags.SetAnnotation(name, BashCompFilenameExt, extensions) return flags.SetAnnotation(name, BashCompFilenameExt, extensions)
} }
// MarkFlagCustom adds the BashCompCustom annotation to the named flag in the flag set, if it exists.
// Generated bash autocompletion will call the bash function f for the flag.
func MarkFlagCustom(flags *pflag.FlagSet, name string, f string) error {
return flags.SetAnnotation(name, BashCompCustom, []string{f})
}

View File

@ -80,7 +80,7 @@ The `BashCompletionFunction` option is really only valid/useful on the root comm
In the above example "pod" was assumed to already be typed. But if you want `kubectl get [tab][tab]` to show a list of valid "nouns" you have to set them. Simplified code from `kubectl get` looks like: In the above example "pod" was assumed to already be typed. But if you want `kubectl get [tab][tab]` to show a list of valid "nouns" you have to set them. Simplified code from `kubectl get` looks like:
```go ```go
validArgs []string = { "pods", "nodes", "services", "replicationControllers" } validArgs []string = { "pod", "node", "service", "replicationcontroller" }
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "get [(-o|--output=)json|yaml|template|...] (RESOURCE [NAME] | RESOURCE/NAME ...)", Use: "get [(-o|--output=)json|yaml|template|...] (RESOURCE [NAME] | RESOURCE/NAME ...)",
@ -99,9 +99,34 @@ Notice we put the "ValidArgs" on the "get" subcommand. Doing so will give result
```bash ```bash
# kubectl get [tab][tab] # kubectl get [tab][tab]
nodes pods replicationControllers services node pod replicationcontroller service
``` ```
## Plural form and shortcuts for nouns
If your nouns have a number of aliases, you can define them alongside `ValidArgs` using `ArgAliases`:
```go`
argAliases []string = { "pods", "nodes", "services", "svc", "replicationcontrollers", "rc" }
cmd := &cobra.Command{
...
ValidArgs: validArgs,
ArgAliases: argAliases
}
```
The aliases are not shown to the user on tab completion, but they are accepted as valid nouns by
the completion aglorithm if entered manually, e.g. in:
```bash
# kubectl get rc [tab][tab]
backend frontend database
```
Note that without declaring `rc` as an alias, the completion algorithm would show the list of nouns
in this example again instead of the replication controllers.
## Mark flags as required ## Mark flags as required
Most of the time completions will only show subcommands. But if a flag is required to make a subcommand work, you probably want it to show up when the user types [tab][tab]. Marking a flag as 'Required' is incredibly easy. Most of the time completions will only show subcommands. But if a flag is required to make a subcommand work, you probably want it to show up when the user types [tab][tab]. Marking a flag as 'Required' is incredibly easy.
@ -147,3 +172,35 @@ 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
Similar to the filename completion and filtering usingn cobra.BashCompFilenameExt, you can specifiy
a custom flag completion function with cobra.BashCompCustom:
```go
annotation := make(map[string][]string)
annotation[cobra.BashCompFilenameExt] = []string{"__kubectl_get_namespaces"}
flag := &pflag.Flag{
Name: "namespace",
Usage: usage,
Annotations: annotation,
}
cmd.Flags().AddFlag(flag)
```
In addition add the `__handle_namespace_flag` implementation in the `BashCompletionFunction`
value, e.g.:
```bash
__kubectl_get_namespaces()
{
local template
template="{{ range .items }}{{ .metadata.name }} {{ end }}"
local kubectl_out
if kubectl_out=$(kubectl get -o template --template="${template}" namespace 2>/dev/null); then
COMPREPLY=( $( compgen -W "${kubectl_out}[*]" -- "$cur" ) )
fi
}
```

View File

@ -26,27 +26,20 @@ import (
"unicode" "unicode"
) )
var templateFuncs template.FuncMap = template.FuncMap{ var templateFuncs = template.FuncMap{
"trim": strings.TrimSpace, "trim": strings.TrimSpace,
"trimRightSpace": trimRightSpace, "trimRightSpace": trimRightSpace,
"rpad": rpad, "appendIfNotPresent": appendIfNotPresent,
"gt": Gt, "rpad": rpad,
"eq": Eq, "gt": Gt,
"eq": Eq,
} }
var initializers []func() var initializers []func()
// automatic prefix matching can be a dangerous thing to automatically enable in CLI tools. // automatic prefix matching can be a dangerous thing to automatically enable in CLI tools.
// Set this to true to enable it // Set this to true to enable it
var EnablePrefixMatching bool = false var EnablePrefixMatching = false
// enables an information splash screen on Windows if the CLI is started from explorer.exe.
var EnableWindowsMouseTrap bool = true
var MousetrapHelpText string = `This is a command line tool
You need to open cmd.exe and run it from there.
`
//AddTemplateFunc adds a template function that's available to Usage and Help //AddTemplateFunc adds a template function that's available to Usage and Help
//template generation. //template generation.
@ -119,6 +112,14 @@ func trimRightSpace(s string) string {
return strings.TrimRightFunc(s, unicode.IsSpace) return strings.TrimRightFunc(s, unicode.IsSpace)
} }
// appendIfNotPresent will append stringToAppend to the end of s, but only if it's not yet present in s
func appendIfNotPresent(s, stringToAppend string) string {
if strings.Contains(s, stringToAppend) {
return s
}
return s + " " + stringToAppend
}
//rpad adds padding to the right of a string //rpad adds padding to the right of a string
func rpad(s string, padding int) string { func rpad(s string, padding int) string {
template := fmt.Sprintf("%%-%ds", padding) template := fmt.Sprintf("%%-%ds", padding)

View File

@ -104,7 +104,7 @@ func guessImportPath() string {
er("Cobra only supports project within $GOPATH") er("Cobra only supports project within $GOPATH")
} }
return filepath.Clean(strings.TrimPrefix(projectPath, getSrcPath())) return filepath.ToSlash(filepath.Clean(strings.TrimPrefix(projectPath, getSrcPath())))
} }
func getSrcPath() string { func getSrcPath() string {
@ -143,7 +143,7 @@ func guessProjectPath() {
srcPath := getSrcPath() srcPath := getSrcPath()
// if provided, inspect for logical locations // if provided, inspect for logical locations
if strings.ContainsRune(inputPath, os.PathSeparator) { if strings.ContainsRune(inputPath, os.PathSeparator) {
if filepath.IsAbs(inputPath) { if filepath.IsAbs(inputPath) || filepath.HasPrefix(inputPath, string(os.PathSeparator)) {
// if Absolute, use it // if Absolute, use it
projectPath = filepath.Clean(inputPath) projectPath = filepath.Clean(inputPath)
return return
@ -196,7 +196,7 @@ func isEmpty(path string) (bool, error) {
if err != nil { if err != nil {
return false, err return false, err
} }
list, err := f.Readdir(-1) list, _ := f.Readdir(-1)
// f.Close() - see bug fix above // f.Close() - see bug fix above
return len(list) == 0, nil return len(list) == 0, nil
} }

View File

@ -29,8 +29,8 @@ func init() {
// initialize Command // initialize Command
var initCmd = &cobra.Command{ var initCmd = &cobra.Command{
Use: "init [name]", Use: "init [name]",
Aliases: []string{"initialize", "initalise", "create"}, Aliases: []string{"initialize", "initialise", "create"},
Short: "Initalize a Cobra Application", Short: "Initialize a Cobra Application",
Long: `Initialize (cobra init) will create a new application, with a license Long: `Initialize (cobra init) will create a new application, with a license
and the appropriate structure for a Cobra-based CLI application. and the appropriate structure for a Cobra-based CLI application.
@ -41,7 +41,7 @@ and the appropriate structure for a Cobra-based CLI application.
* If an absolute path is provided, it will be created; * If an absolute path is provided, it will be created;
* If the directory already exists but is empty, it will be used. * If the directory already exists but is empty, it will be used.
Init will not use an exiting directory with contents.`, Init will not use an existing directory with contents.`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
switch len(args) { switch len(args) {
@ -55,11 +55,11 @@ Init will not use an exiting directory with contents.`,
er("init doesn't support more than 1 parameter") er("init doesn't support more than 1 parameter")
} }
guessProjectPath() guessProjectPath()
initalizePath(projectPath) initializePath(projectPath)
}, },
} }
func initalizePath(path string) { func initializePath(path string) {
b, err := exists(path) b, err := exists(path)
if err != nil { if err != nil {
er(err) er(err)
@ -150,7 +150,7 @@ import (
{{if .viper}} {{if .viper}}
var cfgFile string var cfgFile string
{{ end }} {{ end }}
// This 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",

View File

@ -24,7 +24,7 @@ import (
var cfgFile string var cfgFile string
var userLicense string var userLicense string
// This 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: "cobra", Use: "cobra",
Short: "A generator for Cobra based Applications", Short: "A generator for Cobra based Applications",

View File

@ -21,11 +21,8 @@ import (
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"strings" "strings"
"time"
"github.com/inconshreveable/mousetrap"
flag "github.com/spf13/pflag" flag "github.com/spf13/pflag"
) )
@ -48,8 +45,11 @@ type Command struct {
Long string Long string
// Examples of how to use the command // Examples of how to use the command
Example string Example string
// List of all valid non-flag arguments, used for bash completions *TODO* actually validate these // List of all valid non-flag arguments that are accepted in bash completions
ValidArgs []string ValidArgs []string
// List of aliases for ValidArgs. These are not suggested to the user in the bash
// completion, but accepted if entered manually.
ArgAliases []string
// Custom functions used by the bash autocompletion generator // Custom functions used by the bash autocompletion generator
BashCompletionFunction string BashCompletionFunction string
// Is this command deprecated and should print this string when used? // Is this command deprecated and should print this string when used?
@ -135,9 +135,8 @@ func (c *Command) getOut(def io.Writer) io.Writer {
if c.HasParent() { if c.HasParent() {
return c.parent.Out() return c.parent.Out()
} else {
return def
} }
return def
} }
func (c *Command) Out() io.Writer { func (c *Command) Out() io.Writer {
@ -197,14 +196,13 @@ func (c *Command) UsageFunc() (f func(*Command) error) {
if c.HasParent() { if c.HasParent() {
return c.parent.UsageFunc() return c.parent.UsageFunc()
} else { }
return func(c *Command) error { return func(c *Command) error {
err := tmpl(c.Out(), c.UsageTemplate(), c) err := tmpl(c.Out(), c.UsageTemplate(), c)
if err != nil { if err != nil {
fmt.Print(err) fmt.Print(err)
}
return err
} }
return err
} }
} }
@ -226,35 +224,32 @@ func (c *Command) HelpFunc() func(*Command, []string) {
} }
} }
var minUsagePadding int = 25 var minUsagePadding = 25
func (c *Command) UsagePadding() int { func (c *Command) UsagePadding() int {
if c.parent == nil || minUsagePadding > c.parent.commandsMaxUseLen { if c.parent == nil || minUsagePadding > c.parent.commandsMaxUseLen {
return minUsagePadding return minUsagePadding
} else {
return c.parent.commandsMaxUseLen
} }
return c.parent.commandsMaxUseLen
} }
var minCommandPathPadding int = 11 var minCommandPathPadding = 11
// //
func (c *Command) CommandPathPadding() int { func (c *Command) CommandPathPadding() int {
if c.parent == nil || minCommandPathPadding > c.parent.commandsMaxCommandPathLen { if c.parent == nil || minCommandPathPadding > c.parent.commandsMaxCommandPathLen {
return minCommandPathPadding return minCommandPathPadding
} else {
return c.parent.commandsMaxCommandPathLen
} }
return c.parent.commandsMaxCommandPathLen
} }
var minNamePadding int = 11 var minNamePadding = 11
func (c *Command) NamePadding() int { func (c *Command) NamePadding() int {
if c.parent == nil || minNamePadding > c.parent.commandsMaxNameLen { if c.parent == nil || minNamePadding > c.parent.commandsMaxNameLen {
return minNamePadding return minNamePadding
} else {
return c.parent.commandsMaxNameLen
} }
return c.parent.commandsMaxNameLen
} }
func (c *Command) UsageTemplate() string { func (c *Command) UsageTemplate() string {
@ -264,9 +259,9 @@ func (c *Command) UsageTemplate() string {
if c.HasParent() { if c.HasParent() {
return c.parent.UsageTemplate() return c.parent.UsageTemplate()
} else { }
return `Usage:{{if .Runnable}} return `Usage:{{if .Runnable}}
{{.UseLine}}{{if .HasFlags}} [flags]{{end}}{{end}}{{if .HasSubCommands}} {{if .HasFlags}}{{appendIfNotPresent .UseLine "[flags]"}}{{else}}{{.UseLine}}{{end}}{{end}}{{if .HasSubCommands}}
{{ .CommandPath}} [command]{{end}}{{if gt .Aliases 0}} {{ .CommandPath}} [command]{{end}}{{if gt .Aliases 0}}
Aliases: Aliases:
@ -290,7 +285,6 @@ Additional help topics:{{range .Commands}}{{if .IsHelpCommand}}
Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}}
` `
}
} }
func (c *Command) HelpTemplate() string { func (c *Command) HelpTemplate() string {
@ -300,11 +294,10 @@ func (c *Command) HelpTemplate() string {
if c.HasParent() { if c.HasParent() {
return c.parent.HelpTemplate() return c.parent.HelpTemplate()
} else { }
return `{{with or .Long .Short }}{{. | trim}} return `{{with or .Long .Short }}{{. | trim}}
{{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}` {{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`
}
} }
// Really only used when casting a command to a commander // Really only used when casting a command to a commander
@ -606,9 +599,8 @@ func (c *Command) errorMsgFromParse() string {
if len(x) > 0 { if len(x) > 0 {
return x[0] return x[0]
} else {
return ""
} }
return ""
} }
// Call execute to use the args (os.Args[1:] by default) // Call execute to use the args (os.Args[1:] by default)
@ -626,12 +618,9 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
return c.Root().ExecuteC() return c.Root().ExecuteC()
} }
if EnableWindowsMouseTrap && runtime.GOOS == "windows" { // windows hook
if mousetrap.StartedByExplorer() { if preExecHookFn != nil {
c.Print(MousetrapHelpText) preExecHookFn(c)
time.Sleep(5 * time.Second)
os.Exit(1)
}
} }
// initialize help as the last point possible to allow for user // initialize help as the last point possible to allow for user
@ -641,7 +630,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
var args []string var args []string
// Workaround FAIL with "go test -v" or "cobra.test -test.v", see #155 // Workaround FAIL with "go test -v" or "cobra.test -test.v", see #155
if len(c.args) == 0 && filepath.Base(os.Args[0]) != "cobra.test" { if c.args == nil && filepath.Base(os.Args[0]) != "cobra.test" {
args = os.Args[1:] args = os.Args[1:]
} else { } else {
args = c.args args = c.args
@ -661,13 +650,16 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
} }
err = cmd.execute(flags) err = cmd.execute(flags)
if err != nil { if err != nil {
// Always show help if requested, even if SilenceErrors is in
// effect
if err == flag.ErrHelp {
cmd.HelpFunc()(cmd, args)
return cmd, nil
}
// If root command has SilentErrors flagged, // If root command has SilentErrors flagged,
// all subcommands should respect it // all subcommands should respect it
if !cmd.SilenceErrors && !c.SilenceErrors { if !cmd.SilenceErrors && !c.SilenceErrors {
if err == flag.ErrHelp {
cmd.HelpFunc()(cmd, args)
return cmd, nil
}
c.Println("Error:", err.Error()) c.Println("Error:", err.Error())
} }
@ -747,7 +739,7 @@ func (c *Command) AddCommand(cmds ...*Command) {
if nameLen > c.commandsMaxNameLen { if nameLen > c.commandsMaxNameLen {
c.commandsMaxNameLen = nameLen c.commandsMaxNameLen = nameLen
} }
// If glabal normalization function exists, update all children // If global normalization function exists, update all children
if c.globNormFunc != nil { if c.globNormFunc != nil {
x.SetGlobalNormalizationFunc(c.globNormFunc) x.SetGlobalNormalizationFunc(c.globNormFunc)
} }
@ -789,18 +781,18 @@ main:
} }
} }
// Convenience method to Print to the defined output // Print is a convenience method to Print to the defined output
func (c *Command) Print(i ...interface{}) { func (c *Command) Print(i ...interface{}) {
fmt.Fprint(c.Out(), i...) fmt.Fprint(c.Out(), i...)
} }
// Convenience method to Println to the defined output // Println is a convenience method to Println to the defined output
func (c *Command) Println(i ...interface{}) { func (c *Command) Println(i ...interface{}) {
str := fmt.Sprintln(i...) str := fmt.Sprintln(i...)
c.Print(str) c.Print(str)
} }
// Convenience method to Printf to the defined output // Printf is a convenience method to Printf to the defined output
func (c *Command) Printf(format string, i ...interface{}) { func (c *Command) Printf(format string, i ...interface{}) {
str := fmt.Sprintf(format, i...) str := fmt.Sprintf(format, i...)
c.Print(str) c.Print(str)
@ -911,7 +903,7 @@ func (c *Command) Name() string {
return name return name
} }
// Determine if a given string is an alias of the command. // HasAlias determines if a given string is an alias of the command.
func (c *Command) HasAlias(s string) bool { func (c *Command) HasAlias(s string) bool {
for _, a := range c.Aliases { for _, a := range c.Aliases {
if a == s { if a == s {
@ -929,12 +921,12 @@ func (c *Command) HasExample() bool {
return len(c.Example) > 0 return len(c.Example) > 0
} }
// Determine if the command is itself runnable // Runnable determines if the command is itself runnable
func (c *Command) Runnable() bool { func (c *Command) Runnable() bool {
return c.Run != nil || c.RunE != nil return c.Run != nil || c.RunE != nil
} }
// Determine if the command has children commands // HasSubCommands determines if the command has children commands
func (c *Command) HasSubCommands() bool { func (c *Command) HasSubCommands() bool {
return len(c.commands) > 0 return len(c.commands) > 0
} }
@ -1126,7 +1118,7 @@ func (c *Command) HasInheritedFlags() bool {
return c.InheritedFlags().HasFlags() return c.InheritedFlags().HasFlags()
} }
// Climbs up the command tree looking for matching flag // Flag climbs up the command tree looking for matching flag
func (c *Command) Flag(name string) (flag *flag.Flag) { func (c *Command) Flag(name string) (flag *flag.Flag) {
flag = c.Flags().Lookup(name) flag = c.Flags().Lookup(name)
@ -1149,13 +1141,14 @@ func (c *Command) persistentFlag(name string) (flag *flag.Flag) {
return return
} }
// Parses persistent flag tree & local flags // ParseFlags parses persistent flag tree & local flags
func (c *Command) ParseFlags(args []string) (err error) { func (c *Command) ParseFlags(args []string) (err error) {
c.mergePersistentFlags() c.mergePersistentFlags()
err = c.Flags().Parse(args) err = c.Flags().Parse(args)
return return
} }
// Parent returns a commands parent command
func (c *Command) Parent() *Command { func (c *Command) Parent() *Command {
return c.parent return c.parent
} }

View File

@ -0,0 +1,5 @@
// +build !windows
package cobra
var preExecHookFn func(*Command)

View File

@ -0,0 +1,26 @@
// +build windows
package cobra
import (
"os"
"time"
"github.com/inconshreveable/mousetrap"
)
var preExecHookFn = preExecHook
// enables an information splash screen on Windows if the CLI is started from explorer.exe.
var MousetrapHelpText string = `This is a command line tool
You need to open cmd.exe and run it from there.
`
func preExecHook(c *Command) {
if mousetrap.StartedByExplorer() {
c.Print(MousetrapHelpText)
time.Sleep(5 * time.Second)
os.Exit(1)
}
}

View File

@ -11,63 +11,58 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package cobra package doc
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io"
"os" "os"
"path/filepath"
"sort" "sort"
"strings" "strings"
"time" "time"
mangen "github.com/cpuguy83/go-md2man/md2man" mangen "github.com/cpuguy83/go-md2man/md2man"
"github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
// GenManTree will call cmd.GenManTree(header, dir)
func GenManTree(cmd *Command, header *GenManHeader, dir string) {
cmd.GenManTree(header, dir)
}
// GenManTree will generate a man page for this command and all decendants // GenManTree will generate a man page for this command and all decendants
// in the directory given. The header may be nil. This function may not work // in the directory given. The header may be nil. This function may not work
// correctly if your command names have - in them. If you have `cmd` with two // correctly if your command names have - in them. If you have `cmd` with two
// subcmds, `sub` and `sub-third`. And `sub` has a subcommand called `third` // subcmds, `sub` and `sub-third`. And `sub` has a subcommand called `third`
// it is undefined which help output will be in the file `cmd-sub-third.1`. // it is undefined which help output will be in the file `cmd-sub-third.1`.
func (cmd *Command) GenManTree(header *GenManHeader, dir string) { func GenManTree(cmd *cobra.Command, header *GenManHeader, dir string) error {
if header == nil { if header == nil {
header = &GenManHeader{} header = &GenManHeader{}
} }
for _, c := range cmd.Commands() { for _, c := range cmd.Commands() {
if !c.IsAvailableCommand() || c == cmd.helpCommand { if !c.IsAvailableCommand() || c.IsHelpCommand() {
continue continue
} }
GenManTree(c, header, dir) if err := GenManTree(c, header, dir); err != nil {
return err
}
} }
out := new(bytes.Buffer)
needToResetTitle := header.Title == "" needToResetTitle := header.Title == ""
cmd.GenMan(header, out) basename := strings.Replace(cmd.CommandPath(), " ", "_", -1) + ".1"
filename := filepath.Join(dir, basename)
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
if err := GenMan(cmd, header, f); err != nil {
return err
}
if needToResetTitle { if needToResetTitle {
header.Title = "" header.Title = ""
} }
return nil
filename := cmd.CommandPath()
filename = dir + strings.Replace(filename, " ", "-", -1) + ".1"
outFile, err := os.Create(filename)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer outFile.Close()
_, err = outFile.Write(out.Bytes())
if err != nil {
fmt.Println(err)
os.Exit(1)
}
} }
// GenManHeader is a lot like the .TH header at the start of man pages. These // GenManHeader is a lot like the .TH header at the start of man pages. These
@ -83,20 +78,16 @@ type GenManHeader struct {
Manual string Manual string
} }
// GenMan will call cmd.GenMan(header, out) // GenMan will generate a man page for the given command and write it to
func GenMan(cmd *Command, header *GenManHeader, out *bytes.Buffer) { // w. The header argument may be nil, however obviously w may not.
cmd.GenMan(header, out) func GenMan(cmd *cobra.Command, header *GenManHeader, w io.Writer) error {
}
// GenMan will generate a man page for the given command in the out buffer.
// The header argument may be nil, however obviously out may not.
func (cmd *Command) GenMan(header *GenManHeader, out *bytes.Buffer) {
if header == nil { if header == nil {
header = &GenManHeader{} header = &GenManHeader{}
} }
buf := genMarkdown(cmd, header) b := genMan(cmd, header)
final := mangen.Render(buf) final := mangen.Render(b)
out.Write(final) _, err := w.Write(final)
return err
} }
func fillHeader(header *GenManHeader, name string) { func fillHeader(header *GenManHeader, name string) {
@ -116,7 +107,7 @@ func fillHeader(header *GenManHeader, name string) {
} }
} }
func manPreamble(out *bytes.Buffer, header *GenManHeader, name, short, long string) { func manPreamble(out io.Writer, header *GenManHeader, name, short, long string) {
dashName := strings.Replace(name, " ", "-", -1) dashName := strings.Replace(name, " ", "-", -1)
fmt.Fprintf(out, `%% %s(%s)%s fmt.Fprintf(out, `%% %s(%s)%s
%% %s %% %s
@ -130,7 +121,7 @@ func manPreamble(out *bytes.Buffer, header *GenManHeader, name, short, long stri
fmt.Fprintf(out, "%s\n\n", long) fmt.Fprintf(out, "%s\n\n", long)
} }
func manPrintFlags(out *bytes.Buffer, flags *pflag.FlagSet) { func manPrintFlags(out io.Writer, flags *pflag.FlagSet) {
flags.VisitAll(func(flag *pflag.Flag) { flags.VisitAll(func(flag *pflag.Flag) {
if len(flag.Deprecated) > 0 || flag.Hidden { if len(flag.Deprecated) > 0 || flag.Hidden {
return return
@ -158,7 +149,7 @@ func manPrintFlags(out *bytes.Buffer, flags *pflag.FlagSet) {
}) })
} }
func manPrintOptions(out *bytes.Buffer, command *Command) { func manPrintOptions(out io.Writer, command *cobra.Command) {
flags := command.NonInheritedFlags() flags := command.NonInheritedFlags()
if flags.HasFlags() { if flags.HasFlags() {
fmt.Fprintf(out, "# OPTIONS\n") fmt.Fprintf(out, "# OPTIONS\n")
@ -173,7 +164,7 @@ func manPrintOptions(out *bytes.Buffer, command *Command) {
} }
} }
func genMarkdown(cmd *Command, header *GenManHeader) []byte { func genMan(cmd *cobra.Command, header *GenManHeader) []byte {
// something like `rootcmd subcmd1 subcmd2` // something like `rootcmd subcmd1 subcmd2`
commandName := cmd.CommandPath() commandName := cmd.CommandPath()
// something like `rootcmd-subcmd1-subcmd2` // something like `rootcmd-subcmd1-subcmd2`
@ -195,13 +186,15 @@ func genMarkdown(cmd *Command, header *GenManHeader) []byte {
fmt.Fprintf(buf, "# EXAMPLE\n") fmt.Fprintf(buf, "# EXAMPLE\n")
fmt.Fprintf(buf, "```\n%s\n```\n", cmd.Example) fmt.Fprintf(buf, "```\n%s\n```\n", cmd.Example)
} }
if cmd.hasSeeAlso() { if hasSeeAlso(cmd) {
fmt.Fprintf(buf, "# SEE ALSO\n") fmt.Fprintf(buf, "# SEE ALSO\n")
seealsos := make([]string, 0)
if cmd.HasParent() { if cmd.HasParent() {
parentPath := cmd.Parent().CommandPath() parentPath := cmd.Parent().CommandPath()
dashParentPath := strings.Replace(parentPath, " ", "-", -1) dashParentPath := strings.Replace(parentPath, " ", "-", -1)
fmt.Fprintf(buf, "**%s(%s)**", dashParentPath, header.Section) seealso := fmt.Sprintf("**%s(%s)**", dashParentPath, header.Section)
cmd.VisitParents(func(c *Command) { seealsos = append(seealsos, seealso)
cmd.VisitParents(func(c *cobra.Command) {
if c.DisableAutoGenTag { if c.DisableAutoGenTag {
cmd.DisableAutoGenTag = c.DisableAutoGenTag cmd.DisableAutoGenTag = c.DisableAutoGenTag
} }
@ -209,16 +202,14 @@ func genMarkdown(cmd *Command, header *GenManHeader) []byte {
} }
children := cmd.Commands() children := cmd.Commands()
sort.Sort(byName(children)) sort.Sort(byName(children))
for i, c := range children { for _, c := range children {
if !c.IsAvailableCommand() || c == cmd.helpCommand { if !c.IsAvailableCommand() || c.IsHelpCommand() {
continue continue
} }
if cmd.HasParent() || i > 0 { seealso := fmt.Sprintf("**%s-%s(%s)**", dashCommandName, c.Name(), header.Section)
fmt.Fprintf(buf, ", ") seealsos = append(seealsos, seealso)
}
fmt.Fprintf(buf, "**%s-%s(%s)**", dashCommandName, c.Name(), header.Section)
} }
fmt.Fprintf(buf, "\n") fmt.Fprintf(buf, "%s\n", strings.Join(seealsos, ", "))
} }
if !cmd.DisableAutoGenTag { if !cmd.DisableAutoGenTag {
fmt.Fprintf(buf, "# HISTORY\n%s Auto generated by spf13/cobra\n", header.Date.Format("2-Jan-2006")) fmt.Fprintf(buf, "# HISTORY\n%s Auto generated by spf13/cobra\n", header.Date.Format("2-Jan-2006"))

View File

@ -7,6 +7,7 @@ package main
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
) )
func main() { func main() {
@ -18,7 +19,7 @@ func main() {
Title: "MINE", Title: "MINE",
Section: "3", Section: "3",
} }
cmd.GenManTree(header, "/tmp") doc.GenManTree(cmd, header, "/tmp")
} }
``` ```

View File

@ -0,0 +1,175 @@
//Copyright 2015 Red Hat Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package doc
import (
"fmt"
"io"
"os"
"path/filepath"
"sort"
"strings"
"time"
"github.com/spf13/cobra"
)
func printOptions(w io.Writer, cmd *cobra.Command, name string) error {
flags := cmd.NonInheritedFlags()
flags.SetOutput(w)
if flags.HasFlags() {
if _, err := fmt.Fprintf(w, "### Options\n\n```\n"); err != nil {
return err
}
flags.PrintDefaults()
if _, err := fmt.Fprintf(w, "```\n\n"); err != nil {
return err
}
}
parentFlags := cmd.InheritedFlags()
parentFlags.SetOutput(w)
if parentFlags.HasFlags() {
if _, err := fmt.Fprintf(w, "### Options inherited from parent commands\n\n```\n"); err != nil {
return err
}
parentFlags.PrintDefaults()
if _, err := fmt.Fprintf(w, "```\n\n"); err != nil {
return err
}
}
return nil
}
func GenMarkdown(cmd *cobra.Command, w io.Writer) error {
return GenMarkdownCustom(cmd, w, func(s string) string { return s })
}
func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string) string) error {
name := cmd.CommandPath()
short := cmd.Short
long := cmd.Long
if len(long) == 0 {
long = short
}
if _, err := fmt.Fprintf(w, "## %s\n\n", name); err != nil {
return err
}
if _, err := fmt.Fprintf(w, "%s\n\n", short); err != nil {
return err
}
if _, err := fmt.Fprintf(w, "### Synopsis\n\n"); err != nil {
return err
}
if _, err := fmt.Fprintf(w, "\n%s\n\n", long); err != nil {
return err
}
if cmd.Runnable() {
if _, err := fmt.Fprintf(w, "```\n%s\n```\n\n", cmd.UseLine()); err != nil {
return err
}
}
if len(cmd.Example) > 0 {
if _, err := fmt.Fprintf(w, "### Examples\n\n"); err != nil {
return err
}
if _, err := fmt.Fprintf(w, "```\n%s\n```\n\n", cmd.Example); err != nil {
return err
}
}
if err := printOptions(w, cmd, name); err != nil {
return err
}
if hasSeeAlso(cmd) {
if _, err := fmt.Fprintf(w, "### SEE ALSO\n"); err != nil {
return err
}
if cmd.HasParent() {
parent := cmd.Parent()
pname := parent.CommandPath()
link := pname + ".md"
link = strings.Replace(link, " ", "_", -1)
if _, err := fmt.Fprintf(w, "* [%s](%s)\t - %s\n", pname, linkHandler(link), parent.Short); err != nil {
return err
}
cmd.VisitParents(func(c *cobra.Command) {
if c.DisableAutoGenTag {
cmd.DisableAutoGenTag = c.DisableAutoGenTag
}
})
}
children := cmd.Commands()
sort.Sort(byName(children))
for _, child := range children {
if !child.IsAvailableCommand() || child.IsHelpCommand() {
continue
}
cname := name + " " + child.Name()
link := cname + ".md"
link = strings.Replace(link, " ", "_", -1)
if _, err := fmt.Fprintf(w, "* [%s](%s)\t - %s\n", cname, linkHandler(link), child.Short); err != nil {
return err
}
}
if _, err := fmt.Fprintf(w, "\n"); err != nil {
return err
}
}
if !cmd.DisableAutoGenTag {
if _, err := fmt.Fprintf(w, "###### Auto generated by spf13/cobra on %s\n", time.Now().Format("2-Jan-2006")); err != nil {
return err
}
}
return nil
}
func GenMarkdownTree(cmd *cobra.Command, dir string) error {
identity := func(s string) string { return s }
emptyStr := func(s string) string { return "" }
return GenMarkdownTreeCustom(cmd, dir, emptyStr, identity)
}
func GenMarkdownTreeCustom(cmd *cobra.Command, dir string, filePrepender, linkHandler func(string) string) error {
for _, c := range cmd.Commands() {
if !c.IsAvailableCommand() || c.IsHelpCommand() {
continue
}
if err := GenMarkdownTreeCustom(c, dir, filePrepender, linkHandler); err != nil {
return err
}
}
basename := strings.Replace(cmd.CommandPath(), " ", "_", -1) + ".md"
filename := filepath.Join(dir, basename)
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
if _, err := io.WriteString(f, filePrepender(filename)); err != nil {
return err
}
if err := GenMarkdownCustom(cmd, f, linkHandler); err != nil {
return err
}
return nil
}

View File

@ -1,5 +1,26 @@
# Generating Markdown Docs For Your Own cobra.Command # Generating Markdown Docs For Your Own cobra.Command
Generating man pages from a cobra command is incredibly easy. An example is as follows:
```go
package main
import (
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
)
func main() {
cmd := &cobra.Command{
Use: "test",
Short: "my test program",
}
doc.GenMarkdownTree(cmd, "/tmp")
}
```
That will get you a Markdown document `/tmp/test.md`
## Generate markdown docs for the entire command tree ## Generate markdown docs for the entire command tree
This program can actually generate docs for the kubectl command in the kubernetes project This program can actually generate docs for the kubectl command in the kubernetes project
@ -11,13 +32,15 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd" kubectlcmd "k8s.io/kubernetes/pkg/kubectl/cmd"
"github.com/spf13/cobra" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"github.com/spf13/cobra/doc"
) )
func main() { func main() {
kubectl := cmd.NewFactory(nil).NewKubectlCommand(os.Stdin, ioutil.Discard, ioutil.Discard) cmd := kubectlcmd.NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, ioutil.Discard, ioutil.Discard)
cobra.GenMarkdownTree(kubectl, "./") doc.GenMarkdownTree(cmd, "./")
} }
``` ```
@ -29,7 +52,7 @@ You may wish to have more control over the output, or only generate for a single
```go ```go
out := new(bytes.Buffer) out := new(bytes.Buffer)
cobra.GenMarkdown(cmd, out) doc.GenMarkdown(cmd, out)
``` ```
This will write the markdown doc for ONLY "cmd" into the out, buffer. This will write the markdown doc for ONLY "cmd" into the out, buffer.
@ -39,14 +62,14 @@ This will write the markdown doc for ONLY "cmd" into the out, buffer.
Both `GenMarkdown` and `GenMarkdownTree` have alternate versions with callbacks to get some control of the output: Both `GenMarkdown` and `GenMarkdownTree` have alternate versions with callbacks to get some control of the output:
```go ```go
func GenMarkdownTreeCustom(cmd *Command, dir string, filePrepender func(string) string, linkHandler func(string) string) { func GenMarkdownTreeCustom(cmd *Command, dir string, filePrepender, linkHandler func(string) string) error {
//... //...
} }
``` ```
```go ```go
func GenMarkdownCustom(cmd *Command, out *bytes.Buffer, linkHandler func(string) string) { func GenMarkdownCustom(cmd *Command, out *bytes.Buffer, linkHandler func(string) string) error {
//... //...
} }
``` ```

View File

@ -11,24 +11,28 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package cobra package doc
import "github.com/spf13/cobra"
// Test to see if we have a reason to print See Also information in docs // Test to see if we have a reason to print See Also information in docs
// Basically this is a test for a parent commend or a subcommand which is // Basically this is a test for a parent commend or a subcommand which is
// both not deprecated and not the autogenerated help command. // both not deprecated and not the autogenerated help command.
func (cmd *Command) hasSeeAlso() bool { func hasSeeAlso(cmd *cobra.Command) bool {
if cmd.HasParent() { if cmd.HasParent() {
return true return true
} }
children := cmd.Commands() for _, c := range cmd.Commands() {
if len(children) == 0 { if !c.IsAvailableCommand() || c.IsHelpCommand() {
return false
}
for _, c := range children {
if !c.IsAvailableCommand() || c == cmd.helpCommand {
continue continue
} }
return true return true
} }
return false return false
} }
type byName []*cobra.Command
func (s byName) Len() int { return len(s) }
func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() }

View File

@ -1,162 +0,0 @@
//Copyright 2015 Red Hat Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cobra
import (
"bytes"
"fmt"
"os"
"sort"
"strings"
"time"
)
func printOptions(out *bytes.Buffer, cmd *Command, name string) {
flags := cmd.NonInheritedFlags()
flags.SetOutput(out)
if flags.HasFlags() {
fmt.Fprintf(out, "### Options\n\n```\n")
flags.PrintDefaults()
fmt.Fprintf(out, "```\n\n")
}
parentFlags := cmd.InheritedFlags()
parentFlags.SetOutput(out)
if parentFlags.HasFlags() {
fmt.Fprintf(out, "### Options inherited from parent commands\n\n```\n")
parentFlags.PrintDefaults()
fmt.Fprintf(out, "```\n\n")
}
}
type byName []*Command
func (s byName) Len() int { return len(s) }
func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() }
func GenMarkdown(cmd *Command, out *bytes.Buffer) {
cmd.GenMarkdown(out)
}
func (cmd *Command) GenMarkdown(out *bytes.Buffer) {
cmd.GenMarkdownCustom(out, func(s string) string { return s })
}
func GenMarkdownCustom(cmd *Command, out *bytes.Buffer, linkHandler func(string) string) {
cmd.GenMarkdownCustom(out, linkHandler)
}
func (cmd *Command) GenMarkdownCustom(out *bytes.Buffer, linkHandler func(string) string) {
name := cmd.CommandPath()
short := cmd.Short
long := cmd.Long
if len(long) == 0 {
long = short
}
fmt.Fprintf(out, "## %s\n\n", name)
fmt.Fprintf(out, "%s\n\n", short)
fmt.Fprintf(out, "### Synopsis\n\n")
fmt.Fprintf(out, "\n%s\n\n", long)
if cmd.Runnable() {
fmt.Fprintf(out, "```\n%s\n```\n\n", cmd.UseLine())
}
if len(cmd.Example) > 0 {
fmt.Fprintf(out, "### Examples\n\n")
fmt.Fprintf(out, "```\n%s\n```\n\n", cmd.Example)
}
printOptions(out, cmd, name)
if cmd.hasSeeAlso() {
fmt.Fprintf(out, "### SEE ALSO\n")
if cmd.HasParent() {
parent := cmd.Parent()
pname := parent.CommandPath()
link := pname + ".md"
link = strings.Replace(link, " ", "_", -1)
fmt.Fprintf(out, "* [%s](%s)\t - %s\n", pname, linkHandler(link), parent.Short)
cmd.VisitParents(func(c *Command) {
if c.DisableAutoGenTag {
cmd.DisableAutoGenTag = c.DisableAutoGenTag
}
})
}
children := cmd.Commands()
sort.Sort(byName(children))
for _, child := range children {
if !child.IsAvailableCommand() || child == cmd.helpCommand {
continue
}
cname := name + " " + child.Name()
link := cname + ".md"
link = strings.Replace(link, " ", "_", -1)
fmt.Fprintf(out, "* [%s](%s)\t - %s\n", cname, linkHandler(link), child.Short)
}
fmt.Fprintf(out, "\n")
}
if !cmd.DisableAutoGenTag {
fmt.Fprintf(out, "###### Auto generated by spf13/cobra on %s\n", time.Now().Format("2-Jan-2006"))
}
}
func GenMarkdownTree(cmd *Command, dir string) {
cmd.GenMarkdownTree(dir)
}
func (cmd *Command) GenMarkdownTree(dir string) {
identity := func(s string) string { return s }
emptyStr := func(s string) string { return "" }
cmd.GenMarkdownTreeCustom(dir, emptyStr, identity)
}
func GenMarkdownTreeCustom(cmd *Command, dir string, filePrepender func(string) string, linkHandler func(string) string) {
cmd.GenMarkdownTreeCustom(dir, filePrepender, linkHandler)
}
func (cmd *Command) GenMarkdownTreeCustom(dir string, filePrepender func(string) string, linkHandler func(string) string) {
for _, c := range cmd.Commands() {
if !c.IsAvailableCommand() || c == cmd.helpCommand {
continue
}
c.GenMarkdownTreeCustom(dir, filePrepender, linkHandler)
}
out := new(bytes.Buffer)
cmd.GenMarkdownCustom(out, linkHandler)
filename := cmd.CommandPath()
filename = dir + strings.Replace(filename, " ", "_", -1) + ".md"
outFile, err := os.Create(filename)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer outFile.Close()
_, err = outFile.WriteString(filePrepender(filename))
if err != nil {
fmt.Println(err)
os.Exit(1)
}
_, err = outFile.Write(out.Bytes())
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}