use dep instead of glide

This commit is contained in:
fatedier
2018-05-08 02:35:13 +08:00
parent 8a6d6c534a
commit dd8f788ca4
1209 changed files with 7782 additions and 310762 deletions

View File

@@ -1,38 +0,0 @@
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

@@ -1,241 +0,0 @@
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

@@ -1,217 +0,0 @@
package cobra
import (
"bytes"
"fmt"
"os"
"os/exec"
"regexp"
"strings"
"testing"
)
func checkOmit(t *testing.T, found, unexpected string) {
if strings.Contains(found, unexpected) {
t.Errorf("Got: %q\nBut should not have!\n", unexpected)
}
}
func check(t *testing.T, found, expected string) {
if !strings.Contains(found, expected) {
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)
}
}
func runShellCheck(s string) error {
excluded := []string{
"SC2034", // PREFIX appears unused. Verify it or export it.
}
cmd := exec.Command("shellcheck", "-s", "bash", "-", "-e", strings.Join(excluded, ","))
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
stdin, err := cmd.StdinPipe()
if err != nil {
return err
}
go func() {
stdin.Write([]byte(s))
stdin.Close()
}()
return cmd.Run()
}
// World worst custom function, just keep telling you to enter hello!
const bashCompletionFunc = `__custom_func() {
COMPREPLY=( "hello" )
}
`
func TestBashCompletions(t *testing.T) {
rootCmd := &Command{
Use: "root",
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")
// Filename.
rootCmd.Flags().String("filename", "", "Enter a filename")
rootCmd.MarkFlagFilename("filename", "json", "yaml", "yml")
// Persistent filename.
rootCmd.PersistentFlags().String("persistent-filename", "", "Enter a filename")
rootCmd.MarkPersistentFlagFilename("persistent-filename")
rootCmd.MarkPersistentFlagRequired("persistent-filename")
// Filename extensions.
rootCmd.Flags().String("filename-ext", "", "Enter a filename (extension limited)")
rootCmd.MarkFlagFilename("filename-ext")
rootCmd.Flags().String("custom", "", "Enter a filename (extension limited)")
rootCmd.MarkFlagCustom("custom", "__complete_custom")
// Subdirectories in a given directory.
rootCmd.Flags().String("theme", "", "theme to use (located in /themes/THEMENAME/)")
rootCmd.Flags().SetAnnotation("theme", BashCompSubdirsInDir, []string{"themes"})
echoCmd := &Command{
Use: "echo [string to echo]",
Aliases: []string{"say"},
Short: "Echo anything to the screen",
Long: "an utterly useless command for testing.",
Example: "Just run cobra-test echo",
Run: emptyRun,
}
echoCmd.Flags().String("filename", "", "Enter a filename")
echoCmd.MarkFlagFilename("filename", "json", "yaml", "yml")
echoCmd.Flags().String("config", "", "config to use (located in /config/PROFILE/)")
echoCmd.Flags().SetAnnotation("config", BashCompSubdirsInDir, []string{"config"})
printCmd := &Command{
Use: "print [string to print]",
Args: MinimumNArgs(1),
Short: "Print anything to the screen",
Long: "an absolutely utterly useless command for testing.",
Run: emptyRun,
}
deprecatedCmd := &Command{
Use: "deprecated [can't do anything here]",
Args: NoArgs,
Short: "A command which is deprecated",
Long: "an absolutely utterly useless command for testing deprecation!.",
Deprecated: "Please use echo instead",
Run: emptyRun,
}
colonCmd := &Command{
Use: "cmd:colon",
Run: emptyRun,
}
timesCmd := &Command{
Use: "times [# times] [string to echo]",
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,
}
echoCmd.AddCommand(timesCmd)
rootCmd.AddCommand(echoCmd, printCmd, deprecatedCmd, colonCmd)
buf := new(bytes.Buffer)
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(t, output, `must_have_one_flag+=("--introot=")`)
check(t, output, `must_have_one_flag+=("--persistent-filename=")`)
// check for custom completion function
check(t, output, `COMPREPLY=( "hello" )`)
// check for required nouns
check(t, output, `must_have_one_noun+=("pod")`)
// check for noun aliases
check(t, output, `noun_aliases+=("pods")`)
check(t, output, `noun_aliases+=("rc")`)
checkOmit(t, output, `must_have_one_noun+=("pods")`)
// check for filename extension flags
check(t, output, `flags_completion+=("_filedir")`)
// check for filename extension flags
check(t, output, `must_have_one_noun+=("three")`)
// check for filename extension flags
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(t, output, `flags_completion+=("__complete_custom")`)
// check for subdirs_in_dir flags
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, output, deprecatedCmd.Name())
// If available, run shellcheck against the script.
if err := exec.Command("which", "shellcheck").Run(); err != nil {
return
}
if err := runShellCheck(output); err != nil {
t.Fatalf("shellcheck failed: %v", err)
}
}
func TestBashCompletionHiddenFlag(t *testing.T) {
c := &Command{Use: "c", Run: emptyRun}
const flagName = "hiddenFlag"
c.Flags().Bool(flagName, false, "")
c.Flags().MarkHidden(flagName)
buf := new(bytes.Buffer)
c.GenBashCompletion(buf)
output := buf.String()
if strings.Contains(output, flagName) {
t.Errorf("Expected completion to not include %q flag: Got %v", flagName, output)
}
}
func TestBashCompletionDeprecatedFlag(t *testing.T) {
c := &Command{Use: "c", Run: emptyRun}
const flagName = "deprecated-flag"
c.Flags().Bool(flagName, false, "")
c.Flags().MarkDeprecated(flagName, "use --not-deprecated instead")
buf := new(bytes.Buffer)
c.GenBashCompletion(buf)
output := buf.String()
if strings.Contains(output, flagName) {
t.Errorf("expected completion to not include %q flag: Got %v", flagName, output)
}
}

View File

@@ -1,94 +0,0 @@
# Cobra Generator
Cobra provides its own program that will create your application and add any
commands you want. It's the easiest way to incorporate Cobra into your application.
In order to use the cobra command, compile it using the following command:
go get github.com/spf13/cobra/cobra
This will create the cobra executable under your `$GOPATH/bin` directory.
### cobra init
The `cobra init [app]` command will create your initial application code
for you. It is a very powerful application that will populate your program with
the right structure so you can immediately enjoy all the benefits of Cobra. It
will also automatically apply the license you specify to your application.
Cobra init is pretty smart. You can provide it a full path, or simply a path
similar to what is expected in the import.
```
cobra init github.com/spf13/newApp
```
### cobra add
Once an application is initialized, Cobra can create additional commands for you.
Let's say you created an app and you wanted the following commands for it:
* app serve
* app config
* app config create
In your project directory (where your main.go file is) you would run the following:
```
cobra add serve
cobra add config
cobra add create -p 'configCmd'
```
*Note: Use camelCase (not snake_case/snake-case) for command names.
Otherwise, you will encounter errors.
For example, `cobra add add-user` is incorrect, but `cobra add addUser` is valid.*
Once you have run these three commands you would have an app structure similar to
the following:
```
▾ app/
▾ cmd/
serve.go
config.go
create.go
main.go
```
At this point you can run `go run main.go` and it would run your app. `go run
main.go serve`, `go run main.go config`, `go run main.go config create` along
with `go run main.go help serve`, etc. would all work.
Obviously you haven't added your own code to these yet. The commands are ready
for you to give them their tasks. Have fun!
### Configuring the cobra generator
The Cobra generator will be easier to use if you provide a simple configuration
file which will help you eliminate providing a bunch of repeated information in
flags over and over.
An example ~/.cobra.yaml file:
```yaml
author: Steve Francia <spf@spf13.com>
license: MIT
```
You can specify no license by setting `license` to `none` or you can specify
a custom license:
```yaml
license:
header: This file is part of {{ .appName }}.
text: |
{{ .copyright }}
This is my license. There are many like it, but this one is mine.
My license is my best friend. It is my life. I must master it as I must
master my life.
```
You can also use built-in licenses. For example, **GPLv2**, **GPLv3**, **LGPL**,
**AGPL**, **MIT**, **2-Clause BSD** or **3-Clause BSD**.

View File

