Repository: spf13/cobra
Pull request #817 (2019-01-31 04:24:02)

Vgo
# Moves to resolve Issue #756 
This was a re-work of the cobra code generator to allow it to work outside of the GOPATH and hence be compatible with Go modules (vgo) introduced in Go 1.11.  This change introduces a new flag to the `init` command and deprecates the `--package` flag from the `add` command.  With all these changes the flows remains relatively the same.  Any exceptions are outlined below.

## Changes
* Add a `--pkg-name` flag to the init command which takes a string representing the package name.
e.g: `--pkg-name github.com/spf14/hugo`
* For adding commands, you should be inside your project root.
* Init will no longer fail on non-empty directories.

ironsalsa: I ran into what this fixes today. Upvote to anything that lets us use the Go Modules way of life, as some of my projects have been moved over by necessity and I'm missing the generator!
jharshman: @ironsalsa Yeah I think there is a lot of desire for this feature, but it's not really getting much attention from the maintainers.  There are actually a lot of open PRs on this project that aren't being attended.
eparis: I'm actually hoping to take a look at all of the vgo PRs today....   hopefully.   I know we need it.
jharshman: 👍 that would be awesome @eparis thanks!!
shawncatz: 👍 
jharshman: @eparis were you able to review this PR?
1138-4EB: ping...
kyriediculous: @eparis So it's been a few months now, did you manage to review it? 

This is a high priority PR... 
jharshman: I've been using my fork of this for some time just to ensure that the usage makes sense and is stable.
So far so good.  I'm going to do a little clean up and rebase against upstream.
williamokano: Still nothing from the maintainers? This feature would be awesome :)
jozuenoon: :ping_pong:
tsal: @jharshman since it doesn't look like we're ever going to hear back from the maintainers on this - can you provide a write-up of how you got this working locally? I tried checking out your branch and doing a "go install ." but the binary doesn't recognize the new flag. I suspect I'm missing something dreadfully obvious.
jharshman: @tsal `go install` works for me.  Make sure you have the branch checked out.
andrewmeissner: @spf13 ping
brainstorm: Might totally be a PEBKAC issue, but your branch doesn't seem to install for me :/

```
$ go version
go version go1.12.4 darwin/amd64
$ cd $GOPATH/src/github.com/spf13/cobra && git checkout -b vgo
Switched to a new branch 'vgo'
$ git remote add vgo https://github.com/jharshman/cobra
$ git pull vgo vgo
remote: Enumerating objects: 108, done.
remote: Counting objects: 100% (108/108), done.
remote: Compressing objects: 100% (30/30), done.
remote: Total 172 (delta 78), reused 107 (delta 78), pack-reused 64
Receiving objects: 100% (172/172), 24.43 KiB | 962.00 KiB/s, done.
Resolving deltas: 100% (104/104), completed with 12 local objects.
From https://github.com/jharshman/cobra
 * branch            vgo        -> FETCH_HEAD
 * [new branch]      vgo        -> vgo/vgo
Updating 67fc483..35a3584
Fast-forward
 .gitignore                        |   2 +
 cobra/cmd/add.go                  | 122 ++++++++++++++-------------------------------
 cobra/cmd/add_test.go             |  92 ++++++++++------------------------
 cobra/cmd/init.go                 | 229 ++++++++++++++-----------------------------------------------------------------------
 cobra/cmd/init_test.go            |  87 +++++++++-----------------------
 cobra/cmd/project.go              | 222 ++++++++++++++++++++++------------------------------------------------------------
 cobra/cmd/project_test.go         |  23 +--------
 cobra/cmd/root.go                 |   3 +-
 cobra/cmd/testdata/main.go.golden |  29 +++++------
 cobra/cmd/testdata/root.go.golden | 124 ++++++++++++++++++++++++----------------------
 cobra/cmd/testdata/test.go.golden |  27 +++++-----
 cobra/tpl/main.go                 | 153 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 12 files changed, 433 insertions(+), 680 deletions(-)
 create mode 100644 cobra/tpl/main.go

$ go install github.com/spf13/cobra/cobra
# github.com/spf13/cobra
./command.go:31:25: undefined: pflag.ParseErrorsWhitelist
$ cobra init --pkg-name github.com/foo/bar
Error: unknown flag: --pkg-name
(...)
```
gmelillo: Some news about that?
will be really useful to have it merged
umarcor: > @jharshman since it doesn't look like we're ever going to hear back from the maintainers on this - can you provide a write-up of how you got this working locally? I tried checking out your branch and doing a "go install ." but the binary doesn't recognize the new flag. I suspect I'm missing something dreadfully obvious.

I hope that the following example is useful to you:

``` bash
#!/bin/sh

cat > run.sh <<-EOF
apk add git
git clone -b vgo https://github.com/jharshman/cobra cobra
cd cobra/cobra
go install
cobra
EOF

$(command -v winpty) docker run --rm -it golang:alpine sh -c "$(cat run.sh)"
```

Patch:
diff --git a/.gitignore b/.gitignore
index 1b8c7c2..3b053c5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,3 +34,5 @@ tags
 *.exe
 
 cobra.test