@@ -1,179 +0,0 @@
// Copyright © 2015 Steve Francia <spf@spf13.com>.
//
// 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 cmd
import (
"fmt"
"os"
"path/filepath"
"unicode"
"github.com/spf13/cobra"
)
func init() {
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")
}
var packageName, parentName string
var addCmd = &cobra.Command{
Use: "add [command name]",
Aliases: []string{"command"},
Short: "Add a command to a Cobra Application",
Long: `Add (cobra add) will create a new command, with a license and
the appropriate structure for a Cobra-based CLI application,
and register it to its parent (default rootCmd).
If you want your command to be public, pass in the command name
with an initial uppercase letter.
Example: cobra add server -> resulting in a new cmd/server.go`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) < 1 {
er("add needs a name for the command")
}
var project *Project
if packageName != "" {
project = NewProject(packageName)
} else {
wd, err := os.Getwd()
if err != nil {
er(err)
}
project = NewProjectFromPath(wd)
}
cmdName := validateCmdName(args[0])
cmdPath := filepath.Join(project.CmdPath(), cmdName+".go")
createCmdFile(project.License(), cmdPath, cmdName)
fmt.Fprintln(cmd.OutOrStdout(), cmdName, "created at", cmdPath)
},
}
// validateCmdName returns source without any dashes and underscore.
// If there will be dash or underscore, next letter will be uppered.
// It supports only ASCII (1-byte character) strings.
// https://github.com/spf13/cobra/issues/269
func validateCmdName(source string) string {
i := 0
l := len(source)
// The output is initialized on demand, then first dash or underscore
// occurs.
var output string
for i < l {
if source[i] == '-' || source[i] == '_' {
if output == "" {
output = source[:i]
}
// If it's last rune and it's dash or underscore,
// don't add it output and break the loop.
if i == l-1 {
break
}
// If next character is dash or underscore,
// just skip the current character.
if source[i+1] == '-' || source[i+1] == '_' {
i++
continue
}
// If the current character is dash or underscore,
// upper next letter and add to output.
output += string(unicode.ToUpper(rune(source[i+1])))
// We know, what source[i] is dash or underscore and source[i+1] is
// uppered character, so make i = i+2.
i += 2
continue
}
// If the current character isn't dash or underscore,
// just add it.
if output != "" {
output += string(source[i])
}
i++
}
if output == "" {
return source // source is initially valid name.
}
return output
}
func createCmdFile(license License, path, cmdName string) {
template := `{{comment .copyright}}
{{if .license}}{{comment .license}}{{end}}
package {{.cmdPackage}}
import (
"fmt"
"github.com/spf13/cobra"
)
// {{.cmdName}}Cmd represents the {{.cmdName}} command
var {{.cmdName}}Cmd = &cobra.Command{
Use: "{{.cmdName}}",
Short: "A brief description of your command",
Long: ` + "`" + `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.` + "`" + `,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("{{.cmdName}} called")
},
}
func init() {
{{.parentName}}.AddCommand({{.cmdName}}Cmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// {{.cmdName}}Cmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// {{.cmdName}}Cmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
`
data := make(map[string]interface{})
data["copyright"] = copyrightLine()
data["license"] = license.Header
data["cmdPackage"] = filepath.Base(filepath.Dir(path)) // last dir of path
data["parentName"] = parentName
data["cmdName"] = cmdName
cmdScript, err := executeTemplate(template, data)
if err != nil {
er(err)
}
err = writeStringToFile(path, cmdScript)
if err != nil {
er(err)
}
}

View File

@@ -1,109 +0,0 @@
package cmd
import (
"errors"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/spf13/viper"
)
// TestGoldenAddCmd initializes the project "github.com/spf13/testproject"
// in GOPATH, adds "test" command
// and compares the content of all files in cmd directory of testproject
// with appropriate golden files.
// Use -update to update existing golden files.
func TestGoldenAddCmd(t *testing.T) {
projectName := "github.com/spf13/testproject"
project := NewProject(projectName)
defer os.RemoveAll(project.AbsPath())
viper.Set("author", "NAME HERE <EMAIL ADDRESS>")
viper.Set("license", "apache")
viper.Set("year", 2017)
defer viper.Set("author", nil)
defer viper.Set("license", nil)
defer viper.Set("year", nil)
// Initialize the project first.
initializeProject(project)
// Then add the "test" command.
cmdName := "test"
cmdPath := filepath.Join(project.CmdPath(), cmdName+".go")
createCmdFile(project.License(), cmdPath, cmdName)
expectedFiles := []string{".", "root.go", "test.go"}
gotFiles := []string{}
// Check project file hierarchy and compare the content of every single file
// with appropriate golden file.
err := filepath.Walk(project.CmdPath(), func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Make path relative to project.CmdPath().
// E.g. path = "/home/user/go/src/github.com/spf13/testproject/cmd/root.go"
// then it returns just "root.go".
relPath, err := filepath.Rel(project.CmdPath(), path)
if err != nil {
return err
}
relPath = filepath.ToSlash(relPath)
gotFiles = append(gotFiles, relPath)
goldenPath := filepath.Join("testdata", filepath.Base(path)+".golden")
switch relPath {
// Known directories.
case ".":
return nil
// Known files.
case "root.go", "test.go":
if *update {
got, err := ioutil.ReadFile(path)
if err != nil {
return err
}
ioutil.WriteFile(goldenPath, got, 0644)
}
return compareFiles(path, goldenPath)
}
// Unknown file.
return errors.New("unknown file: " + path)
})
if err != nil {
t.Fatal(err)
}
// Check if some files lack.
if err := checkLackFiles(expectedFiles, gotFiles); err != nil {
t.Fatal(err)
}
}
func TestValidateCmdName(t *testing.T) {
testCases := []struct {
input string
expected string
}{
{"cmdName", "cmdName"},
{"cmd_name", "cmdName"},
{"cmd-name", "cmdName"},
{"cmd______Name", "cmdName"},
{"cmd------Name", "cmdName"},
{"cmd______name", "cmdName"},
{"cmd------name", "cmdName"},
{"cmdName-----", "cmdName"},
{"cmdname-", "cmdname"},
}
for _, testCase := range testCases {
got := validateCmdName(testCase.input)
if testCase.expected != got {
t.Errorf("Expected %q, got %q", testCase.expected, got)
}
}
}

View File

@@ -1,77 +0,0 @@
package cmd
import (
"bytes"
"errors"
"flag"
"fmt"
"io/ioutil"
"os/exec"
)
var update = flag.Bool("update", false, "update .golden files")
func init() {
// Mute commands.
addCmd.SetOutput(new(bytes.Buffer))
initCmd.SetOutput(new(bytes.Buffer))
}
// compareFiles compares the content of files with pathA and pathB.
// If contents are equal, it returns nil.
// If not, it returns which files are not equal
// and diff (if system has diff command) between these files.
func compareFiles(pathA, pathB string) error {
contentA, err := ioutil.ReadFile(pathA)
if err != nil {
return err
}
contentB, err := ioutil.ReadFile(pathB)
if err != nil {
return err
}
if !bytes.Equal(contentA, contentB) {
output := new(bytes.Buffer)
output.WriteString(fmt.Sprintf("%q and %q are not equal!\n\n", pathA, pathB))
diffPath, err := exec.LookPath("diff")
if err != nil {
// Don't execute diff if it can't be found.
return nil
}
diffCmd := exec.Command(diffPath, "-u", pathA, pathB)
diffCmd.Stdout = output
diffCmd.Stderr = output
output.WriteString("$ diff -u " + pathA + " " + pathB + "\n")
if err := diffCmd.Run(); err != nil {
output.WriteString("\n" + err.Error())
}
return errors.New(output.String())
}
return nil
}
// checkLackFiles checks if all elements of expected are in got.
func checkLackFiles(expected, got []string) error {
lacks := make([]string, 0, len(expected))
for _, ev := range expected {
if !stringInStringSlice(ev, got) {
lacks = append(lacks, ev)
}
}
if len(lacks) > 0 {
return fmt.Errorf("Lack %v file(s): %v", len(lacks), lacks)
}
return nil
}
// stringInStringSlice checks if s is an element of slice.
func stringInStringSlice(s string, slice []string) bool {
for _, v := range slice {
if s == v {
return true
}
}
return false
}

View File

@@ -1,168 +0,0 @@
// Copyright © 2015 Steve Francia <spf@spf13.com>.
//
// 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 cmd
import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"text/template"
)
var srcPaths []string
func init() {
// Initialize srcPaths.
envGoPath := os.Getenv("GOPATH")
goPaths := filepath.SplitList(envGoPath)
if len(goPaths) == 0 {
// Adapted from https://github.com/Masterminds/glide/pull/798/files.
// As of Go 1.8 the GOPATH is no longer required to be set. Instead there
// is a default value. If there is no GOPATH check for the default value.
// Note, checking the GOPATH first to avoid invoking the go toolchain if
// possible.
goExecutable := os.Getenv("COBRA_GO_EXECUTABLE")
if len(goExecutable) <= 0 {
goExecutable = "go"
}
out, err := exec.Command(goExecutable, "env", "GOPATH").Output()
if err != nil {
er(err)
}
toolchainGoPath := strings.TrimSpace(string(out))
goPaths = filepath.SplitList(toolchainGoPath)
if len(goPaths) == 0 {
er("$GOPATH is not set")
}
}
srcPaths = make([]string, 0, len(goPaths))
for _, goPath := range goPaths {
srcPaths = append(srcPaths, filepath.Join(goPath, "src"))
}
}
func er(msg interface{}) {
fmt.Println("Error:", msg)
os.Exit(1)
}
// isEmpty checks if a given path is empty.
// Hidden files in path are ignored.
func isEmpty(path string) bool {
fi, err := os.Stat(path)
if err != nil {
er(err)
}
if !fi.IsDir() {
return fi.Size() == 0
}
f, err := os.Open(path)
if err != nil {
er(err)
}
defer f.Close()
names, err := f.Readdirnames(-1)
if err != nil && err != io.EOF {
er(err)
}
for _, name := range names {
if len(name) > 0 && name[0] != '.' {
return false
}
}
return true
}
// exists checks if a file or directory exists.
func exists(path string) bool {
if path == "" {
return false
}
_, err := os.Stat(path)
if err == nil {
return true
}
if !os.IsNotExist(err) {
er(err)
}
return false
}
func executeTemplate(tmplStr string, data interface{}) (string, error) {
tmpl, err := template.New("").Funcs(template.FuncMap{"comment": commentifyString}).Parse(tmplStr)
if err != nil {
return "", err
}
buf := new(bytes.Buffer)
err = tmpl.Execute(buf, data)
return buf.String(), err
}
func writeStringToFile(path string, s string) error {
return writeToFile(path, strings.NewReader(s))
}
// writeToFile writes r to file with path only
// if file/directory on given path doesn't exist.
func writeToFile(path string, r io.Reader) error {
if exists(path) {
return fmt.Errorf("%v already exists", path)
}
dir := filepath.Dir(path)
if dir != "" {
if err := os.MkdirAll(dir, 0777); err != nil {
return err
}
}
file, err := os.Create(path)
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(file, r)
return err
}
// commentfyString comments every line of in.
func commentifyString(in string) string {
var newlines []string
lines := strings.Split(in, "\n")
for _, line := range lines {
if strings.HasPrefix(line, "//") {
newlines = append(newlines, line)
} else {
if line == "" {
newlines = append(newlines, "//")
} else {
newlines = append(newlines, "// "+line)
}
}
}
return strings.Join(newlines, "\n")
}

View File

@@ -1,234 +0,0 @@
// Copyright © 2015 Steve Francia <spf@spf13.com>.
//
// 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 cmd
import (
"fmt"
"os"
"path"
"path/filepath"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var initCmd = &cobra.Command{
Use: "init [name]",
Aliases: []string{"initialize", "initialise", "create"},
Short: "Initialize a Cobra Application",
Long: `Initialize (cobra init) will create a new application, with a license
and the appropriate structure for a Cobra-based CLI application.
* If a name is provided, it will be created in the current directory;
* If no name is provided, the current directory will be assumed;
* If a relative path is provided, it will be created inside $GOPATH
(e.g. github.com/spf13/hugo);
* If an absolute path is provided, it will be created;
* If the directory already exists but is empty, it will be used.
Init will not use an existing directory with contents.`,
Run: func(cmd *cobra.Command, args []string) {
wd, err := os.Getwd()
if err != nil {
er(err)
}
var project *Project
if len(args) == 0 {
project = NewProjectFromPath(wd)
} else if len(args) == 1 {
arg := args[0]
if arg[0] == '.' {
arg = filepath.Join(wd, arg)
}
if filepath.IsAbs(arg) {
project = NewProjectFromPath(arg)
} else {
project = NewProject(arg)
}
} else {
er("please provide only one argument")
}
initializeProject(project)
fmt.Fprintln(cmd.OutOrStdout(), `Your Cobra application is ready at
`+project.AbsPath()+`.
Give it a try by going there and running `+"`go run main.go`."+`
Add commands to it by running `+"`cobra add [cmdname]`.")
},
}
func initializeProject(project *Project) {
if !exists(project.AbsPath()) { // If path doesn't yet exist, create it
err := os.MkdirAll(project.AbsPath(), os.ModePerm)
if err != nil {
er(err)
}
} else if !isEmpty(project.AbsPath()) { // If path exists and is not empty don't use it
er("Cobra will not create a new project in a non empty directory: " + project.AbsPath())
}
// We have a directory and it's empty. Time to initialize it.
createLicenseFile(project.License(), project.AbsPath())
createMainFile(project)
createRootCmdFile(project)
}
func createLicenseFile(license License, path string) {
data := make(map[string]interface{})
data["copyright"] = copyrightLine()
// Generate license template from text and data.
text, err := executeTemplate(license.Text, data)
if err != nil {
er(err)
}
// Write license text to LICENSE file.
err = writeStringToFile(filepath.Join(path, "LICENSE"), text)
if err != nil {
er(err)
}
}
func createMainFile(project *Project) {
mainTemplate := `{{ comment .copyright }}
{{if .license}}{{ comment .license }}{{end}}
package main
import "{{ .importpath }}"
func main() {
cmd.Execute()
}
`
data := make(map[string]interface{})
data["copyright"] = copyrightLine()
data["license"] = project.License().Header
data["importpath"] = path.Join(project.Name(), filepath.Base(project.CmdPath()))
mainScript, err := executeTemplate(mainTemplate, data)
if err != nil {
er(err)
}
err = writeStringToFile(filepath.Join(project.AbsPath(), "main.go"), mainScript)
if err != nil {
er(err)
}
}
func createRootCmdFile(project *Project) {
template := `{{comment .copyright}}
{{if .license}}{{comment .license}}{{end}}
package cmd
import (
"fmt"
"os"
{{if .viper}}
homedir "github.com/mitchellh/go-homedir"{{end}}
"github.com/spf13/cobra"{{if .viper}}
"github.com/spf13/viper"{{end}}
){{if .viper}}
var cfgFile string{{end}}
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "{{.appName}}",
Short: "A brief description of your application",
Long: ` + "`" + `A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.` + "`" + `,
// Uncomment the following line if your bare application
// has an action associated with it:
// Run: func(cmd *cobra.Command, args []string) { },
}
// 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.
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func init() { {{- if .viper}}
cobra.OnInitialize(initConfig)
{{end}}
// Here you will define your flags and configuration settings.
// Cobra supports persistent flags, which, if defined here,
// 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)"){{ end }}
// Cobra also supports local flags, which will only run
// when this action is called directly.
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}{{ if .viper }}
// initConfig reads in config file and ENV variables if set.
func initConfig() {
if cfgFile != "" {
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
// Find home directory.
home, err := homedir.Dir()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// Search config in home directory with name ".{{ .appName }}" (without extension).
viper.AddConfigPath(home)
viper.SetConfigName(".{{ .appName }}")
}
viper.AutomaticEnv() // read in environment variables that match
// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
fmt.Println("Using config file:", viper.ConfigFileUsed())
}
}{{ end }}
`
data := make(map[string]interface{})
data["copyright"] = copyrightLine()
data["viper"] = viper.GetBool("useViper")
data["license"] = project.License().Header
data["appName"] = path.Base(project.Name())
rootCmdScript, err := executeTemplate(template, data)
if err != nil {
er(err)
}
err = writeStringToFile(filepath.Join(project.CmdPath(), "root.go"), rootCmdScript)
if err != nil {
er(err)
}
}

View File

@@ -1,83 +0,0 @@
package cmd
import (
"errors"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/spf13/viper"
)
// TestGoldenInitCmd initializes the project "github.com/spf13/testproject"
// in GOPATH and compares the content of files in initialized project with
// appropriate golden files ("testdata/*.golden").
// Use -update to update existing golden files.
func TestGoldenInitCmd(t *testing.T) {
projectName := "github.com/spf13/testproject"
project := NewProject(projectName)
defer os.RemoveAll(project.AbsPath())
viper.Set("author", "NAME HERE <EMAIL ADDRESS>")
viper.Set("license", "apache")
viper.Set("year", 2017)
defer viper.Set("author", nil)
defer viper.Set("license", nil)
defer viper.Set("year", nil)
os.Args = []string{"cobra", "init", projectName}
if err := rootCmd.Execute(); err != nil {
t.Fatal("Error by execution:", err)
}
expectedFiles := []string{".", "cmd", "LICENSE", "main.go", "cmd/root.go"}
gotFiles := []string{}
// Check project file hierarchy and compare the content of every single file
// with appropriate golden file.
err := filepath.Walk(project.AbsPath(), func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Make path relative to project.AbsPath().
// E.g. path = "/home/user/go/src/github.com/spf13/testproject/cmd/root.go"
// then it returns just "cmd/root.go".
relPath, err := filepath.Rel(project.AbsPath(), path)
if err != nil {
return err
}
relPath = filepath.ToSlash(relPath)
gotFiles = append(gotFiles, relPath)
goldenPath := filepath.Join("testdata", filepath.Base(path)+".golden")
switch relPath {
// Known directories.
case ".", "cmd":
return nil
// Known files.
case "LICENSE", "main.go", "cmd/root.go":
if *update {
got, err := ioutil.ReadFile(path)
if err != nil {
return err
}
if err := ioutil.WriteFile(goldenPath, got, 0644); err != nil {
t.Fatal("Error while updating file:", err)
}
}
return compareFiles(path, goldenPath)
}
// Unknown file.
return errors.New("unknown file: " + path)
})
if err != nil {
t.Fatal(err)
}
// Check if some files lack.
if err := checkLackFiles(expectedFiles, gotFiles); err != nil {
t.Fatal(err)
}
}

View File