+
+.idea/*
diff --git a/README.md b/README.md
index ff16e3f..8a9ace4 100644
--- a/README.md
+++ b/README.md
@@ -48,6 +48,7 @@ etc.
   * [Suggestions when "unknown command" happens](#suggestions-when-unknown-command-happens)
   * [Generating documentation for your command](#generating-documentation-for-your-command)
   * [Generating bash completions](#generating-bash-completions)
+  * [Generating zsh completions](#generating-zsh-completions)
 - [Contributing](#contributing)
 - [License](#license)
 
@@ -719,6 +720,11 @@ Cobra can generate documentation based on subcommands, flags, etc. in the follow
 
 Cobra can generate a bash-completion file. If you add more information to your command, these completions can be amazingly powerful and flexible.  Read more about it in [Bash Completions](bash_completions.md).
 
+## Generating zsh completions
+
+Cobra can generate zsh-completion file. Read more about it in
+[Zsh Completions](zsh_completions.md).
+
 # Contributing
 
 1. Fork it
diff --git a/bash_completions.go b/bash_completions.go
index c3c1e50..57bb8e1 100644
--- a/bash_completions.go
+++ b/bash_completions.go
@@ -545,51 +545,3 @@ func (c *Command) GenBashCompletionFile(filename string) error {
 
 	return c.GenBashCompletion(outFile)
 }
-
-// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag if it exists,
-// and causes your command to report an error if invoked without the flag.
-func (c *Command) MarkFlagRequired(name string) error {
-	return MarkFlagRequired(c.Flags(), name)
-}
-
-// MarkPersistentFlagRequired adds the BashCompOneRequiredFlag annotation to the named persistent flag if it exists,
-// and causes your command to report an error if invoked without the flag.
-func (c *Command) MarkPersistentFlagRequired(name string) error {
-	return MarkFlagRequired(c.PersistentFlags(), name)
-}
-
-// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag if it exists,
-// and causes your command to report an error if invoked without the flag.
-func MarkFlagRequired(flags *pflag.FlagSet, name string) error {
-	return flags.SetAnnotation(name, BashCompOneRequiredFlag, []string{"true"})
-}
-
-// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag, if it exists.
-// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided.
-func (c *Command) MarkFlagFilename(name string, extensions ...string) error {
-	return MarkFlagFilename(c.Flags(), name, extensions...)
-}
-
-// MarkFlagCustom adds the BashCompCustom annotation to the named flag, if it exists.
-// Generated bash autocompletion will call the bash function f for the flag.
-func (c *Command) MarkFlagCustom(name string, f string) error {
-	return MarkFlagCustom(c.Flags(), name, f)
-}
-
-// MarkPersistentFlagFilename adds the BashCompFilenameExt annotation to the named persistent flag, if it exists.
-// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided.
-func (c *Command) MarkPersistentFlagFilename(name string, extensions ...string) error {
-	return MarkFlagFilename(c.PersistentFlags(), name, extensions...)
-}
-
-// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag in the flag set, if it exists.
-// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided.
-func MarkFlagFilename(flags *pflag.FlagSet, name string, extensions ...string) error {
-	return flags.SetAnnotation(name, BashCompFilenameExt, extensions)
-}
-
-// MarkFlagCustom adds the BashCompCustom annotation to the named flag in the flag set, if it exists.
-// Generated bash autocompletion will call the bash function f for the flag.
-func MarkFlagCustom(flags *pflag.FlagSet, name string, f string) error {
-	return flags.SetAnnotation(name, BashCompCustom, []string{f})
-}
diff --git a/cobra/cmd/add.go b/cobra/cmd/add.go
index fb22096..2d240b8 100644
--- a/cobra/cmd/add.go
+++ b/cobra/cmd/add.go
@@ -16,24 +16,20 @@ 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 (
+	packageName string
+	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
+	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).
 
@@ -42,28 +38,41 @@ 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")
-		}
+		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)
+			commandName := validateCmdName(args[0])
+			command := &Command{
+				CmdName:   commandName,
+				CmdParent: parentName,
+				Project: &Project{
+					AbsolutePath: wd,
+					Legal:        getLicense(),
+					Copyright:    copyrightLine(),
+				},
+			}
 
-		fmt.Fprintln(cmd.OutOrStdout(), cmdName, "created at", cmdPath)
-	},
+			err = command.Create()
+			if err != nil {
+				er(err)
+			}
+
+			fmt.Printf("%s created at %s", command.CmdName, command.AbsolutePath)
+		},
+	}
+)
+
+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")
+	addCmd.Flags().MarkDeprecated("package", "this operation has been removed.")
 }
 
 // validateCmdName returns source without any dashes and underscore.
@@ -118,62 +127,3 @@ func validateCmdName(source string) string {
 	}
 	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)
-	}
-}
diff --git a/cobra/cmd/init.go b/cobra/cmd/init.go
index d65e6c8..63397d1 100644
--- a/cobra/cmd/init.go
+++ b/cobra/cmd/init.go
@@ -15,19 +15,20 @@ package cmd
 
 import (
 	"fmt"
-	"os"
-	"path"
-	"path/filepath"
-
 	"github.com/spf13/cobra"
 	"github.com/spf13/viper"
+	"os"
+	"path"
 )
 
-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
+var (
+	pkgName string
+
+	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;
@@ -39,196 +40,38 @@ and the appropriate structure for a Cobra-based CLI application.
 
 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)
-		}
+		Run: func(cmd *cobra.Command, args []string) {
 
-		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)
+			wd, err := os.Getwd()
+			if err != nil {
+				er(err)
 			}
-			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 }}
-`
+			if len(args) > 0 {
+				if args[0] != "." {
+					wd = fmt.Sprintf("%s/%s", wd, args[0])
+				}
+			}
 
-	data := make(map[string]interface{})
-	data["copyright"] = copyrightLine()
-	data["viper"] = viper.GetBool("useViper")
-	data["license"] = project.License().Header
-	data["appName"] = path.Base(project.Name())
+			project := &Project{
+				AbsolutePath: wd,
+				PkgName:      pkgName,
+				Legal:        getLicense(),
+				Copyright:    copyrightLine(),
+				Viper:        viper.GetBool("useViper"),
+				AppName:      path.Base(pkgName),
+			}
 
-	rootCmdScript, err := executeTemplate(template, data)
-	if err != nil {
-		er(err)
-	}
+			if err := project.Create(); err != nil {
+				er(err)
+			}
 
-	err = writeStringToFile(filepath.Join(project.CmdPath(), "root.go"), rootCmdScript)
-	if err != nil {
-		er(err)
+			fmt.Printf("Your Cobra applicaton is ready at\n%s\n", project.AbsolutePath)
+		},
 	}
+)
 
+func init() {
+	initCmd.Flags().StringVar(&pkgName, "pkg-name", "", "fully qualified pkg name")
+	initCmd.MarkFlagRequired("pkg-name")
 }
diff --git a/cobra/cmd/project.go b/cobra/cmd/project.go
index 7ddb825..dd2f7ea 100644
--- a/cobra/cmd/project.go
+++ b/cobra/cmd/project.go
@@ -1,200 +1,96 @@
 package cmd
 
 import (
+	"fmt"
+	"github.com/spf13/cobra/cobra/tpl"
 	"os"
-	"path/filepath"
-	"runtime"
-	"strings"
+	"text/template"
 )
 
 // Project contains name, license and paths to projects.
 type Project struct {
-	absPath string
-	cmdPath string
-	srcPath string
-	license License
-	name    string
+	// v2
+	PkgName      string
+	Copyright    string
+	AbsolutePath string
+	Legal        License
+	Viper        bool
+	AppName      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
+type Command struct {
+	CmdName   string
+	CmdParent string
+	*Project
 }
 
-// findPackage returns full path to existing go package in GOPATHs.
-func findPackage(packageName string) string {
-	if packageName == "" {
-		return ""
-	}
+func (p *Project) Create() error {
 
-	for _, srcPath := range srcPaths {
-		packagePath := filepath.Join(srcPath, packageName)
-		if exists(packagePath) {
-			return packagePath
+	// check if AbsolutePath exists
+	if _, err := os.Stat(p.AbsolutePath); os.IsNotExist(err) {
+		// create directory
+		if err := os.Mkdir(p.AbsolutePath, 0754); err != nil {
+			return err
 		}
 	}
 
-	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)
+	// create main.go
+	mainFile, err := os.Create(fmt.Sprintf("%s/main.go", p.AbsolutePath))
 	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
+		return err
 	}
+	defer mainFile.Close()
 
-	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)
+	mainTemplate := template.Must(template.New("main").Parse(string(tpl.MainTemplate())))
+	err = mainTemplate.Execute(mainFile, p)
 	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"
+		return err
 	}
 
-	if isCmdDir(absPath) {
-		return filepath.Base(absPath)
+	// create cmd/root.go
+	if _, err = os.Stat(fmt.Sprintf("%s/cmd", p.AbsolutePath)); os.IsNotExist(err) {
+		os.Mkdir(fmt.Sprintf("%s/cmd", p.AbsolutePath), 0751)
 	}
-
-	files, _ := filepath.Glob(filepath.Join(absPath, "c*"))
-	for _, file := range files {
-		if isCmdDir(file) {
-			return filepath.Base(file)
-		}
+	rootFile, err := os.Create(fmt.Sprintf("%s/cmd/root.go", p.AbsolutePath))
+	if err != nil {
+		return err
 	}
+	defer rootFile.Close()
 
-	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
-		}
+	rootTemplate := template.Must(template.New("root").Parse(string(tpl.RootTemplate())))
+	err = rootTemplate.Execute(rootFile, p)
+	if err != nil {
+		return err
 	}
-	return false
-}
 
-// AbsPath returns absolute path of project.
-func (p Project) AbsPath() string {
-	return p.absPath
+	// create license
+	return p.createLicenseFile()
 }
 
-// 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
+func (p *Project) createLicenseFile() error {
+	data := map[string]interface{}{
+		"copyright": copyrightLine(),
 	}
-
-	for _, srcPath := range srcPaths {
-		if filepathHasPrefix(p.absPath, srcPath) {
-			p.srcPath = srcPath
-			break
-		}
+	licenseFile, err := os.Create(fmt.Sprintf("%s/LICENSE", p.AbsolutePath))
+	if err != nil {
+		return err
 	}
 
-	return p.srcPath
+	licenseTemplate := template.Must(template.New("license").Parse(p.Legal.Text))
+	return licenseTemplate.Execute(licenseFile, data)
 }
 
-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)
+func (c *Command) Create() error {
+	cmdFile, err := os.Create(fmt.Sprintf("%s/cmd/%s.go", c.AbsolutePath, c.CmdName))
+	if err != nil {
+		return err
 	}
-	return path[0:len(prefix)] == prefix
+	defer cmdFile.Close()
 
+	commandTemplate := template.Must(template.New("sub").Parse(string(tpl.AddCommandTemplate())))
+	err = commandTemplate.Execute(cmdFile, c)
+	if err != nil {
+		return err
+	}
+	return nil
 }
diff --git a/cobra/cmd/root.go b/cobra/cmd/root.go
index 19568f9..624c717 100644
--- a/cobra/cmd/root.go
+++ b/cobra/cmd/root.go
@@ -23,7 +23,8 @@ import (
 
 var (
 	// Used for flags.
-	cfgFile, userLicense string
+	cfgFile     string
+	userLicense string
 
 	rootCmd = &cobra.Command{
 		Use:   "cobra",
diff --git a/cobra/tpl/main.go b/cobra/tpl/main.go
new file mode 100644
index 0000000..5e5a0fa
--- /dev/null
+++ b/cobra/tpl/main.go
@@ -0,0 +1,153 @@
+package tpl
+
+func MainTemplate() []byte {
+	return []byte(`/*
+{{ .Copyright }}
+{{ if .Legal.Header }}{{ .Legal.Header }}{{ end }}
+*/
+package main
+
+import "{{ .PkgName }}/cmd"
+
+func main() {
+  cmd.Execute()
+}
+`)
+}
+
+func RootTemplate() []byte {
+	return []byte(`/*
+{{ .Copyright }}
+{{ if .Legal.Header }}{{ .Legal.Header }}{{ end }}
+*/
+package cmd
+
+import (
+  "fmt"
+  "os"
+  "github.com/spf13/cobra"
+{{ if .Viper }}
+  homedir "github.com/mitchellh/go-homedir"
+  "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 }}
+`)
+}
+
+func AddCommandTemplate() []byte {
+	return []byte(`/*
+{{ .Project.Copyright }}
+{{ if .Legal.Header }}{{ .Legal.Header }}{{ end }}
+*/
+package cmd
+
+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() {
+	{{ .CmdParent }}.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")
+}
+`)
+}
diff --git a/powershell_completions.go b/powershell_completions.go
new file mode 100644
index 0000000..756c61b
--- /dev/null
+++ b/powershell_completions.go
@@ -0,0 +1,100 @@
+// PowerShell completions are based on the amazing work from clap:
+// https://github.com/clap-rs/clap/blob/3294d18efe5f264d12c9035f404c7d189d4824e1/src/completions/powershell.rs
+//
+// The generated scripts require PowerShell v5.0+ (which comes Windows 10, but
+// can be downloaded separately for windows 7 or 8.1).
+
+package cobra
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"os"
+	"strings"
+
+	"github.com/spf13/pflag"
+)
+
+var powerShellCompletionTemplate = `using namespace System.Management.Automation
+using namespace System.Management.Automation.Language
+Register-ArgumentCompleter -Native -CommandName '%s' -ScriptBlock {
+    param($wordToComplete, $commandAst, $cursorPosition)
+    $commandElements = $commandAst.CommandElements
+    $command = @(
+        '%s'
+        for ($i = 1; $i -lt $commandElements.Count; $i++) {
+            $element = $commandElements[$i]
+            if ($element -isnot [StringConstantExpressionAst] -or
+                $element.StringConstantType -ne [StringConstantType]::BareWord -or
+                $element.Value.StartsWith('-')) {
+                break
+            }
+            $element.Value
+        }
+    ) -join ';'
+    $completions = @(switch ($command) {%s
+    })
+    $completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
+        Sort-Object -Property ListItemText
+}`
+
+func generatePowerShellSubcommandCases(out io.Writer, cmd *Command, previousCommandName string) {
+	var cmdName string
+	if previousCommandName == "" {
+		cmdName = cmd.Name()
+	} else {
+		cmdName = fmt.Sprintf("%s;%s", previousCommandName, cmd.Name())
+	}
+
+	fmt.Fprintf(out, "\n        '%s' {", cmdName)
+
+	cmd.Flags().VisitAll(func(flag *pflag.Flag) {
+		if nonCompletableFlag(flag) {
+			return
+		}
+		usage := escapeStringForPowerShell(flag.Usage)
+		if len(flag.Shorthand) > 0 {
+			fmt.Fprintf(out, "\n            [CompletionResult]::new('-%s', '%s', [CompletionResultType]::ParameterName, '%s')", flag.Shorthand, flag.Shorthand, usage)
+		}
+		fmt.Fprintf(out, "\n            [CompletionResult]::new('--%s', '%s', [CompletionResultType]::ParameterName, '%s')", flag.Name, flag.Name, usage)
+	})
+
+	for _, subCmd := range cmd.Commands() {
+		usage := escapeStringForPowerShell(subCmd.Short)
+		fmt.Fprintf(out, "\n            [CompletionResult]::new('%s', '%s', [CompletionResultType]::ParameterValue, '%s')", subCmd.Name(), subCmd.Name(), usage)
+	}
+
+	fmt.Fprint(out, "\n            break\n        }")
+
+	for _, subCmd := range cmd.Commands() {
+		generatePowerShellSubcommandCases(out, subCmd, cmdName)
+	}
+}
+
+func escapeStringForPowerShell(s string) string {
+	return strings.Replace(s, "'", "''", -1)
+}
+
+// GenPowerShellCompletion generates PowerShell completion file and writes to the passed writer.
+func (c *Command) GenPowerShellCompletion(w io.Writer) error {
+	buf := new(bytes.Buffer)
+
+	var subCommandCases bytes.Buffer
+	generatePowerShellSubcommandCases(&subCommandCases, c, "")
+	fmt.Fprintf(buf, powerShellCompletionTemplate, c.Name(), c.Name(), subCommandCases.String())
+
+	_, err := buf.WriteTo(w)
+	return err
+}
+
+// GenPowerShellCompletionFile generates PowerShell completion file.
+func (c *Command) GenPowerShellCompletionFile(filename string) error {
+	outFile, err := os.Create(filename)
+	if err != nil {
+		return err
+	}
+	defer outFile.Close()
+
+	return c.GenPowerShellCompletion(outFile)
+}
diff --git a/powershell_completions.md b/powershell_completions.md
new file mode 100644
index 0000000..afed802
--- /dev/null
+++ b/powershell_completions.md
@@ -0,0 +1,14 @@
+# Generating PowerShell Completions For Your Own cobra.Command
+
+Cobra can generate PowerShell completion scripts. Users need PowerShell version 5.0 or above, which comes with Windows 10 and can be downloaded separately for Windows 7 or 8.1. They can then write the completions to a file and source this file from their PowerShell profile, which is referenced by the `$Profile` environment variable. See `Get-Help about_Profiles` for more info about PowerShell profiles.
+
+# What's supported
+
+- Completion for subcommands using their `.Short` description
+- Completion for non-hidden flags using their `.Name` and `.Shorthand`
+
+# What's not yet supported
+
+- Command aliases
+- Required, filename or custom flags (they will work like normal flags)
+- Custom completion scripts
diff --git a/shell_completions.go b/shell_completions.go
new file mode 100644
index 0000000..ba0af9c
--- /dev/null
+++ b/shell_completions.go
@@ -0,0 +1,85 @@
+package cobra
+
+import (
+	"github.com/spf13/pflag"
+)
+
+// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag if it exists,
+// and causes your command to report an error if invoked without the flag.
+func (c *Command) MarkFlagRequired(name string) error {
+	return MarkFlagRequired(c.Flags(), name)
+}
+
+// MarkPersistentFlagRequired adds the BashCompOneRequiredFlag annotation to the named persistent flag if it exists,
+// and causes your command to report an error if invoked without the flag.
+func (c *Command) MarkPersistentFlagRequired(name string) error {
+	return MarkFlagRequired(c.PersistentFlags(), name)
+}
+
+// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag if it exists,
+// and causes your command to report an error if invoked without the flag.
+func MarkFlagRequired(flags *pflag.FlagSet, name string) error {
+	return flags.SetAnnotation(name, BashCompOneRequiredFlag, []string{"true"})
+}
+
+// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag, if it exists.
+// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided.
+func (c *Command) MarkFlagFilename(name string, extensions ...string) error {
+	return MarkFlagFilename(c.Flags(), name, extensions...)
+}
+
+// MarkFlagCustom adds the BashCompCustom annotation to the named flag, if it exists.
+// Generated bash autocompletion will call the bash function f for the flag.
+func (c *Command) MarkFlagCustom(name string, f string) error {
+	return MarkFlagCustom(c.Flags(), name, f)
+}
+
+// MarkPersistentFlagFilename instructs the various shell completion
+// implementations to limit completions for this persistent flag to the
+// specified extensions (patterns).
+//
+// Shell Completion compatibility matrix: bash, zsh
+func (c *Command) MarkPersistentFlagFilename(name string, extensions ...string) error {
+	return MarkFlagFilename(c.PersistentFlags(), name, extensions...)
+}
+
+// MarkFlagFilename instructs the various shell completion implementations to
+// limit completions for this flag to the specified extensions (patterns).
+//
+// Shell Completion compatibility matrix: bash, zsh
+func MarkFlagFilename(flags *pflag.FlagSet, name string, extensions ...string) error {
+	return flags.SetAnnotation(name, BashCompFilenameExt, extensions)
+}
+
+// MarkFlagCustom instructs the various shell completion implementations to
+// limit completions for this flag to the specified extensions (patterns).
+//
+// Shell Completion compatibility matrix: bash, zsh
+func MarkFlagCustom(flags *pflag.FlagSet, name string, f string) error {
+	return flags.SetAnnotation(name, BashCompCustom, []string{f})
+}
+
+// MarkFlagDirname instructs the various shell completion implementations to
+// complete only directories with this named flag.
+//
+// Shell Completion compatibility matrix: zsh
+func (c *Command) MarkFlagDirname(name string) error {
+	return MarkFlagDirname(c.Flags(), name)
+}
+
+// MarkPersistentFlagDirname instructs the various shell completion
+// implementations to complete only directories with this persistent named flag.
+//
+// Shell Completion compatibility matrix: zsh
+func (c *Command) MarkPersistentFlagDirname(name string) error {
+	return MarkFlagDirname(c.PersistentFlags(), name)
+}
+
+// MarkFlagDirname instructs the various shell completion implementations to
+// complete only directories with this specified flag.
+//
+// Shell Completion compatibility matrix: zsh
+func MarkFlagDirname(flags *pflag.FlagSet, name string) error {
+	zshPattern := "-(/)"
+	return flags.SetAnnotation(name, zshCompDirname, []string{zshPattern})
+}
diff --git a/zsh_completions.go b/zsh_completions.go
index 889c22e..1275548 100644
--- a/zsh_completions.go
+++ b/zsh_completions.go
@@ -1,13 +1,102 @@
 package cobra
 
 import (
-	"bytes"
+	"encoding/json"
 	"fmt"
 	"io"
 	"os"
+	"sort"
 	"strings"
+	"text/template"
+
+	"github.com/spf13/pflag"
+)
+
+const (
+	zshCompArgumentAnnotation   = "cobra_annotations_zsh_completion_argument_annotation"
+	zshCompArgumentFilenameComp = "cobra_annotations_zsh_completion_argument_file_completion"
+	zshCompArgumentWordComp     = "cobra_annotations_zsh_completion_argument_word_completion"
+	zshCompDirname              = "cobra_annotations_zsh_dirname"
+)
+
+var (
+	zshCompFuncMap = template.FuncMap{
+		"genZshFuncName":              zshCompGenFuncName,
+		"extractFlags":                zshCompExtractFlag,
+		"genFlagEntryForZshArguments": zshCompGenFlagEntryForArguments,
+		"extractArgsCompletions":      zshCompExtractArgumentCompletionHintsForRendering,
+	}
+	zshCompletionText = `
+{{/* should accept Command (that contains subcommands) as parameter */}}
+{{define "argumentsC" -}}
+{{ $cmdPath := genZshFuncName .}}
+function {{$cmdPath}} {
+  local -a commands
+
+  _arguments -C \{{- range extractFlags .}}
+    {{genFlagEntryForZshArguments .}} \{{- end}}
+    "1: :->cmnds" \
+    "*::arg:->args"
+
+  case $state in
+  cmnds)
+    commands=({{range .Commands}}{{if not .Hidden}}
+      "{{.Name}}:{{.Short}}"{{end}}{{end}}
+    )
+    _describe "command" commands
+    ;;
+  esac
+
+  case "$words[1]" in {{- range .Commands}}{{if not .Hidden}}
+  {{.Name}})
+    {{$cmdPath}}_{{.Name}}
+    ;;{{end}}{{end}}
+  esac
+}
+{{range .Commands}}{{if not .Hidden}}
+{{template "selectCmdTemplate" .}}
+{{- end}}{{end}}
+{{- end}}
+
+{{/* should accept Command without subcommands as parameter */}}
+{{define "arguments" -}}
+function {{genZshFuncName .}} {
+{{"  _arguments"}}{{range extractFlags .}} \
+    {{genFlagEntryForZshArguments . -}}
+{{end}}{{range extractArgsCompletions .}} \
+    {{.}}{{end}}
+}
+{{end}}
+
+{{/* dispatcher for commands with or without subcommands */}}
+{{define "selectCmdTemplate" -}}
+{{if .Hidden}}{{/* ignore hidden*/}}{{else -}}
+{{if .Commands}}{{template "argumentsC" .}}{{else}}{{template "arguments" .}}{{end}}
+{{- end}}
+{{- end}}
+
+{{/* template entry point */}}
+{{define "Main" -}}
+#compdef _{{.Name}} {{.Name}}
+
+{{template "selectCmdTemplate" .}}
+{{end}}
+`
 )
 
+// zshCompArgsAnnotation is used to encode/decode zsh completion for
+// arguments to/from Command.Annotations.
+type zshCompArgsAnnotation map[int]zshCompArgHint
+
+type zshCompArgHint struct {
+	// Indicates the type of the completion to use. One of:
+	// zshCompArgumentFilenameComp or zshCompArgumentWordComp
+	Tipe string `json:"type"`
+
+	// A value for the type above (globs for file completion or words)
+	Options []string `json:"options"`
+}
+
 // GenZshCompletionFile generates zsh completion file.
 func (c *Command) GenZshCompletionFile(filename string) error {
 	outFile, err := os.Create(filename)
@@ -19,108 +108,229 @@ func (c *Command) GenZshCompletionFile(filename string) error {
 	return c.GenZshCompletion(outFile)
 }
 
-// GenZshCompletion generates a zsh completion file and writes to the passed writer.
+// GenZshCompletion generates a zsh completion file and writes to the passed
+// writer. The completion always run on the root command regardless of the
+// command it was called from.
 func (c *Command) GenZshCompletion(w io.Writer) error {
-	buf := new(bytes.Buffer)
-
-	writeHeader(buf, c)
-	maxDepth := maxDepth(c)
-	writeLevelMapping(buf, maxDepth)
-	writeLevelCases(buf, maxDepth, c)
+	tmpl, err := template.New("Main").Funcs(zshCompFuncMap).Parse(zshCompletionText)
+	if err != nil {
+		return fmt.Errorf("error creating zsh completion template: %v", err)
+	}
+	return tmpl.Execute(w, c.Root())
+}
 
-	_, err := buf.WriteTo(w)
-	return err
+// MarkZshCompPositionalArgumentFile marks the specified argument (first
+// argument is 1) as completed by file selection. patterns (e.g. "*.txt") are
+// optional - if not provided the completion will search for all files.
+func (c *Command) MarkZshCompPositionalArgumentFile(argPosition int, patterns ...string) error {
+	if argPosition < 1 {
+		return fmt.Errorf("Invalid argument position (%d)", argPosition)
+	}
+	annotation, err := c.zshCompGetArgsAnnotations()
+	if err != nil {
+		return err
+	}
+	if c.zshcompArgsAnnotationnIsDuplicatePosition(annotation, argPosition) {
+		return fmt.Errorf("Duplicate annotation for positional argument at index %d", argPosition)
+	}
+	annotation[argPosition] = zshCompArgHint{
+		Tipe:    zshCompArgumentFilenameComp,
+		Options: patterns,
+	}
+	return c.zshCompSetArgsAnnotations(annotation)
 }
 
-func writeHeader(w io.Writer, cmd *Command) {
-	fmt.Fprintf(w, "#compdef %s\n\n", cmd.Name())
+// MarkZshCompPositionalArgumentWords marks the specified positional argument
+// (first argument is 1) as completed by the provided words. At east one word
+// must be provided, spaces within words will be offered completion with
+// "word\ word".
+func (c *Command) MarkZshCompPositionalArgumentWords(argPosition int, words ...string) error {
+	if argPosition < 1 {
+		return fmt.Errorf("Invalid argument position (%d)", argPosition)
+	}
+	if len(words) == 0 {
+		return fmt.Errorf("Trying to set empty word list for positional argument %d", argPosition)
+	}
+	annotation, err := c.zshCompGetArgsAnnotations()
+	if err != nil {
+		return err
+	}
+	if c.zshcompArgsAnnotationnIsDuplicatePosition(annotation, argPosition) {
+		return fmt.Errorf("Duplicate annotation for positional argument at index %d", argPosition)
+	}
+	annotation[argPosition] = zshCompArgHint{
+		Tipe:    zshCompArgumentWordComp,
+		Options: words,
+	}
+	return c.zshCompSetArgsAnnotations(annotation)
 }
 
-func maxDepth(c *Command) int {
-	if len(c.Commands()) == 0 {
-		return 0
+func zshCompExtractArgumentCompletionHintsForRendering(c *Command) ([]string, error) {
+	var result []string
+	annotation, err := c.zshCompGetArgsAnnotations()
+	if err != nil {
+		return nil, err
 	}
-	maxDepthSub := 0
-	for _, s := range c.Commands() {
-		subDepth := maxDepth(s)
-		if subDepth > maxDepthSub {
-			maxDepthSub = subDepth
+	for k, v := range annotation {
+		s, err := zshCompRenderZshCompArgHint(k, v)
+		if err != nil {
+			return nil, err
 		}
+		result = append(result, s)
 	}
-	return 1 + maxDepthSub
+	if len(c.ValidArgs) > 0 {
+		if _, positionOneExists := annotation[1]; !positionOneExists {
+			s, err := zshCompRenderZshCompArgHint(1, zshCompArgHint{
+				Tipe:    zshCompArgumentWordComp,
+				Options: c.ValidArgs,
+			})
+			if err != nil {
+				return nil, err
+			}
+			result = append(result, s)
+		}
+	}
+	sort.Strings(result)
+	return result, nil
 }
 
-func writeLevelMapping(w io.Writer, numLevels int) {
-	fmt.Fprintln(w, `_arguments \`)
-	for i := 1; i <= numLevels; i++ {
-		fmt.Fprintf(w, `  '%d: :->level%d' \`, i, i)
-		fmt.Fprintln(w)
+func zshCompRenderZshCompArgHint(i int, z zshCompArgHint) (string, error) {
+	switch t := z.Tipe; t {
+	case zshCompArgumentFilenameComp:
+		var globs []string
+		for _, g := range z.Options {
+			globs = append(globs, fmt.Sprintf(`-g "%s"`, g))
+		}
+		return fmt.Sprintf(`'%d: :_files %s'`, i, strings.Join(globs, " ")), nil
+	case zshCompArgumentWordComp:
+		var words []string
+		for _, w := range z.Options {
+			words = append(words, fmt.Sprintf("%q", w))
+		}
+		return fmt.Sprintf(`'%d: :(%s)'`, i, strings.Join(words, " ")), nil
+	default:
+		return "", fmt.Errorf("Invalid zsh argument completion annotation: %s", t)
 	}
-	fmt.Fprintf(w, `  '%d: :%s'`, numLevels+1, "_files")
-	fmt.Fprintln(w)
 }
 
-func writeLevelCases(w io.Writer, maxDepth int, root *Command) {
-	fmt.Fprintln(w, "case $state in")
-	defer fmt.Fprintln(w, "esac")
+func (c *Command) zshcompArgsAnnotationnIsDuplicatePosition(annotation zshCompArgsAnnotation, position int) bool {
+	_, dup := annotation[position]
+	return dup
+}
 
-	for i := 1; i <= maxDepth; i++ {
-		fmt.Fprintf(w, "  level%d)\n", i)
-		writeLevel(w, root, i)
-		fmt.Fprintln(w, "  ;;")
+func (c *Command) zshCompGetArgsAnnotations() (zshCompArgsAnnotation, error) {
+	annotation := make(zshCompArgsAnnotation)
+	annotationString, ok := c.Annotations[zshCompArgumentAnnotation]
+	if !ok {
+		return annotation, nil
+	}
+	err := json.Unmarshal([]byte(annotationString), &annotation)
+	if err != nil {
+		return annotation, fmt.Errorf("Error unmarshaling zsh argument annotation: %v", err)
 	}
-	fmt.Fprintln(w, "  *)")
-	fmt.Fprintln(w, "    _arguments '*: :_files'")
-	fmt.Fprintln(w, "  ;;")
+	return annotation, nil
 }
 
-func writeLevel(w io.Writer, root *Command, i int) {
-	fmt.Fprintf(w, "    case $words[%d] in\n", i)
-	defer fmt.Fprintln(w, "    esac")
-
-	commands := filterByLevel(root, i)
-	byParent := groupByParent(commands)
+func (c *Command) zshCompSetArgsAnnotations(annotation zshCompArgsAnnotation) error {
+	jsn, err := json.Marshal(annotation)
+	if err != nil {
+		return fmt.Errorf("Error marshaling zsh argument annotation: %v", err)
+	}
+	if c.Annotations == nil {
+		c.Annotations = make(map[string]string)
+	}
+	c.Annotations[zshCompArgumentAnnotation] = string(jsn)
+	return nil
+}
 
-	for p, c := range byParent {
-		names := names(c)
-		fmt.Fprintf(w, "      %s)\n", p)
-		fmt.Fprintf(w, "        _arguments '%d: :(%s)'\n", i, strings.Join(names, " "))
-		fmt.Fprintln(w, "      ;;")
+func zshCompGenFuncName(c *Command) string {
+	if c.HasParent() {
+		return zshCompGenFuncName(c.Parent()) + "_" + c.Name()
 	}
-	fmt.Fprintln(w, "      *)")
-	fmt.Fprintln(w, "        _arguments '*: :_files'")
-	fmt.Fprintln(w, "      ;;")
+	return "_" + c.Name()
+}
 
+func zshCompExtractFlag(c *Command) []*pflag.Flag {
+	var flags []*pflag.Flag
+	c.LocalFlags().VisitAll(func(f *pflag.Flag) {
+		if !f.Hidden {
+			flags = append(flags, f)
+		}
+	})
+	c.InheritedFlags().VisitAll(func(f *pflag.Flag) {
+		if !f.Hidden {
+			flags = append(flags, f)
+		}
+	})
+	return flags
 }
 
-func filterByLevel(c *Command, l int) []*Command {
-	cs := make([]*Command, 0)
-	if l == 0 {
-		cs = append(cs, c)
-		return cs
+// zshCompGenFlagEntryForArguments returns an entry that matches _arguments
+// zsh-completion parameters. It's too complicated to generate in a template.
+func zshCompGenFlagEntryForArguments(f *pflag.Flag) string {
+	if f.Name == "" || f.Shorthand == "" {
+		return zshCompGenFlagEntryForSingleOptionFlag(f)
 	}
-	for _, s := range c.Commands() {
-		cs = append(cs, filterByLevel(s, l-1)...)
+	return zshCompGenFlagEntryForMultiOptionFlag(f)
+}
+
+func zshCompGenFlagEntryForSingleOptionFlag(f *pflag.Flag) string {
+	var option, multiMark, extras string
+
+	if zshCompFlagCouldBeSpecifiedMoreThenOnce(f) {
+		multiMark = "*"
 	}
-	return cs
+
+	option = "--" + f.Name
+	if option == "--" {
+		option = "-" + f.Shorthand
+	}
+	extras = zshCompGenFlagEntryExtras(f)
+
+	return fmt.Sprintf(`'%s%s[%s]%s'`, multiMark, option, zshCompQuoteFlagDescription(f.Usage), extras)
 }
 
-func groupByParent(commands []*Command) map[string][]*Command {
-	m := make(map[string][]*Command)
-	for _, c := range commands {
-		parent := c.Parent()
-		if parent == nil {
-			continue
-		}
-		m[parent.Name()] = append(m[parent.Name()], c)
+func zshCompGenFlagEntryForMultiOptionFlag(f *pflag.Flag) string {
+	var options, parenMultiMark, curlyMultiMark, extras string
+
+	if zshCompFlagCouldBeSpecifiedMoreThenOnce(f) {
+		parenMultiMark = "*"
+		curlyMultiMark = "\\*"
 	}
-	return m
+
+	options = fmt.Sprintf(`'(%s-%s %s--%s)'{%s-%s,%s--%s}`,
+		parenMultiMark, f.Shorthand, parenMultiMark, f.Name, curlyMultiMark, f.Shorthand, curlyMultiMark, f.Name)
+	extras = zshCompGenFlagEntryExtras(f)
+
+	return fmt.Sprintf(`%s'[%s]%s'`, options, zshCompQuoteFlagDescription(f.Usage), extras)
 }
 
-func names(commands []*Command) []string {
-	ns := make([]string, len(commands))
-	for i, c := range commands {
-		ns[i] = c.Name()
+func zshCompGenFlagEntryExtras(f *pflag.Flag) string {
+	if f.NoOptDefVal != "" {
+		return ""
 	}
-	return ns
+
+	extras := ":" // allow options for flag (even without assistance)
+	for key, values := range f.Annotations {
+		switch key {
+		case zshCompDirname:
+			extras = fmt.Sprintf(":filename:_files -g %q", values[0])
+		case BashCompFilenameExt:
+			extras = ":filename:_files"
+			for _, pattern := range values {
+				extras = extras + fmt.Sprintf(` -g "%s"`, pattern)
+			}
+		}
+	}
+
+	return extras
+}
+
+func zshCompFlagCouldBeSpecifiedMoreThenOnce(f *pflag.Flag) bool {
+	return strings.Contains(f.Value.Type(), "Slice") ||
+		strings.Contains(f.Value.Type(), "Array")
+}
+
+func zshCompQuoteFlagDescription(s string) string {
+	return strings.Replace(s, "'", `'\''`, -1)
 }
diff --git a/zsh_completions.md b/zsh_completions.md
new file mode 100644
index 0000000..df9c2ea
--- /dev/null
+++ b/zsh_completions.md
@@ -0,0 +1,39 @@
+## Generating Zsh Completion for your cobra.Command
+
+Cobra supports native Zsh completion generated from the root `cobra.Command`.
+The generated completion script should be put somewhere in your `$fpath` named
+`_<YOUR COMMAND>`.
+
+### What's Supported
+
+* Completion for all non-hidden subcommands using their `.Short` description.
+* Completion for all non-hidden flags using the following rules:
+  * Filename completion works by marking the flag with `cmd.MarkFlagFilename...`
+    family of commands.
+  * The requirement for argument to the flag is decided by the `.NoOptDefVal`
+    flag value - if it's empty then completion will expect an argument.
+  * Flags of one of the various `*Array` and `*Slice` types supports multiple
+    specifications (with or without argument depending on the specific type).
+* Completion of positional arguments using the following rules:
+  * Argument position for all options below starts at `1`. If argument position
+    `0` is requested it will raise an error.
+  * Use `command.MarkZshCompPositionalArgumentFile` to complete filenames. Glob
+    patterns (e.g. `"*.log"`) are optional - if not specified it will offer to
+    complete all file types.
+  * Use `command.MarkZshCompPositionalArgumentWords` to offer specific words for
+    completion. At least one word is required.
+  * It's possible to specify completion for some arguments and leave some
+    unspecified (e.g. offer words for second argument but nothing for first
+    argument). This will cause no completion for first argument but words
+    completion for second argument.
+  * If no argument completion was specified for 1st argument (but optionally was
+    specified for 2nd) and the command has `ValidArgs` it will be used as
+    completion options for 1st argument.
+  * Argument completions only offered for commands with no subcommands.
+
+### What's not yet Supported
+
+* Custom completion scripts are not supported yet (We should probably create zsh
+  specific one, doesn't make sense to re-use the bash one as the functions will
+  be different).
+* Whatever other feature you're looking for and doesn't exist :)


Test patch:
diff --git a/cobra/cmd/add_test.go b/cobra/cmd/add_test.go
index b920e2b..0de1d22 100644
--- a/cobra/cmd/add_test.go
+++ b/cobra/cmd/add_test.go
@@ -1,85 +1,45 @@
 package cmd
 
 import (
-	"errors"
-	"io/ioutil"
+	"fmt"
 	"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)
+	wd, _ := os.Getwd()
+	command := &Command{
+		CmdName:   "test",
+		CmdParent: parentName,
+		Project: &Project{
+			AbsolutePath: fmt.Sprintf("%s/testproject", wd),
+			Legal:        getLicense(),
+			Copyright:    copyrightLine(),
 
-	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
-		}
+			// required to init
+			AppName: "testproject",
+			PkgName: "github.com/spf13/testproject",
+			Viper:   true,
+		},
+	}
 
-		// 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
+	// init project first
+	command.Project.Create()
+	defer func() {
+		if _, err := os.Stat(command.AbsolutePath); err == nil {
+			os.RemoveAll(command.AbsolutePath)
 		}
-		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 {
+	if err := command.Create(); err != nil {
 		t.Fatal(err)
 	}
 
-	// Check if some files lack.
-	if err := checkLackFiles(expectedFiles, gotFiles); err != nil {
+	generatedFile := fmt.Sprintf("%s/cmd/%s.go", command.AbsolutePath, command.CmdName)
+	goldenFile := fmt.Sprintf("testdata/%s.go.golden", command.CmdName)
+	err := compareFiles(generatedFile, goldenFile)
+	if err != nil {
 		t.Fatal(err)
 	}
 }
diff --git a/cobra/cmd/init_test.go b/cobra/cmd/init_test.go
index 40eb403..9540b2d 100644
--- a/cobra/cmd/init_test.go
+++ b/cobra/cmd/init_test.go
@@ -1,83 +1,42 @@
 package cmd
 
 import (
-	"errors"
-	"io/ioutil"
+	"fmt"
 	"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)
+	wd, _ := os.Getwd()
+	project := &Project{
+		AbsolutePath: fmt.Sprintf("%s/testproject", wd),
+		PkgName:      "github.com/spf13/testproject",
+		Legal:        getLicense(),
+		Copyright:    copyrightLine(),
+		Viper:        true,
+		AppName:      "testproject",
 	}
 
-	expectedFiles := []string{".", "cmd", "LICENSE", "main.go", "cmd/root.go"}
-	gotFiles := []string{}
+	err := project.Create()
+	if err != nil {
+		t.Fatal(err)
+	}
 
-	// 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
+	defer func() {
+		if _, err := os.Stat(project.AbsolutePath); err == nil {
+			os.RemoveAll(project.AbsolutePath)
 		}
+	}()
 
-		// 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)
+	expectedFiles := []string{"LICENSE", "main.go", "cmd/root.go"}
+	for _, f := range expectedFiles {
+		generatedFile := fmt.Sprintf("%s/%s", project.AbsolutePath, f)
+		goldenFile := fmt.Sprintf("testdata/%s.golden", filepath.Base(f))
+		err := compareFiles(generatedFile, goldenFile)
 		if err != nil {
-			return err
+			t.Fatal(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)
 	}
 }
diff --git a/cobra/cmd/project_test.go b/cobra/cmd/project_test.go
index 037f7c5..ed5b054 100644
--- a/cobra/cmd/project_test.go
+++ b/cobra/cmd/project_test.go
@@ -1,24 +1,3 @@
 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
-}
+/* todo: write tests */
diff --git a/cobra/cmd/testdata/main.go.golden b/cobra/cmd/testdata/main.go.golden
index cdbe38d..4ad570c 100644
--- a/cobra/cmd/testdata/main.go.golden
+++ b/cobra/cmd/testdata/main.go.golden
@@ -1,21 +1,22 @@
-// 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.
+/*
+Copyright © 2019 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()
+  cmd.Execute()
 }
diff --git a/cobra/cmd/testdata/root.go.golden b/cobra/cmd/testdata/root.go.golden
index d74f4cd..d3b889b 100644
--- a/cobra/cmd/testdata/root.go.golden
+++ b/cobra/cmd/testdata/root.go.golden
@@ -1,89 +1,97 @@
-// 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.
+/*
+Copyright © 2019 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"
+  "fmt"
+  "os"
+  "github.com/spf13/cobra"
+
+  homedir "github.com/mitchellh/go-homedir"
+  "github.com/spf13/viper"
 
-	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
+  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) { },
+  // 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)
-	}
+  if err := rootCmd.Execute(); err != nil {
+    fmt.Println(err)
+    os.Exit(1)
+  }
 }
 
 func init() {
-	cobra.OnInitialize(initConfig)
+  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.
 
-	// 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)")
+  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")
+
+  // 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())
-	}
+  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())
+  }
 }
+
diff --git a/cobra/cmd/testdata/test.go.golden b/cobra/cmd/testdata/test.go.golden
index ed64427..fb8e0fa 100644
--- a/cobra/cmd/testdata/test.go.golden
+++ b/cobra/cmd/testdata/test.go.golden
@@ -1,17 +1,18 @@
-// 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.
+/*
+Copyright © 2019 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 (
diff --git a/powershell_completions_test.go b/powershell_completions_test.go
new file mode 100644
index 0000000..29b609d
--- /dev/null
+++ b/powershell_completions_test.go
@@ -0,0 +1,122 @@
+package cobra
+
+import (
+	"bytes"
+	"strings"
+	"testing"
+)
+
+func TestPowerShellCompletion(t *testing.T) {
+	tcs := []struct {
+		name                string
+		root                *Command
+		expectedExpressions []string
+	}{
+		{
+			name: "trivial",
+			root: &Command{Use: "trivialapp"},
+			expectedExpressions: []string{
+				"Register-ArgumentCompleter -Native -CommandName 'trivialapp' -ScriptBlock",
+				"$command = @(\n        'trivialapp'\n",
+			},
+		},
+		{
+			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{
+				"'tree'",
+				"[CompletionResult]::new('sub1', 'sub1', [CompletionResultType]::ParameterValue, '')",
+				"[CompletionResult]::new('sub2', 'sub2', [CompletionResultType]::ParameterValue, '')",
+				"'tree;sub1'",
+				"[CompletionResult]::new('sub11', 'sub11', [CompletionResultType]::ParameterValue, '')",
+				"[CompletionResult]::new('sub12', 'sub12', [CompletionResultType]::ParameterValue, '')",
+				"'tree;sub1;sub11'",
+				"'tree;sub1;sub12'",
+				"'tree;sub2'",
+				"[CompletionResult]::new('sub21', 'sub21', [CompletionResultType]::ParameterValue, '')",
+				"[CompletionResult]::new('sub22', 'sub22', [CompletionResultType]::ParameterValue, '')",
+				"'tree;sub2;sub21'",
+				"'tree;sub2;sub22'",
+			},
+		},
+		{
+			name: "flags",
+			root: func() *Command {
+				r := &Command{Use: "flags"}
+				r.Flags().StringP("flag1", "a", "", "")
+				r.Flags().String("flag2", "", "")
+
+				sub1 := &Command{Use: "sub1"}
+				sub1.Flags().StringP("flag3", "c", "", "")
+				r.AddCommand(sub1)
+
+				return r
+			}(),
+			expectedExpressions: []string{
+				"'flags'",
+				"[CompletionResult]::new('-a', 'a', [CompletionResultType]::ParameterName, '')",
+				"[CompletionResult]::new('--flag1', 'flag1', [CompletionResultType]::ParameterName, '')",
+				"[CompletionResult]::new('--flag2', 'flag2', [CompletionResultType]::ParameterName, '')",
+				"[CompletionResult]::new('sub1', 'sub1', [CompletionResultType]::ParameterValue, '')",
+				"'flags;sub1'",
+				"[CompletionResult]::new('-c', 'c', [CompletionResultType]::ParameterName, '')",
+				"[CompletionResult]::new('--flag3', 'flag3', [CompletionResultType]::ParameterName, '')",
+			},
+		},
+		{
+			name: "usage",
+			root: func() *Command {
+				r := &Command{Use: "usage"}
+				r.Flags().String("flag", "", "this describes the usage of the 'flag' flag")
+
+				sub1 := &Command{
+					Use:   "sub1",
+					Short: "short describes 'sub1'",
+				}
+				r.AddCommand(sub1)
+
+				return r
+			}(),
+			expectedExpressions: []string{
+				"[CompletionResult]::new('--flag', 'flag', [CompletionResultType]::ParameterName, 'this describes the usage of the ''flag'' flag')",
+				"[CompletionResult]::new('sub1', 'sub1', [CompletionResultType]::ParameterValue, 'short describes ''sub1''')",
+			},
+		},
+	}
+
+	for _, tc := range tcs {
+		t.Run(tc.name, func(t *testing.T) {
+			buf := new(bytes.Buffer)
+			tc.root.GenPowerShellCompletion(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)
+				}
+			}
+		})
+	}
+}
diff --git a/zsh_completions_test.go b/zsh_completions_test.go
index 34e6949..e53fa88 100644
--- a/zsh_completions_test.go
+++ b/zsh_completions_test.go
@@ -2,88 +2,474 @@ package cobra
 
 import (
 	"bytes"
+	"regexp"
 	"strings"
 	"testing"
 )
 
-func TestZshCompletion(t *testing.T) {
+func TestGenZshCompletion(t *testing.T) {
+	var debug bool
+	var option string
+
 	tcs := []struct {
 		name                string
 		root                *Command
 		expectedExpressions []string
+		invocationArgs      []string
+		skip                string
 	}{
 		{
-			name:                "trivial",
-			root:                &Command{Use: "trivialapp"},
-			expectedExpressions: []string{"#compdef trivial"},
+			name: "simple command",
+			root: func() *Command {
+				r := &Command{
+					Use:  "mycommand",
+					Long: "My Command long description",
+					Run:  emptyRun,
+				}
+				r.Flags().BoolVar(&debug, "debug", debug, "description")
+				return r
+			}(),
+			expectedExpressions: []string{
+				`(?s)function _mycommand {\s+_arguments \\\s+'--debug\[description\]'.*--help.*}`,
+				"#compdef _mycommand mycommand",
+			},
 		},
 		{
-			name: "linear",
+			name: "flags with both long and short flags",
 			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)
+				r := &Command{
+					Use:  "testcmd",
+					Long: "long description",
+					Run:  emptyRun,
+				}
+				r.Flags().BoolVarP(&debug, "debug", "d", debug, "debug description")
 				return r
 			}(),
-			expectedExpressions: []string{"sub1", "sub2", "sub3"},
+			expectedExpressions: []string{
+				`'\(-d --debug\)'{-d,--debug}'\[debug description\]'`,
+			},
 		},
 		{
-			name: "flat",
+			name: "command with subcommands and flags with values",
 			root: func() *Command {
-				r := &Command{Use: "flat"}
-				r.AddCommand(&Command{Use: "c1"})
-				r.AddCommand(&Command{Use: "c2"})
+				r := &Command{
+					Use:  "rootcmd",
+					Long: "Long rootcmd description",
+				}
+				d := &Command{
+					Use:   "subcmd1",
+					Short: "Subcmd1 short description",
+					Run:   emptyRun,
+				}
+				e := &Command{
+					Use:  "subcmd2",
+					Long: "Subcmd2 short description",
+					Run:  emptyRun,
+				}
+				r.PersistentFlags().BoolVar(&debug, "debug", debug, "description")
+				d.Flags().StringVarP(&option, "option", "o", option, "option description")
+				r.AddCommand(d, e)
 				return r
 			}(),
-			expectedExpressions: []string{"(c1 c2)"},
+			expectedExpressions: []string{
+				`commands=\(\n\s+"help:.*\n\s+"subcmd1:.*\n\s+"subcmd2:.*\n\s+\)`,
+				`_arguments \\\n.*'--debug\[description]'`,
+				`_arguments -C \\\n.*'--debug\[description]'`,
+				`function _rootcmd_subcmd1 {`,
+				`function _rootcmd_subcmd1 {`,
+				`_arguments \\\n.*'\(-o --option\)'{-o,--option}'\[option description]:' \\\n`,
+			},
 		},
 		{
-			name: "tree",
+			name: "filename completion with and without globs",
 			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)