@@ -1,200 +0,0 @@
package cmd
import (
"os"
"path/filepath"
"runtime"
"strings"
)
// Project contains name, license and paths to projects.
type Project struct {
absPath string
cmdPath string
srcPath string
license License
name string
}
// NewProject returns Project with specified project name.
func NewProject(projectName string) *Project {
if projectName == "" {
er("can't create project with blank name")
}
p := new(Project)
p.name = projectName
// 1. Find already created protect.
p.absPath = findPackage(projectName)
// 2. If there are no created project with this path, and user is in GOPATH,
// then use GOPATH/src/projectName.
if p.absPath == "" {
wd, err := os.Getwd()
if err != nil {
er(err)
}
for _, srcPath := range srcPaths {
goPath := filepath.Dir(srcPath)
if filepathHasPrefix(wd, goPath) {
p.absPath = filepath.Join(srcPath, projectName)
break
}
}
}
// 3. If user is not in GOPATH, then use (first GOPATH)/src/projectName.
if p.absPath == "" {
p.absPath = filepath.Join(srcPaths[0], projectName)
}
return p
}
// findPackage returns full path to existing go package in GOPATHs.
func findPackage(packageName string) string {
if packageName == "" {
return ""
}
for _, srcPath := range srcPaths {
packagePath := filepath.Join(srcPath, packageName)
if exists(packagePath) {
return packagePath
}
}
return ""
}
// NewProjectFromPath returns Project with specified absolute path to
// package.
func NewProjectFromPath(absPath string) *Project {
if absPath == "" {
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.absPath = strings.TrimSuffix(absPath, findCmdDir(absPath))
p.name = filepath.ToSlash(trimSrcPath(p.absPath, p.SrcPath()))
return p
}
// trimSrcPath trims at the beginning of absPath the srcPath.
func trimSrcPath(absPath, srcPath string) string {
relPath, err := filepath.Rel(srcPath, absPath)
if err != nil {
er(err)
}
return relPath
}
// License returns the License object of project.
func (p *Project) License() License {
if p.license.Text == "" && p.license.Name != "None" {
p.license = getLicense()
}
return p.license
}
// Name returns the name of project, e.g. "github.com/spf13/cobra"
func (p Project) Name() string {
return p.name
}
// CmdPath returns absolute path to directory, where all commands are located.
func (p *Project) CmdPath() string {
if p.absPath == "" {
return ""
}
if p.cmdPath == "" {
p.cmdPath = filepath.Join(p.absPath, findCmdDir(p.absPath))
}
return p.cmdPath
}
// findCmdDir checks if base of absPath is cmd dir and returns it or
// looks for existing cmd dir in absPath.
func findCmdDir(absPath string) string {
if !exists(absPath) || isEmpty(absPath) {
return "cmd"
}
if isCmdDir(absPath) {
return filepath.Base(absPath)
}
files, _ := filepath.Glob(filepath.Join(absPath, "c*"))
for _, file := range files {
if isCmdDir(file) {
return filepath.Base(file)
}
}
return "cmd"
}
// isCmdDir checks if base of name is one of cmdDir.
func isCmdDir(name string) bool {
name = filepath.Base(name)
for _, cmdDir := range []string{"cmd", "cmds", "command", "commands"} {
if name == cmdDir {
return true
}
}
return false
}
// AbsPath returns absolute path of project.
func (p Project) AbsPath() string {
return p.absPath
}
// SrcPath returns absolute path to $GOPATH/src where project is located.
func (p *Project) SrcPath() string {
if p.srcPath != "" {
return p.srcPath
}
if p.absPath == "" {
p.srcPath = srcPaths[0]
return p.srcPath
}
for _, srcPath := range srcPaths {
if filepathHasPrefix(p.absPath, srcPath) {
p.srcPath = srcPath
break
}
}
return p.srcPath
}
func filepathHasPrefix(path string, prefix string) bool {
if len(path) <= len(prefix) {
return false
}
if runtime.GOOS == "windows" {
// Paths in windows are case-insensitive.
return strings.EqualFold(path[0:len(prefix)], prefix)
}
return path[0:len(prefix)] == prefix
}

View File

@@ -1,24 +0,0 @@
package cmd
import (
"testing"
)
func TestFindExistingPackage(t *testing.T) {
path := findPackage("github.com/spf13/cobra")
if path == "" {
t.Fatal("findPackage didn't find the existing package")
}
if !hasGoPathPrefix(path) {
t.Fatalf("%q is not in GOPATH, but must be", path)
}
}
func hasGoPathPrefix(path string) bool {
for _, srcPath := range srcPaths {
if filepathHasPrefix(path, srcPath) {
return true
}
}
return false
}

View File

@@ -1,79 +0,0 @@
// Copyright © 2015 Steve Francia <spf@spf13.com>.
//
// 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 cmd
import (
"fmt"
homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var (
// Used for flags.
cfgFile, userLicense string
rootCmd = &cobra.Command{
Use: "cobra",
Short: "A generator for Cobra based Applications",
Long: `Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
}
)
// Execute executes the root command.
func Execute() {
rootCmd.Execute()
}
func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)")
rootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "author name for copyright attribution")
rootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "name of license for the project")
rootCmd.PersistentFlags().Bool("viper", true, "use Viper for configuration")
viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author"))
viper.BindPFlag("useViper", rootCmd.PersistentFlags().Lookup("viper"))
viper.SetDefault("author", "NAME HERE <EMAIL ADDRESS>")
viper.SetDefault("license", "apache")
rootCmd.AddCommand(addCmd)
rootCmd.AddCommand(initCmd)
}
func initConfig() {
if cfgFile != "" {
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
// Find home directory.
home, err := homedir.Dir()
if err != nil {
er(err)
}
// Search config in home directory with name ".cobra" (without extension).
viper.AddConfigPath(home)
viper.SetConfigName(".cobra")
}
viper.AutomaticEnv()
if err := viper.ReadInConfig(); err == nil {
fmt.Println("Using config file:", viper.ConfigFileUsed())
}
}

View File

@@ -1,21 +0,0 @@
// Copyright © 2017 NAME HERE <EMAIL ADDRESS>
//
// 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 main
import "github.com/spf13/testproject/cmd"
func main() {
cmd.Execute()
}

View File

@@ -1,89 +0,0 @@
// Copyright © 2017 NAME HERE <EMAIL ADDRESS>
//
// 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 cmd
import (
"fmt"
"os"
homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var cfgFile string
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "testproject",
Short: "A brief description of your application",
Long: `A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
// Uncomment the following line if your bare application
// has an action associated with it:
// Run: func(cmd *cobra.Command, args []string) { },
}
// 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.
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func init() {
cobra.OnInitialize(initConfig)
// Here you will define your flags and configuration settings.
// Cobra supports persistent flags, which, if defined here,
// will be global for your application.
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.testproject.yaml)")
// Cobra also supports local flags, which will only run
// when this action is called directly.
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
// initConfig reads in config file and ENV variables if set.
func initConfig() {
if cfgFile != "" {
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
// Find home directory.
home, err := homedir.Dir()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// Search config in home directory with name ".testproject" (without extension).
viper.AddConfigPath(home)
viper.SetConfigName(".testproject")
}
viper.AutomaticEnv() // read in environment variables that match
// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
fmt.Println("Using config file:", viper.ConfigFileUsed())
}
}

View File

@@ -1,50 +0,0 @@
// Copyright © 2017 NAME HERE <EMAIL ADDRESS>
//
// 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 cmd
import (
"fmt"
"github.com/spf13/cobra"
)
// testCmd represents the test command
var testCmd = &cobra.Command{
Use: "test",
Short: "A brief description of your command",
Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("test called")
},
}
func init() {
rootCmd.AddCommand(testCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// testCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// testCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

View File

@@ -1,20 +0,0 @@
// Copyright © 2015 Steve Francia <spf@spf13.com>.
//
// 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 main
import "github.com/spf13/cobra/cobra/cmd"
func main() {
cmd.Execute()
}

View File

@@ -1,22 +0,0 @@
package cobra
import (
"testing"
"text/template"
)
func TestAddTemplateFunctions(t *testing.T) {
AddTemplateFunc("t", func() bool { return true })
AddTemplateFuncs(template.FuncMap{
"f": func() bool { return false },
"h": func() string { return "Hello," },
"w": func() string { return "world." }})
c := &Command{}
c.SetUsageTemplate(`{{if t}}{{h}}{{end}}{{if f}}{{h}}{{end}} {{w}}`)
const expected = "Hello, world."
if got := c.UsageString(); got != expected {
t.Errorf("Expected UsageString: %v\nGot: %v", expected, got)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,86 +0,0 @@
package doc
import (
"strings"
"testing"
"github.com/spf13/cobra"
)
func emptyRun(*cobra.Command, []string) {}
func init() {
rootCmd.PersistentFlags().StringP("rootflag", "r", "two", "")
rootCmd.PersistentFlags().StringP("strtwo", "t", "two", "help message for parent flag strtwo")
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]",
Aliases: []string{"say"},
Short: "Echo anything to the screen",
Long: "an utterly useless command for testing",
Example: "Just run cobra-test echo",
}
var echoSubCmd = &cobra.Command{
Use: "echosub [string to print]",
Short: "second sub command for echo",
Long: "an absolutely utterly useless command for testing gendocs!.",
Run: emptyRun,
}
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]",
Short: "A command which is deprecated",
Long: `an absolutely utterly useless command for testing deprecation!.`,
Deprecated: "Please use echo instead",
}
var printCmd = &cobra.Command{
Use: "print [string to print]",
Short: "Print anything to the screen",
Long: `an absolutely utterly useless command for testing.`,
}
func checkStringContains(t *testing.T, got, expected string) {
if !strings.Contains(got, expected) {
t.Errorf("Expected to contain: \n %v\nGot:\n %v\n", expected, got)
}
}
func checkStringOmits(t *testing.T, got, expected string) {
if strings.Contains(got, expected) {
t.Errorf("Expected to not contain: \n %v\nGot: %v", expected, got)
}
}

View File

@@ -1,236 +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 doc
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"sort"
"strings"
"time"
"github.com/cpuguy83/go-md2man/md2man"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
// GenManTree will generate a man page for this command and all descendants
// 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
// 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`.
func GenManTree(cmd *cobra.Command, header *GenManHeader, dir string) error {
return GenManTreeFromOpts(cmd, GenManTreeOptions{
Header: header,
Path: dir,
CommandSeparator: "-",
})
}
// GenManTreeFromOpts generates a man page for the command and all descendants.
// The pages are written to the opts.Path directory.
func GenManTreeFromOpts(cmd *cobra.Command, opts GenManTreeOptions) error {
header := opts.Header
if header == nil {
header = &GenManHeader{}
}
for _, c := range cmd.Commands() {
if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
continue
}
if err := GenManTreeFromOpts(c, opts); err != nil {
return err
}
}
section := "1"
if header.Section != "" {
section = header.Section
}
separator := "_"
if opts.CommandSeparator != "" {
separator = opts.CommandSeparator
}
basename := strings.Replace(cmd.CommandPath(), " ", separator, -1)
filename := filepath.Join(opts.Path, basename+"."+section)
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
headerCopy := *header
return GenMan(cmd, &headerCopy, f)
}
// GenManTreeOptions is the options for generating the man pages.
// Used only in GenManTreeFromOpts.
type GenManTreeOptions struct {
Header *GenManHeader
Path string
CommandSeparator string
}
// GenManHeader is a lot like the .TH header at the start of man pages. These
// include the title, section, date, source, and manual. We will use the
// current time if Date if unset and will use "Auto generated by spf13/cobra"
// if the Source is unset.
type GenManHeader struct {
Title string
Section string
Date *time.Time
date string
Source string
Manual string
}
// GenMan will generate a man page for the given command and write it to
// w. The header argument may be nil, however obviously w may not.
func GenMan(cmd *cobra.Command, header *GenManHeader, w io.Writer) error {
if header == nil {
header = &GenManHeader{}
}
fillHeader(header, cmd.CommandPath())
b := genMan(cmd, header)
_, err := w.Write(md2man.Render(b))
return err
}
func fillHeader(header *GenManHeader, name string) {
if header.Title == "" {
header.Title = strings.ToUpper(strings.Replace(name, " ", "\\-", -1))
}
if header.Section == "" {
header.Section = "1"
}
if header.Date == nil {
now := time.Now()
header.Date = &now
}
header.date = (*header.Date).Format("Jan 2006")
if header.Source == "" {
header.Source = "Auto generated by spf13/cobra"
}
}
func manPreamble(buf *bytes.Buffer, header *GenManHeader, cmd *cobra.Command, dashedName string) {
description := cmd.Long
if len(description) == 0 {
description = cmd.Short
}
buf.WriteString(fmt.Sprintf(`%% %s(%s)%s
%% %s
%% %s
# NAME
`, header.Title, header.Section, header.date, header.Source, header.Manual))
buf.WriteString(fmt.Sprintf("%s \\- %s\n\n", dashedName, cmd.Short))
buf.WriteString("# SYNOPSIS\n")
buf.WriteString(fmt.Sprintf("**%s**\n\n", cmd.UseLine()))
buf.WriteString("# DESCRIPTION\n")
buf.WriteString(description + "\n\n")
}
func manPrintFlags(buf *bytes.Buffer, flags *pflag.FlagSet) {
flags.VisitAll(func(flag *pflag.Flag) {
if len(flag.Deprecated) > 0 || flag.Hidden {
return
}
format := ""
if len(flag.Shorthand) > 0 && len(flag.ShorthandDeprecated) == 0 {
format = fmt.Sprintf("**-%s**, **--%s**", flag.Shorthand, flag.Name)
} else {
format = fmt.Sprintf("**--%s**", flag.Name)
}
if len(flag.NoOptDefVal) > 0 {
format += "["
}
if flag.Value.Type() == "string" {
// put quotes on the value
format += "=%q"
} else {
format += "=%s"
}
if len(flag.NoOptDefVal) > 0 {
format += "]"
}
format += "\n\t%s\n\n"
buf.WriteString(fmt.Sprintf(format, flag.DefValue, flag.Usage))
})
}
func manPrintOptions(buf *bytes.Buffer, command *cobra.Command) {
flags := command.NonInheritedFlags()
if flags.HasFlags() {
buf.WriteString("# OPTIONS\n")
manPrintFlags(buf, flags)
buf.WriteString("\n")
}
flags = command.InheritedFlags()
if flags.HasFlags() {
buf.WriteString("# OPTIONS INHERITED FROM PARENT COMMANDS\n")
manPrintFlags(buf, flags)
buf.WriteString("\n")
}
}
func genMan(cmd *cobra.Command, header *GenManHeader) []byte {
cmd.InitDefaultHelpCmd()
cmd.InitDefaultHelpFlag()
// something like `rootcmd-subcmd1-subcmd2`
dashCommandName := strings.Replace(cmd.CommandPath(), " ", "-", -1)
buf := new(bytes.Buffer)
manPreamble(buf, header, cmd, dashCommandName)
manPrintOptions(buf, cmd)
if len(cmd.Example) > 0 {
buf.WriteString("# EXAMPLE\n")
buf.WriteString(fmt.Sprintf("```\n%s\n```\n", cmd.Example))
}
if hasSeeAlso(cmd) {
buf.WriteString("# SEE ALSO\n")
seealsos := make([]string, 0)
if cmd.HasParent() {
parentPath := cmd.Parent().CommandPath()
dashParentPath := strings.Replace(parentPath, " ", "-", -1)
seealso := fmt.Sprintf("**%s(%s)**", dashParentPath, header.Section)
seealsos = append(seealsos, seealso)
cmd.VisitParents(func(c *cobra.Command) {
if c.DisableAutoGenTag {
cmd.DisableAutoGenTag = c.DisableAutoGenTag
}
})
}
children := cmd.Commands()
sort.Sort(byName(children))
for _, c := range children {
if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
continue
}
seealso := fmt.Sprintf("**%s-%s(%s)**", dashCommandName, c.Name(), header.Section)
seealsos = append(seealsos, seealso)
}
buf.WriteString(strings.Join(seealsos, ", ") + "\n")
}
if !cmd.DisableAutoGenTag {
buf.WriteString(fmt.Sprintf("# HISTORY\n%s Auto generated by spf13/cobra\n", header.Date.Format("2-Jan-2006")))
}
return buf.Bytes()
}