+				var file string
+				r := &Command{
+					Use:   "mycmd",
+					Short: "my command short description",
+					Run:   emptyRun,
+				}
+				r.Flags().StringVarP(&file, "config", "c", file, "config file")
+				r.MarkFlagFilename("config")
+				r.Flags().String("output", "", "output file")
+				r.MarkFlagFilename("output", "*.log", "*.txt")
+				return r
+			}(),
+			expectedExpressions: []string{
+				`\n +'\(-c --config\)'{-c,--config}'\[config file]:filename:_files'`,
+				`:_files -g "\*.log" -g "\*.txt"`,
+			},
+		},
+		{
+			name: "repeated variables both with and without value",
+			root: func() *Command {
+				r := genTestCommand("mycmd", true)
+				_ = r.Flags().BoolSliceP("debug", "d", []bool{}, "debug usage")
+				_ = r.Flags().StringArray("option", []string{}, "options")
+				return r
+			}(),
+			expectedExpressions: []string{
+				`'\*--option\[options]`,
+				`'\(\*-d \*--debug\)'{\\\*-d,\\\*--debug}`,
+			},
+		},
+		{
+			name: "generated flags --help and --version should be created even when not executing root cmd",
+			root: func() *Command {
+				r := &Command{
+					Use:     "mycmd",
+					Short:   "mycmd short description",
+					Version: "myversion",
+				}
+				s := genTestCommand("sub1", true)
+				r.AddCommand(s)
+				return s
+			}(),
+			expectedExpressions: []string{
+				"--version",
+				"--help",
+			},
+			invocationArgs: []string{
+				"sub1",
+			},
+			skip: "--version and --help are currently not generated when not running on root command",
+		},
+		{
+			name: "zsh generation should run on root command",
+			root: func() *Command {
+				r := genTestCommand("root", false)
+				s := genTestCommand("sub1", true)
+				r.AddCommand(s)
+				return s
+			}(),
+			expectedExpressions: []string{
+				"function _root {",
+			},
+		},
+		{
+			name: "flag description with single quote (') shouldn't break quotes in completion file",
+			root: func() *Command {
+				r := genTestCommand("root", true)
+				r.Flags().Bool("private", false, "Don't show public info")
+				return r
+			}(),
+			expectedExpressions: []string{
+				`--private\[Don'\\''t show public info]`,
+			},
+		},
+		{
+			name: "argument completion for file with and without patterns",
+			root: func() *Command {
+				r := genTestCommand("root", true)
+				r.MarkZshCompPositionalArgumentFile(1, "*.log")
+				r.MarkZshCompPositionalArgumentFile(2)
+				return r
+			}(),
+			expectedExpressions: []string{
+				`'1: :_files -g "\*.log"' \\\n\s+'2: :_files`,
+			},
+		},
+		{
+			name: "argument zsh completion for words",
+			root: func() *Command {
+				r := genTestCommand("root", true)
+				r.MarkZshCompPositionalArgumentWords(1, "word1", "word2")
+				return r
+			}(),
+			expectedExpressions: []string{
+				`'1: :\("word1" "word2"\)`,
+			},
+		},
+		{
+			name: "argument completion for words with spaces",
+			root: func() *Command {
+				r := genTestCommand("root", true)
+				r.MarkZshCompPositionalArgumentWords(1, "single", "multiple words")
+				return r
+			}(),
+			expectedExpressions: []string{
+				`'1: :\("single" "multiple words"\)'`,
+			},
+		},
+		{
+			name: "argument completion when command has ValidArgs and no annotation for argument completion",
+			root: func() *Command {
+				r := genTestCommand("root", true)
+				r.ValidArgs = []string{"word1", "word2"}
+				return r
+			}(),
+			expectedExpressions: []string{
+				`'1: :\("word1" "word2"\)'`,
+			},
+		},
+		{
+			name: "argument completion when command has ValidArgs and no annotation for argument at argPosition 1",
+			root: func() *Command {
+				r := genTestCommand("root", true)
+				r.ValidArgs = []string{"word1", "word2"}
+				r.MarkZshCompPositionalArgumentFile(2)
+				return r
+			}(),
+			expectedExpressions: []string{
+				`'1: :\("word1" "word2"\)' \\`,
+			},
+		},
+		{
+			name: "directory completion for flag",
+			root: func() *Command {
+				r := genTestCommand("root", true)
+				r.Flags().String("test", "", "test")
+				r.PersistentFlags().String("ptest", "", "ptest")
+				r.MarkFlagDirname("test")
+				r.MarkPersistentFlagDirname("ptest")
+				return r
+			}(),
+			expectedExpressions: []string{
+				`--test\[test]:filename:_files -g "-\(/\)"`,
+				`--ptest\[ptest]:filename:_files -g "-\(/\)"`,
+			},
+		},
+	}
 