View File

@@ -1,31 +0,0 @@
# Generating Man Pages For Your Own cobra.Command
Generating man pages from a cobra command is incredibly easy. An example is as follows:
```go
package main
import (
"log"
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
)
func main() {
cmd := &cobra.Command{
Use: "test",
Short: "my test program",
}
header := &doc.GenManHeader{
Title: "MINE",
Section: "3",
}
err := doc.GenManTree(cmd, header, "/tmp")
if err != nil {
log.Fatal(err)
}
}
```
That will get you a man page `/tmp/test.3`

View File

@@ -1,177 +0,0 @@
package doc
import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"github.com/spf13/cobra"
)
func translate(in string) string {
return strings.Replace(in, "-", "\\-", -1)
}
func TestGenManDoc(t *testing.T) {
header := &GenManHeader{
Title: "Project",
Section: "2",
}
// We generate on a subcommand so we have both subcommands and parents
buf := new(bytes.Buffer)
if err := GenMan(echoCmd, header, buf); err != nil {
t.Fatal(err)
}
output := buf.String()
// Make sure parent has - in CommandPath() in SEE ALSO:
parentPath := echoCmd.Parent().CommandPath()
dashParentPath := strings.Replace(parentPath, " ", "-", -1)
expected := translate(dashParentPath)
expected = expected + "(" + header.Section + ")"
checkStringContains(t, output, expected)
checkStringContains(t, output, translate(echoCmd.Name()))
checkStringContains(t, output, translate(echoCmd.Name()))
checkStringContains(t, output, "boolone")
checkStringContains(t, output, "rootflag")
checkStringContains(t, output, translate(rootCmd.Name()))
checkStringContains(t, output, translate(echoSubCmd.Name()))
checkStringOmits(t, output, translate(deprecatedCmd.Name()))
checkStringContains(t, output, translate("Auto generated"))
}
func TestGenManNoGenTag(t *testing.T) {
echoCmd.DisableAutoGenTag = true
defer func() { echoCmd.DisableAutoGenTag = false }()
header := &GenManHeader{
Title: "Project",
Section: "2",
}
// We generate on a subcommand so we have both subcommands and parents
buf := new(bytes.Buffer)
if err := GenMan(echoCmd, header, buf); err != nil {
t.Fatal(err)
}
output := buf.String()
unexpected := translate("#HISTORY")
checkStringOmits(t, output, unexpected)
}
func TestGenManSeeAlso(t *testing.T) {
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)
buf := new(bytes.Buffer)
header := &GenManHeader{}
if err := GenMan(rootCmd, header, buf); err != nil {
t.Fatal(err)
}
scanner := bufio.NewScanner(buf)
if err := assertLineFound(scanner, ".SH SEE ALSO"); err != nil {
t.Fatalf("Couldn't find SEE ALSO section header: %v", err)
}
if err := assertNextLineEquals(scanner, ".PP"); err != nil {
t.Fatalf("First line after SEE ALSO wasn't break-indent: %v", err)
}
if err := assertNextLineEquals(scanner, `\fBroot\-bbb(1)\fP, \fBroot\-ccc(1)\fP`); err != nil {
t.Fatalf("Second line after SEE ALSO wasn't correct: %v", err)
}
}
func TestManPrintFlagsHidesShortDeperecated(t *testing.T) {
c := &cobra.Command{}
c.Flags().StringP("foo", "f", "default", "Foo flag")
c.Flags().MarkShorthandDeprecated("foo", "don't use it no more")
buf := new(bytes.Buffer)
manPrintFlags(buf, c.Flags())
got := buf.String()
expected := "**--foo**=\"default\"\n\tFoo flag\n\n"
if got != expected {
t.Errorf("Expected %v, got %v", expected, got)
}
}
func TestGenManTree(t *testing.T) {
c := &cobra.Command{Use: "do [OPTIONS] arg1 arg2"}
header := &GenManHeader{Section: "2"}
tmpdir, err := ioutil.TempDir("", "test-gen-man-tree")
if err != nil {
t.Fatalf("Failed to create tmpdir: %s", err.Error())
}
defer os.RemoveAll(tmpdir)
if err := GenManTree(c, header, tmpdir); err != nil {
t.Fatalf("GenManTree failed: %s", err.Error())
}
if _, err := os.Stat(filepath.Join(tmpdir, "do.2")); err != nil {
t.Fatalf("Expected file 'do.2' to exist")
}
if header.Title != "" {
t.Fatalf("Expected header.Title to be unmodified")
}
}
func assertLineFound(scanner *bufio.Scanner, expectedLine string) error {
for scanner.Scan() {
line := scanner.Text()
if line == expectedLine {
return nil
}
}
if err := scanner.Err(); err != nil {
return fmt.Errorf("scan failed: %s", err)
}
return fmt.Errorf("hit EOF before finding %v", expectedLine)
}
func assertNextLineEquals(scanner *bufio.Scanner, expectedLine string) error {
if scanner.Scan() {
line := scanner.Text()
if line == expectedLine {
return nil
}
return fmt.Errorf("got %v, not %v", line, expectedLine)
}
if err := scanner.Err(); err != nil {
return fmt.Errorf("scan failed: %v", err)
}
return fmt.Errorf("hit EOF before finding %v", expectedLine)
}
func BenchmarkGenManToFile(b *testing.B) {
file, err := ioutil.TempFile("", "")
if err != nil {
b.Fatal(err)
}
defer os.Remove(file.Name())
defer file.Close()
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := GenMan(rootCmd, nil, file); err != nil {
b.Fatal(err)
}
}
}