-				sub2 := &Command{Use: "sub2"}
-				r.AddCommand(sub2)
+	for _, tc := range tcs {
+		t.Run(tc.name, func(t *testing.T) {
+			if tc.skip != "" {
+				t.Skip(tc.skip)
+			}
+			tc.root.Root().SetArgs(tc.invocationArgs)
+			tc.root.Execute()
+			buf := new(bytes.Buffer)
+			if err := tc.root.GenZshCompletion(buf); err != nil {
+				t.Error(err)
+			}
+			output := buf.Bytes()
 
-				sub21 := &Command{Use: "sub21"}
-				sub22 := &Command{Use: "sub22"}
+			for _, expr := range tc.expectedExpressions {
+				rgx, err := regexp.Compile(expr)
+				if err != nil {
+					t.Errorf("error compiling expression (%s): %v", expr, err)
+				}
+				if !rgx.Match(output) {
+					t.Errorf("expected completion (%s) to match '%s'", buf.String(), expr)
+				}
+			}
+		})
+	}
+}
 
-				sub2.AddCommand(sub21)
-				sub2.AddCommand(sub22)
+func TestGenZshCompletionHidden(t *testing.T) {
+	tcs := []struct {
+		name                string
+		root                *Command
+		expectedExpressions []string
+	}{
+		{
+			name: "hidden commands",
+			root: func() *Command {
+				r := &Command{
+					Use:   "main",
+					Short: "main short description",
+				}
+				s1 := &Command{
+					Use:    "sub1",
+					Hidden: true,
+					Run:    emptyRun,
+				}
+				s2 := &Command{
+					Use:   "sub2",
+					Short: "short sub2 description",
+					Run:   emptyRun,
+				}
+				r.AddCommand(s1, s2)
 
 				return r
 			}(),
-			expectedExpressions: []string{"(sub11 sub12)", "(sub21 sub22)"},
+			expectedExpressions: []string{
+				"sub1",
+			},
+		},
+		{
+			name: "hidden flags",
+			root: func() *Command {
+				var hidden string
+				r := &Command{
+					Use:   "root",
+					Short: "root short description",
+					Run:   emptyRun,
+				}
+				r.Flags().StringVarP(&hidden, "hidden", "H", hidden, "hidden usage")
+				if err := r.Flags().MarkHidden("hidden"); err != nil {
+					t.Errorf("Error setting flag hidden: %v\n", err)
+				}
+				return r
+			}(),
+			expectedExpressions: []string{
+				"--hidden",
+			},
 		},
 	}
 
 	for _, tc := range tcs {
 		t.Run(tc.name, func(t *testing.T) {
+			tc.root.Execute()
 			buf := new(bytes.Buffer)
-			tc.root.GenZshCompletion(buf)
+			if err := tc.root.GenZshCompletion(buf); err != nil {
+				t.Error(err)
+			}
 			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)
+			for _, expr := range tc.expectedExpressions {
+				if strings.Contains(output, expr) {
+					t.Errorf("Expected completion (%s) not to contain '%s' but it does", output, expr)
 				}
 			}
 		})
 	}
 }
+
+func TestMarkZshCompPositionalArgumentFile(t *testing.T) {
+	t.Run("Doesn't allow overwriting existing positional argument", func(t *testing.T) {
+		c := &Command{}
+		if err := c.MarkZshCompPositionalArgumentFile(1, "*.log"); err != nil {
+			t.Errorf("Received error when we shouldn't have: %v\n", err)
+		}
+		if err := c.MarkZshCompPositionalArgumentFile(1); err == nil {
+			t.Error("Didn't receive an error when trying to overwrite argument position")
+		}
+	})
+
+	t.Run("Refuses to accept argPosition less then 1", func(t *testing.T) {
+		c := &Command{}
+		err := c.MarkZshCompPositionalArgumentFile(0, "*")
+		if err == nil {
+			t.Fatal("Error was not thrown when indicating argument position 0")
+		}
+		if !strings.Contains(err.Error(), "position") {
+			t.Errorf("expected error message '%s' to contain 'position'", err.Error())
+		}
+	})
+}
+
+func TestMarkZshCompPositionalArgumentWords(t *testing.T) {
+	t.Run("Doesn't allow overwriting existing positional argument", func(t *testing.T) {
+		c := &Command{}
+		if err := c.MarkZshCompPositionalArgumentFile(1, "*.log"); err != nil {
+			t.Errorf("Received error when we shouldn't have: %v\n", err)
+		}
+		if err := c.MarkZshCompPositionalArgumentWords(1, "hello"); err == nil {
+			t.Error("Didn't receive an error when trying to overwrite argument position")
+		}
+	})
+
+	t.Run("Doesn't allow calling without words", func(t *testing.T) {
+		c := &Command{}
+		if err := c.MarkZshCompPositionalArgumentWords(0); err == nil {
+			t.Error("Should not allow saving empty word list for annotation")
+		}
+	})
+
+	t.Run("Refuses to accept argPosition less then 1", func(t *testing.T) {
+		c := &Command{}
+		err := c.MarkZshCompPositionalArgumentWords(0, "word")
+		if err == nil {
+			t.Fatal("Should not allow setting argument position less then 1")
+		}
+		if !strings.Contains(err.Error(), "position") {
+			t.Errorf("Expected error '%s' to contain 'position' but didn't", err.Error())
+		}
+	})
+}
+
+func BenchmarkMediumSizeConstruct(b *testing.B) {
+	root := constructLargeCommandHierarchy()
+	// if err := root.GenZshCompletionFile("_mycmd"); err != nil {
+	// 	b.Error(err)
+	// }
+
+	for i := 0; i < b.N; i++ {
+		buf := new(bytes.Buffer)
+		err := root.GenZshCompletion(buf)
+		if err != nil {
+			b.Error(err)
+		}
+	}
+}
+
+func TestExtractFlags(t *testing.T) {
+	var debug, cmdc, cmdd bool
+	c := &Command{
+		Use:  "cmdC",
+		Long: "Command C",
+	}
+	c.PersistentFlags().BoolVarP(&debug, "debug", "d", debug, "debug mode")
+	c.Flags().BoolVar(&cmdc, "cmd-c", cmdc, "Command C")
+	d := &Command{
+		Use:  "CmdD",
+		Long: "Command D",
+	}
+	d.Flags().BoolVar(&cmdd, "cmd-d", cmdd, "Command D")
+	c.AddCommand(d)
+
+	resC := zshCompExtractFlag(c)
+	resD := zshCompExtractFlag(d)
+
+	if len(resC) != 2 {
+		t.Errorf("expected Command C to return 2 flags, got %d", len(resC))
+	}
+	if len(resD) != 2 {
+		t.Errorf("expected Command D to return 2 flags, got %d", len(resD))
+	}
+}
+
+func constructLargeCommandHierarchy() *Command {
+	var config, st1, st2 string
+	var long, debug bool
+	var in1, in2 int
+	var verbose []bool
+
+	r := genTestCommand("mycmd", false)
+	r.PersistentFlags().StringVarP(&config, "config", "c", config, "config usage")
+	if err := r.MarkPersistentFlagFilename("config", "*"); err != nil {
+		panic(err)
+	}
+	s1 := genTestCommand("sub1", true)
+	s1.Flags().BoolVar(&long, "long", long, "long description")
+	s1.Flags().BoolSliceVar(&verbose, "verbose", verbose, "verbose description")
+	s1.Flags().StringArray("option", []string{}, "various options")
+	s2 := genTestCommand("sub2", true)
+	s2.PersistentFlags().BoolVar(&debug, "debug", debug, "debug description")
+	s3 := genTestCommand("sub3", true)
+	s3.Hidden = true
+	s1_1 := genTestCommand("sub1sub1", true)
+	s1_1.Flags().StringVar(&st1, "st1", st1, "st1 description")
+	s1_1.Flags().StringVar(&st2, "st2", st2, "st2 description")
+	s1_2 := genTestCommand("sub1sub2", true)
+	s1_3 := genTestCommand("sub1sub3", true)
+	s1_3.Flags().IntVar(&in1, "int1", in1, "int1 description")
+	s1_3.Flags().IntVar(&in2, "int2", in2, "int2 description")
+	s1_3.Flags().StringArrayP("option", "O", []string{}, "more options")
+	s2_1 := genTestCommand("sub2sub1", true)
+	s2_2 := genTestCommand("sub2sub2", true)
+	s2_3 := genTestCommand("sub2sub3", true)
+	s2_4 := genTestCommand("sub2sub4", true)
+	s2_5 := genTestCommand("sub2sub5", true)
+
+	s1.AddCommand(s1_1, s1_2, s1_3)
+	s2.AddCommand(s2_1, s2_2, s2_3, s2_4, s2_5)
+	r.AddCommand(s1, s2, s3)
+	r.Execute()
+	return r
+}
+
+func genTestCommand(name string, withRun bool) *Command {
+	r := &Command{
+		Use:   name,
+		Short: name + " short description",
+		Long:  "Long description for " + name,
+	}
+	if withRun {
+		r.Run = emptyRun
+	}
+
+	return r
+}