View File

@@ -1,35 +0,0 @@
package doc_test
import (
"bytes"
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
)
func ExampleGenManTree() {
cmd := &cobra.Command{
Use: "test",
Short: "my test program",
}
header := &doc.GenManHeader{
Title: "MINE",
Section: "3",
}
doc.GenManTree(cmd, header, "/tmp")
}
func ExampleGenMan() {
cmd := &cobra.Command{
Use: "test",
Short: "my test program",
}
header := &doc.GenManHeader{
Title: "MINE",
Section: "3",
}
out := new(bytes.Buffer)
doc.GenMan(cmd, header, out)
fmt.Print(out.String())
}

View File

@@ -1,159 +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 doc
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"sort"
"strings"
"time"
"github.com/spf13/cobra"
)
func printOptions(buf *bytes.Buffer, cmd *cobra.Command, name string) error {
flags := cmd.NonInheritedFlags()
flags.SetOutput(buf)
if flags.HasFlags() {
buf.WriteString("### Options\n\n```\n")
flags.PrintDefaults()
buf.WriteString("```\n\n")
}
parentFlags := cmd.InheritedFlags()
parentFlags.SetOutput(buf)
if parentFlags.HasFlags() {
buf.WriteString("### Options inherited from parent commands\n\n```\n")
parentFlags.PrintDefaults()
buf.WriteString("```\n\n")
}
return nil
}
// GenMarkdown creates markdown output.
func GenMarkdown(cmd *cobra.Command, w io.Writer) error {
return GenMarkdownCustom(cmd, w, func(s string) string { return s })
}
// GenMarkdownCustom creates custom markdown output.
func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string) string) error {
cmd.InitDefaultHelpCmd()
cmd.InitDefaultHelpFlag()
buf := new(bytes.Buffer)
name := cmd.CommandPath()
short := cmd.Short
long := cmd.Long
if len(long) == 0 {
long = short
}
buf.WriteString("## " + name + "\n\n")
buf.WriteString(short + "\n\n")
buf.WriteString("### Synopsis\n\n")
buf.WriteString(long + "\n\n")
if cmd.Runnable() {
buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.UseLine()))
}
if len(cmd.Example) > 0 {
buf.WriteString("### Examples\n\n")
buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.Example))
}
if err := printOptions(buf, cmd, name); err != nil {
return err
}
if hasSeeAlso(cmd) {
buf.WriteString("### SEE ALSO\n\n")
if cmd.HasParent() {
parent := cmd.Parent()
pname := parent.CommandPath()
link := pname + ".md"
link = strings.Replace(link, " ", "_", -1)
buf.WriteString(fmt.Sprintf("* [%s](%s)\t - %s\n", pname, linkHandler(link), parent.Short))
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.IsAdditionalHelpTopicCommand() {
continue
}
cname := name + " " + child.Name()
link := cname + ".md"
link = strings.Replace(link, " ", "_", -1)
buf.WriteString(fmt.Sprintf("* [%s](%s)\t - %s\n", cname, linkHandler(link), child.Short))
}
buf.WriteString("\n")
}
if !cmd.DisableAutoGenTag {
buf.WriteString("###### Auto generated by spf13/cobra on " + time.Now().Format("2-Jan-2006") + "\n")
}
_, err := buf.WriteTo(w)
return err
}
// GenMarkdownTree will generate a markdown page for this command and all
// descendants 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 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`.
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)
}
// GenMarkdownTreeCustom is the the same as GenMarkdownTree, but
// with custom filePrepender and linkHandler.
func GenMarkdownTreeCustom(cmd *cobra.Command, dir string, filePrepender, linkHandler func(string) string) error {
for _, c := range cmd.Commands() {
if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
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,115 +0,0 @@
# 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 (
"log"
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
)
func main() {
cmd := &cobra.Command{
Use: "test",
Short: "my test program",
}
err := doc.GenMarkdownTree(cmd, "/tmp")
if err != nil {
log.Fatal(err)
}
}
```
That will get you a Markdown document `/tmp/test.md`
## Generate markdown docs for the entire command tree
This program can actually generate docs for the kubectl command in the kubernetes project
```go
package main
import (
"log"
"io/ioutil"
"os"
"k8s.io/kubernetes/pkg/kubectl/cmd"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"github.com/spf13/cobra/doc"
)
func main() {
kubectl := cmd.NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, ioutil.Discard, ioutil.Discard)
err := doc.GenMarkdownTree(kubectl, "./")
if err != nil {
log.Fatal(err)
}
}
```
This will generate a whole series of files, one for each command in the tree, in the directory specified (in this case "./")
## Generate markdown docs for a single command
You may wish to have more control over the output, or only generate for a single command, instead of the entire command tree. If this is the case you may prefer to `GenMarkdown` instead of `GenMarkdownTree`
```go
out := new(bytes.Buffer)
err := doc.GenMarkdown(cmd, out)
if err != nil {
log.Fatal(err)
}
```
This will write the markdown doc for ONLY "cmd" into the out, buffer.
## Customize the output
Both `GenMarkdown` and `GenMarkdownTree` have alternate versions with callbacks to get some control of the output:
```go
func GenMarkdownTreeCustom(cmd *Command, dir string, filePrepender, linkHandler func(string) string) error {
//...
}
```
```go
func GenMarkdownCustom(cmd *Command, out *bytes.Buffer, linkHandler func(string) string) error {
//...
}
```
The `filePrepender` will prepend the return value given the full filepath to the rendered Markdown file. A common use case is to add front matter to use the generated documentation with [Hugo](http://gohugo.io/):
```go
const fmTemplate = `---
date: %s
title: "%s"
slug: %s
url: %s
---
`
filePrepender := func(filename string) string {
now := time.Now().Format(time.RFC3339)
name := filepath.Base(filename)
base := strings.TrimSuffix(name, path.Ext(name))
url := "/commands/" + strings.ToLower(base) + "/"
return fmt.Sprintf(fmTemplate, now, strings.Replace(base, "_", " ", -1), base, url)
}
```
The `linkHandler` can be used to customize the rendered internal links to the commands, given a filename:
```go
linkHandler := func(name string) string {
base := strings.TrimSuffix(name, path.Ext(name))
return "/commands/" + strings.ToLower(base) + "/"
}
```

View File

@@ -1,74 +0,0 @@
package doc
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/spf13/cobra"
)
func TestGenMdDoc(t *testing.T) {
// We generate on subcommand so we have both subcommands and parents.
buf := new(bytes.Buffer)
if err := GenMarkdown(echoCmd, buf); err != nil {
t.Fatal(err)
}
output := buf.String()
checkStringContains(t, output, echoCmd.Long)
checkStringContains(t, output, echoCmd.Example)
checkStringContains(t, output, "boolone")
checkStringContains(t, output, "rootflag")
checkStringContains(t, output, rootCmd.Short)
checkStringContains(t, output, echoSubCmd.Short)
checkStringOmits(t, output, deprecatedCmd.Short)
}
func TestGenMdNoTag(t *testing.T) {
rootCmd.DisableAutoGenTag = true
defer func() { rootCmd.DisableAutoGenTag = false }()
buf := new(bytes.Buffer)
if err := GenMarkdown(rootCmd, buf); err != nil {
t.Fatal(err)
}
output := buf.String()
checkStringOmits(t, output, "Auto generated")
}
func TestGenMdTree(t *testing.T) {
c := &cobra.Command{Use: "do [OPTIONS] arg1 arg2"}
tmpdir, err := ioutil.TempDir("", "test-gen-md-tree")
if err != nil {
t.Fatalf("Failed to create tmpdir: %v", err)
}
defer os.RemoveAll(tmpdir)
if err := GenMarkdownTree(c, tmpdir); err != nil {
t.Fatalf("GenMarkdownTree failed: %v", err)
}
if _, err := os.Stat(filepath.Join(tmpdir, "do.md")); err != nil {
t.Fatalf("Expected file 'do.md' to exist")
}
}
func BenchmarkGenMarkdownToFile(b *testing.B) {
file, err := ioutil.TempFile("", "")
if err != nil {
b.Fatal(err)
}
defer os.Remove(file.Name())
defer file.Close()
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := GenMarkdown(rootCmd, file); err != nil {
b.Fatal(err)
}
}
}

View File

@@ -1,185 +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 doc
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"sort"
"strings"
"time"
"github.com/spf13/cobra"
)
func printOptionsReST(buf *bytes.Buffer, cmd *cobra.Command, name string) error {
flags := cmd.NonInheritedFlags()
flags.SetOutput(buf)
if flags.HasFlags() {
buf.WriteString("Options\n")
buf.WriteString("~~~~~~~\n\n::\n\n")
flags.PrintDefaults()
buf.WriteString("\n")
}
parentFlags := cmd.InheritedFlags()
parentFlags.SetOutput(buf)
if parentFlags.HasFlags() {
buf.WriteString("Options inherited from parent commands\n")
buf.WriteString("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n::\n\n")
parentFlags.PrintDefaults()
buf.WriteString("\n")
}
return nil
}
// linkHandler for default ReST hyperlink markup
func defaultLinkHandler(name, ref string) string {
return fmt.Sprintf("`%s <%s.rst>`_", name, ref)
}
// GenReST creates reStructured Text output.
func GenReST(cmd *cobra.Command, w io.Writer) error {
return GenReSTCustom(cmd, w, defaultLinkHandler)
}
// GenReSTCustom creates custom reStructured Text output.
func GenReSTCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string, string) string) error {
cmd.InitDefaultHelpCmd()
cmd.InitDefaultHelpFlag()
buf := new(bytes.Buffer)
name := cmd.CommandPath()
short := cmd.Short
long := cmd.Long
if len(long) == 0 {
long = short
}
ref := strings.Replace(name, " ", "_", -1)
buf.WriteString(".. _" + ref + ":\n\n")
buf.WriteString(name + "\n")
buf.WriteString(strings.Repeat("-", len(name)) + "\n\n")
buf.WriteString(short + "\n\n")
buf.WriteString("Synopsis\n")
buf.WriteString("~~~~~~~~\n\n")
buf.WriteString("\n" + long + "\n\n")
if cmd.Runnable() {
buf.WriteString(fmt.Sprintf("::\n\n %s\n\n", cmd.UseLine()))
}
if len(cmd.Example) > 0 {
buf.WriteString("Examples\n")
buf.WriteString("~~~~~~~~\n\n")
buf.WriteString(fmt.Sprintf("::\n\n%s\n\n", indentString(cmd.Example, " ")))
}
if err := printOptionsReST(buf, cmd, name); err != nil {
return err
}
if hasSeeAlso(cmd) {
buf.WriteString("SEE ALSO\n")
buf.WriteString("~~~~~~~~\n\n")
if cmd.HasParent() {
parent := cmd.Parent()
pname := parent.CommandPath()
ref = strings.Replace(pname, " ", "_", -1)
buf.WriteString(fmt.Sprintf("* %s \t - %s\n", linkHandler(pname, ref), parent.Short))
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.IsAdditionalHelpTopicCommand() {
continue
}
cname := name + " " + child.Name()
ref = strings.Replace(cname, " ", "_", -1)
buf.WriteString(fmt.Sprintf("* %s \t - %s\n", linkHandler(cname, ref), child.Short))
}
buf.WriteString("\n")
}
if !cmd.DisableAutoGenTag {
buf.WriteString("*Auto generated by spf13/cobra on " + time.Now().Format("2-Jan-2006") + "*\n")
}
_, err := buf.WriteTo(w)
return err
}
// GenReSTTree will generate a ReST page for this command and all
// descendants in the directory given.
// This function may not work 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`, it is undefined which
// help output will be in the file `cmd-sub-third.1`.
func GenReSTTree(cmd *cobra.Command, dir string) error {
emptyStr := func(s string) string { return "" }
return GenReSTTreeCustom(cmd, dir, emptyStr, defaultLinkHandler)
}
// GenReSTTreeCustom is the the same as GenReSTTree, but
// with custom filePrepender and linkHandler.
func GenReSTTreeCustom(cmd *cobra.Command, dir string, filePrepender func(string) string, linkHandler func(string, string) string) error {
for _, c := range cmd.Commands() {
if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
continue
}
if err := GenReSTTreeCustom(c, dir, filePrepender, linkHandler); err != nil {
return err
}
}
basename := strings.Replace(cmd.CommandPath(), " ", "_", -1) + ".rst"
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 := GenReSTCustom(cmd, f, linkHandler); err != nil {
return err
}
return nil
}
// adapted from: https://github.com/kr/text/blob/main/indent.go
func indentString(s, p string) string {
var res []byte
b := []byte(s)
prefix := []byte(p)
bol := true
for _, c := range b {
if bol && c != '\n' {
res = append(res, prefix...)
}
res = append(res, c)
bol = c == '\n'
}
return string(res)
}

View File

@@ -1,114 +0,0 @@
# Generating ReStructured Text Docs For Your Own cobra.Command
Generating ReST pages from a cobra command is incredibly easy. An example is as follows:
```go
package main
import (
"log"
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
)
func main() {
cmd := &cobra.Command{
Use: "test",
Short: "my test program",
}
err := doc.GenReSTTree(cmd, "/tmp")
if err != nil {
log.Fatal(err)
}
}
```
That will get you a ReST document `/tmp/test.rst`
## Generate ReST docs for the entire command tree
This program can actually generate docs for the kubectl command in the kubernetes project
```go
package main
import (
"log"
"io/ioutil"
"os"
"k8s.io/kubernetes/pkg/kubectl/cmd"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"github.com/spf13/cobra/doc"
)
func main() {
kubectl := cmd.NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, ioutil.Discard, ioutil.Discard)
err := doc.GenReSTTree(kubectl, "./")
if err != nil {
log.Fatal(err)
}
}
```
This will generate a whole series of files, one for each command in the tree, in the directory specified (in this case "./")
## Generate ReST docs for a single command
You may wish to have more control over the output, or only generate for a single command, instead of the entire command tree. If this is the case you may prefer to `GenReST` instead of `GenReSTTree`
```go
out := new(bytes.Buffer)
err := doc.GenReST(cmd, out)
if err != nil {
log.Fatal(err)
}
```
This will write the ReST doc for ONLY "cmd" into the out, buffer.
## Customize the output
Both `GenReST` and `GenReSTTree` have alternate versions with callbacks to get some control of the output:
```go
func GenReSTTreeCustom(cmd *Command, dir string, filePrepender func(string) string, linkHandler func(string, string) string) error {
//...
}
```
```go
func GenReSTCustom(cmd *Command, out *bytes.Buffer, linkHandler func(string, string) string) error {
//...
}
```
The `filePrepender` will prepend the return value given the full filepath to the rendered ReST file. A common use case is to add front matter to use the generated documentation with [Hugo](http://gohugo.io/):
```go
const fmTemplate = `---
date: %s
title: "%s"
slug: %s
url: %s
---
`
filePrepender := func(filename string) string {
now := time.Now().Format(time.RFC3339)
name := filepath.Base(filename)
base := strings.TrimSuffix(name, path.Ext(name))
url := "/commands/" + strings.ToLower(base) + "/"
return fmt.Sprintf(fmTemplate, now, strings.Replace(base, "_", " ", -1), base, url)
}
```
The `linkHandler` can be used to customize the rendered links to the commands, given a command name and reference. This is useful while converting rst to html or while generating documentation with tools like Sphinx where `:ref:` is used:
```go
// Sphinx cross-referencing format
linkHandler := func(name, ref string) string {
return fmt.Sprintf(":ref:`%s <%s>`", name, ref)
}
```

View File

@@ -1,76 +0,0 @@
package doc
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/spf13/cobra"
)
func TestGenRSTDoc(t *testing.T) {
// We generate on a subcommand so we have both subcommands and parents
buf := new(bytes.Buffer)
if err := GenReST(echoCmd, buf); err != nil {
t.Fatal(err)
}
output := buf.String()
checkStringContains(t, output, echoCmd.Long)
checkStringContains(t, output, echoCmd.Example)
checkStringContains(t, output, "boolone")
checkStringContains(t, output, "rootflag")
checkStringContains(t, output, rootCmd.Short)
checkStringContains(t, output, echoSubCmd.Short)
checkStringOmits(t, output, deprecatedCmd.Short)
}
func TestGenRSTNoTag(t *testing.T) {
rootCmd.DisableAutoGenTag = true
defer func() { rootCmd.DisableAutoGenTag = false }()
buf := new(bytes.Buffer)
if err := GenReST(rootCmd, buf); err != nil {
t.Fatal(err)
}
output := buf.String()
unexpected := "Auto generated"
checkStringOmits(t, output, unexpected)
}
func TestGenRSTTree(t *testing.T) {
c := &cobra.Command{Use: "do [OPTIONS] arg1 arg2"}
tmpdir, err := ioutil.TempDir("", "test-gen-rst-tree")
if err != nil {
t.Fatalf("Failed to create tmpdir: %s", err.Error())
}
defer os.RemoveAll(tmpdir)
if err := GenReSTTree(c, tmpdir); err != nil {
t.Fatalf("GenReSTTree failed: %s", err.Error())
}
if _, err := os.Stat(filepath.Join(tmpdir, "do.rst")); err != nil {
t.Fatalf("Expected file 'do.rst' to exist")
}
}
func BenchmarkGenReSTToFile(b *testing.B) {
file, err := ioutil.TempFile("", "")
if err != nil {
b.Fatal(err)
}
defer os.Remove(file.Name())
defer file.Close()
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := GenReST(rootCmd, file); err != nil {
b.Fatal(err)
}
}
}

View File

@@ -1,51 +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 doc
import (
"strings"
"github.com/spf13/cobra"
)
// 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
// both not deprecated and not the autogenerated help command.
func hasSeeAlso(cmd *cobra.Command) bool {
if cmd.HasParent() {
return true
}
for _, c := range cmd.Commands() {
if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
continue
}
return true
}
return false
}
// Temporary workaround for yaml lib generating incorrect yaml with long strings
// that do not contain \n.
func forceMultiLine(s string) string {
if len(s) > 60 && !strings.Contains(s, "\n") {
s = s + "\n"
}
return s
}
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,169 +0,0 @@
// Copyright 2016 French Ben. 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"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"gopkg.in/yaml.v2"
)
type cmdOption struct {
Name string
Shorthand string `yaml:",omitempty"`
DefaultValue string `yaml:"default_value,omitempty"`
Usage string `yaml:",omitempty"`
}
type cmdDoc struct {
Name string
Synopsis string `yaml:",omitempty"`
Description string `yaml:",omitempty"`
Options []cmdOption `yaml:",omitempty"`
InheritedOptions []cmdOption `yaml:"inherited_options,omitempty"`
Example string `yaml:",omitempty"`
SeeAlso []string `yaml:"see_also,omitempty"`
}
// GenYamlTree creates yaml structured ref files for this command and all descendants
// in the directory given. This function may not work
// 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`
// it is undefined which help output will be in the file `cmd-sub-third.1`.
func GenYamlTree(cmd *cobra.Command, dir string) error {
identity := func(s string) string { return s }
emptyStr := func(s string) string { return "" }
return GenYamlTreeCustom(cmd, dir, emptyStr, identity)
}
// GenYamlTreeCustom creates yaml structured ref files.
func GenYamlTreeCustom(cmd *cobra.Command, dir string, filePrepender, linkHandler func(string) string) error {
for _, c := range cmd.Commands() {
if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
continue
}
if err := GenYamlTreeCustom(c, dir, filePrepender, linkHandler); err != nil {
return err
}
}
basename := strings.Replace(cmd.CommandPath(), " ", "_", -1) + ".yaml"
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 := GenYamlCustom(cmd, f, linkHandler); err != nil {
return err
}
return nil
}
// GenYaml creates yaml output.
func GenYaml(cmd *cobra.Command, w io.Writer) error {
return GenYamlCustom(cmd, w, func(s string) string { return s })
}
// GenYamlCustom creates custom yaml output.
func GenYamlCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string) string) error {
cmd.InitDefaultHelpCmd()
cmd.InitDefaultHelpFlag()
yamlDoc := cmdDoc{}
yamlDoc.Name = cmd.CommandPath()
yamlDoc.Synopsis = forceMultiLine(cmd.Short)
yamlDoc.Description = forceMultiLine(cmd.Long)
if len(cmd.Example) > 0 {
yamlDoc.Example = cmd.Example
}
flags := cmd.NonInheritedFlags()
if flags.HasFlags() {
yamlDoc.Options = genFlagResult(flags)
}
flags = cmd.InheritedFlags()
if flags.HasFlags() {
yamlDoc.InheritedOptions = genFlagResult(flags)
}
if hasSeeAlso(cmd) {
result := []string{}
if cmd.HasParent() {
parent := cmd.Parent()
result = append(result, parent.CommandPath()+" - "+parent.Short)
}
children := cmd.Commands()
sort.Sort(byName(children))
for _, child := range children {
if !child.IsAvailableCommand() || child.IsAdditionalHelpTopicCommand() {
continue
}
result = append(result, child.Name()+" - "+child.Short)
}
yamlDoc.SeeAlso = result
}
final, err := yaml.Marshal(&yamlDoc)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if _, err := w.Write(final); err != nil {
return err
}
return nil
}
func genFlagResult(flags *pflag.FlagSet) []cmdOption {
var result []cmdOption
flags.VisitAll(func(flag *pflag.Flag) {
// Todo, when we mark a shorthand is deprecated, but specify an empty message.
// The flag.ShorthandDeprecated is empty as the shorthand is deprecated.
// Using len(flag.ShorthandDeprecated) > 0 can't handle this, others are ok.
if !(len(flag.ShorthandDeprecated) > 0) && len(flag.Shorthand) > 0 {
opt := cmdOption{
flag.Name,
flag.Shorthand,
flag.DefValue,
forceMultiLine(flag.Usage),
}
result = append(result, opt)
} else {
opt := cmdOption{
Name: flag.Name,
DefaultValue: forceMultiLine(flag.DefValue),
Usage: forceMultiLine(flag.Usage),
}
result = append(result, opt)
}
})
return result
}

View File

@@ -1,112 +0,0 @@
# Generating Yaml Docs For Your Own cobra.Command
Generating yaml files from a cobra command is incredibly easy. An example is as follows:
```go
package main
import (
"log"
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
)
func main() {
cmd := &cobra.Command{
Use: "test",
Short: "my test program",
}
err := doc.GenYamlTree(cmd, "/tmp")
if err != nil {
log.Fatal(err)
}
}
```
That will get you a Yaml document `/tmp/test.yaml`
## Generate yaml docs for the entire command tree
This program can actually generate docs for the kubectl command in the kubernetes project
```go
package main
import (
"io/ioutil"
"log"
"os"
"k8s.io/kubernetes/pkg/kubectl/cmd"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"github.com/spf13/cobra/doc"
)
func main() {
kubectl := cmd.NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, ioutil.Discard, ioutil.Discard)
err := doc.GenYamlTree(kubectl, "./")
if err != nil {
log.Fatal(err)
}
}
```
This will generate a whole series of files, one for each command in the tree, in the directory specified (in this case "./")
## Generate yaml docs for a single command
You may wish to have more control over the output, or only generate for a single command, instead of the entire command tree. If this is the case you may prefer to `GenYaml` instead of `GenYamlTree`
```go
out := new(bytes.Buffer)
doc.GenYaml(cmd, out)
```
This will write the yaml doc for ONLY "cmd" into the out, buffer.
## Customize the output
Both `GenYaml` and `GenYamlTree` have alternate versions with callbacks to get some control of the output:
```go
func GenYamlTreeCustom(cmd *Command, dir string, filePrepender, linkHandler func(string) string) error {
//...
}
```
```go
func GenYamlCustom(cmd *Command, out *bytes.Buffer, linkHandler func(string) string) error {
//...
}
```
The `filePrepender` will prepend the return value given the full filepath to the rendered Yaml file. A common use case is to add front matter to use the generated documentation with [Hugo](http://gohugo.io/):
```go
const fmTemplate = `---
date: %s
title: "%s"
slug: %s
url: %s
---
`
filePrepender := func(filename string) string {
now := time.Now().Format(time.RFC3339)
name := filepath.Base(filename)
base := strings.TrimSuffix(name, path.Ext(name))
url := "/commands/" + strings.ToLower(base) + "/"
return fmt.Sprintf(fmTemplate, now, strings.Replace(base, "_", " ", -1), base, url)
}
```
The `linkHandler` can be used to customize the rendered internal links to the commands, given a filename:
```go
linkHandler := func(name string) string {
base := strings.TrimSuffix(name, path.Ext(name))
return "/commands/" + strings.ToLower(base) + "/"
}
```

View File

@@ -1,74 +0,0 @@
package doc
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/spf13/cobra"
)
func TestGenYamlDoc(t *testing.T) {
// We generate on s subcommand so we have both subcommands and parents
buf := new(bytes.Buffer)
if err := GenYaml(echoCmd, buf); err != nil {
t.Fatal(err)
}
output := buf.String()
checkStringContains(t, output, echoCmd.Long)
checkStringContains(t, output, echoCmd.Example)
checkStringContains(t, output, "boolone")
checkStringContains(t, output, "rootflag")
checkStringContains(t, output, rootCmd.Short)
checkStringContains(t, output, echoSubCmd.Short)
}
func TestGenYamlNoTag(t *testing.T) {
rootCmd.DisableAutoGenTag = true
defer func() { rootCmd.DisableAutoGenTag = false }()
buf := new(bytes.Buffer)
if err := GenYaml(rootCmd, buf); err != nil {
t.Fatal(err)
}
output := buf.String()
checkStringOmits(t, output, "Auto generated")
}
func TestGenYamlTree(t *testing.T) {
c := &cobra.Command{Use: "do [OPTIONS] arg1 arg2"}
tmpdir, err := ioutil.TempDir("", "test-gen-yaml-tree")
if err != nil {
t.Fatalf("Failed to create tmpdir: %s", err.Error())
}
defer os.RemoveAll(tmpdir)
if err := GenYamlTree(c, tmpdir); err != nil {
t.Fatalf("GenYamlTree failed: %s", err.Error())
}
if _, err := os.Stat(filepath.Join(tmpdir, "do.yaml")); err != nil {
t.Fatalf("Expected file 'do.yaml' to exist")
}
}
func BenchmarkGenYamlToFile(b *testing.B) {
file, err := ioutil.TempFile("", "")
if err != nil {
b.Fatal(err)
}
defer os.Remove(file.Name())
defer file.Close()
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := GenYaml(rootCmd, file); err != nil {
b.Fatal(err)
}
}
}

View File

@@ -1,89 +0,0 @@
package cobra
import (
"bytes"
"strings"
"testing"
)
func TestZshCompletion(t *testing.T) {
tcs := []struct {
name string
root *Command
expectedExpressions []string
}{
{
name: "trivial",
root: &Command{Use: "trivialapp"},
expectedExpressions: []string{"#compdef trivial"},
},
{
name: "linear",
root: func() *Command {
r := &Command{Use: "linear"}
sub1 := &Command{Use: "sub1"}
r.AddCommand(sub1)
sub2 := &Command{Use: "sub2"}
sub1.AddCommand(sub2)
sub3 := &Command{Use: "sub3"}
sub2.AddCommand(sub3)
return r
}(),
expectedExpressions: []string{"sub1", "sub2", "sub3"},
},
{
name: "flat",
root: func() *Command {
r := &Command{Use: "flat"}
r.AddCommand(&Command{Use: "c1"})
r.AddCommand(&Command{Use: "c2"})
return r
}(),
expectedExpressions: []string{"(c1 c2)"},
},
{
name: "tree",
root: func() *Command {
r := &Command{Use: "tree"}
sub1 := &Command{Use: "sub1"}
r.AddCommand(sub1)
sub11 := &Command{Use: "sub11"}
sub12 := &Command{Use: "sub12"}
sub1.AddCommand(sub11)
sub1.AddCommand(sub12)
sub2 := &Command{Use: "sub2"}
r.AddCommand(sub2)
sub21 := &Command{Use: "sub21"}
sub22 := &Command{Use: "sub22"}
sub2.AddCommand(sub21)
sub2.AddCommand(sub22)
return r
}(),
expectedExpressions: []string{"(sub11 sub12)", "(sub21 sub22)"},
},
}
for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
buf := new(bytes.Buffer)
tc.root.GenZshCompletion(buf)
output := buf.String()
for _, expectedExpression := range tc.expectedExpressions {
if !strings.Contains(output, expectedExpression) {
t.Errorf("Expected completion to contain %q somewhere; got %q", expectedExpression, output)
}
}
})
}
}