Overview
Zulu is a library providing a simple interface to create powerful modern CLI interfaces similar to git & go tools.
Zulu provides:
- Easy subcommand-based CLIs:
app server
,app fetch
, etc. - Fully POSIX-compliant flags (including short & long versions).
- Nested subcommands.
- Global, local and cascading flags.
- Intelligent suggestions (
app srver
-> did you meanapp server
?). - Automatic help generation for commands and flags.
- Automatic help flag recognition of
-h
,--help
, etc. - Automatically generated shell autocomplete for your application (bash, zsh, fish, PowerShell).
- Automatically generated man pages for your application.
- Command aliases, so you can change things without breaking them.
- The flexibility to define your own help, usage, etc.
- Optional seamless integration with koanf for 12-factor apps.
Installation
Using Zulu is easy. Use go get
to install the latest version of the library.
Install the library and its dependencies.
$ go get -u github.com/zulucmd/zulu/v2
go: downloading github.com/zulucmd/zulu/v2 ...
Import Zulu in your application.
import "github.com/zulucmd/zulu/v2"
Differences with Cobra
Zulu is a fork of spf13/cobra. Notable differences between Cobra and Zulu are:
- Replaced spf13/pflag with zulucmd/zflag.
- Zulu has no support for Viper. Viper only works with spf13/pflag, which was forked into zflag, in use by zulu. Instead, you can use koanf-zflag package to utilise knadh/koanf.
- Removed all the
*Run
hooks, in favour of*RunE
hooks. This just simplifies things and avoids duplicated code. - Added hooks for
InitializeE
andFinalizeE
. - Added new
On*
hooks. - Added a new
CancelRun()
method. - Added an AsciiDoc generator.
- Added support for grouped commands.
- Removed the legacy bash completions.
- Improved support flags with optional values.
- Removed many old code that was there for backwards compatibility reasons.
Note the above list is not exhaustive, and many of the PRs were merged in from unclosed PRs in the Cobra repo. For a full list of changes see Git history.
Concepts
Zulu is built on a structure of commands, arguments & flags.
Commands represent actions, Args are things, and Flags are modifiers for those actions.
The best applications read like sentences when used, and as a result, users intuitively know how to interact with them.
The pattern to follow is
APPNAME VERB NOUN --ADJECTIVE.
or
APPNAME COMMAND ARG --FLAG
A few good real world examples may better illustrate this point.
In the following example, ‘server’ is a command, and ‘port’ is a flag.
$ hugo server --port=1313
hugo: downloading modules
In this command, we are telling Git to clone the URL bare.
$ git clone URL --bare
Cloning into bare repository '/tmp/project'...
Commands
Command is the central point of the application. Each interaction that the application supports will be contained in a Command. A command can have children commands and optionally run an action.
In the example above, ‘server’ is the command.
Flags
A flag is a way to modify the behaviour of a command. Zulu supports fully POSIX-compliant, flags as well as the Go flag package. A Zulu command can define flags that persist through to children commands and flags that are only available to that command.
In the example above, ‘port’ is the flag.
Flag functionality is provided by the zflag library, a fork of the great spf13/pflag library, which itself is a fork of the flag package from the standard library, and maintains the same interface while adding POSIX compliance.
Getting Started
While you are welcome organise your application how you see fit, typically a Zulu-based application will follow the following organisational structure:
▾ cmd/
▾ appName/
add.go
your.go
commands.go
here.go
main.go
In a Zulu app, typically the main.go
file is very bare. It serves one purpose: initializing Zulu. It can optionally include the root zulu.Command
definition.
package main
func main() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
Using the Zulu Library
To implement Zulu, you need to create a bare main.go
file and a rootCmd
file. You can optionally provide additional commands as you see fit.
Create root command
Zulu doesn’t require any special constructors. Simply create your commands.
// cmd/$app/main.go
package main
import "github.com/zulucmd/zulu/v2"
var rootCmd = &zulu.Command{
Use: "hugo",
Short: "Hugo is a very fast static site generator",
Long: `A Fast and Flexible Static Site Generator built with
love by spf13 and friends in Go.
Complete documentation is available at https://hugo.spf13.com`,
RunE: func(cmd *zulu.Command, args []string) error {
// Do Stuff Here
},
}
func main() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
You additionally define flags and handle configuration in your InitializeE()
or the init()
function.
Please note, init()
is generally seen as an antipattern within the Go community.
// cmd/$app/main.go
package main
import (
"fmt"
"os"
"github.com/zulucmd/zflag/v2"
"github.com/zulucmd/zulu/v2"
)
var (
// Used for flags.
userLicense string
rootCmd = &zulu.Command{
Use: "zulu",
Short: "A generator for Zulu based Applications",
Long: `Zulu is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Zulu application.`,
InitializeE: func(cmd *zulu.Command, args []string) error {
cmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.zulu.yaml)")
cmd.PersistentFlags().String("author", "YOUR NAME", "author name for copyright attribution", zflag.OptShorthand('a'))
cmd.PersistentFlags().StringVar(&userLicense, "license", "", "name of license for the project", zflag.OptShorthand('l'))
cmd.PersistentFlags().Bool("install", true, "install the application")
cmd.AddCommand(addCmd)
cmd.AddCommand(initCmd)
return nil
},
}
)
func main() error {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
Create additional commands
Additional commands can be defined, and each is typically assigned its own file within the cmd/
directory.
If you wanted to create a version command, you would create cmd/version.go
, and populate it accordingly.
package main
import (
"fmt"
"github.com/zulucmd/zulu/v2"
)
func init() {
rootCmd.AddCommand(versionCmd)
}
var versionCmd = &zulu.Command{
Use: "version",
Short: "Print the version number of Hugo",
Long: `All software has versions. This is Hugo's`,
RunE: func(cmd *zulu.Command, args []string) error {
fmt.Println("Hugo Static Site Generator v0.9 -- HEAD")
return nil
},
}
Working with Flags
Flags provide modifiers to control how the action command operates.
Assign flags to a command
Since the flags are defined and used in different locations, we need to define a variable outside with the correct scope to assign the flag to work with.
var Verbose bool
var Source string
There are two different approaches to assign a flag.
Persistent Flags
A flag can be ‘persistent’, meaning that this flag will be available to the command it’s assigned to as well as every command under that command. For global flags, assign a flag as a persistent flag on the root.
rootCmd.PersistentFlags().BoolVar(&Verbose, "verbose" false, "verbose output", zflag.OptShorthand('v'))
Local Flags
A flag can also be assigned locally, which will only apply to that specific command.
cmd.Flags().StringVar(&Source, "source", "", "Source directory to read from", zflag.OptShorthand('s'))
Local Flag on Parent Commands
By default, Zulu only parses local flags on the target command, and any local flags on parent commands are ignored. By enabling Command.TraverseChildren
, Zulu will parse local flags on each command before executing the target command.
command := &zulu.Command{
Use: "print [OPTIONS] [COMMANDS]",
TraverseChildren: true,
}
Required flags
Flags are optional by default. If instead you wish your command to report an error when a flag has not been set, mark it as required:
rootCmd.Flags().StringVar(&Region, "region", "", "AWS region", zflag.OptShorthand('r'), zulu.FlagOptRequired())
Flag Groups
If you have different flags that must be provided together (e.g. if they provide the --username
flag they MUST provide the --password
flag as well) then
Cobra can enforce that requirement:
rootCmd.Flags().StringVarP(&u, "username", "u", "", "Username (required if password is set)")
rootCmd.Flags().StringVarP(&pw, "password", "p", "", "Password (required if username is set)")
rootCmd.MarkFlagsRequiredTogether("username", "password")
You can also prevent different flags from being provided together if they represent mutually
exclusive options such as specifying an output format as either --json
or --yaml
but never both:
rootCmd.Flags().BoolVar(&u, "json", false, "Output in JSON")
rootCmd.Flags().BoolVar(&pw, "yaml", false, "Output in YAML")
rootCmd.MarkFlagsMutuallyExclusive("json", "yaml")
In both of these cases:
- both local and persistent flags can be used
- NOTE: the group is only enforced on commands where every flag is defined
- a flag may appear in multiple groups
- a group may contain any number of flags
Positional and Custom Arguments
Validation of positional arguments can be specified using the Args
field of Command
. The following validators are built in:
NoArgs
- report an error if there are any positional args.ArbitraryArgs
- accept any number of args.MinimumNArgs(int)
- report an error if less than N positional args are provided.MaximumNArgs(int)
- report an error if more than N positional args are provided.ExactArgs(int)
- report an error if there are not exactly N positional args.RangeArgs(min, max)
- report an error if the number of args is not betweenmin
andmax
.MatchAll(pargs ...PositionalArgs)
- enables combining existing checks with arbitrary other checks (e.g. you want to check the ExactArgs length along with other qualities).
If Args
is undefined or nil
, it defaults to ArbitraryArgs
.
Field ValidArgs
of type []string
can be defined in Command
, in order to report an error if there are any positional args that are not in the list. This validation is executed implicitly before the validator defined in Args
.
It is possible to set any custom validator that satisfies func(cmd *zulu.Command, args []string) error
.
var cmd = &zulu.Command{
Short: "hello",
Args: func(cmd *zulu.Command, args []string) error {
// Optionally run one of the validators provided by zulu
if err := zulu.MinimumNArgs(1)(cmd, args); err != nil {
return err
}
// Run the custom validation logic
if myapp.IsValidColor(args[0]) {
return nil
}
return fmt.Errorf("invalid color specified: %s", args[0])
},
RunE: func(cmd *zulu.Command, args []string) error {
fmt.Println("Hello, World!")
return nil
},
}
Example
In the example below, we have defined three commands. Two are at the top level and one (cmdTimes
) is a child of one of the top commands. In this case the root is not executable, meaning that a subcommand is required. This is accomplished by not providing a RunE
for the rootCmd
.
We have only defined one flag for a single command.
More documentation about flags is available here.
package main
import (
"fmt"
"strings"
"github.com/zulucmd/zflag/v2"
"github.com/zulucmd/zulu/v2"
)
func main() {
var echoTimes int
var cmdPrint = &zulu.Command{
Use: "print [string to print]",
Short: "Print anything to the screen",
Long: `print is for printing anything back to the screen.
For many years people have printed back to the screen.`,
Args: zulu.MinimumNArgs(1),
RunE: func(cmd *zulu.Command, args []string) error {
fmt.Println("Print: " + strings.Join(args, " "))
return nil
},
}
var cmdEcho = &zulu.Command{
Use: "echo [string to echo]",
Short: "Echo anything to the screen",
Long: `echo is for echoing anything back.
Echo works a lot like print, except it has a child command.`,
Args: zulu.MinimumNArgs(1),
RunE: func(cmd *zulu.Command, args []string) error {
fmt.Println("Echo: " + strings.Join(args, " "))
return nil
},
}
var cmdTimes = &zulu.Command{
Use: "times [string to echo]",
Short: "Echo anything to the screen more times",
Long: `echo things multiple times back to the user by providing
a count and a string.`,
Args: zulu.MinimumNArgs(1),
RunE: func(cmd *zulu.Command, args []string) error {
for i := 0; i < echoTimes; i++ {
fmt.Println("Echo: " + strings.Join(args, " "))
}
return nil
},
}
cmdTimes.Flags().IntVar(&echoTimes, "times", 1, "times to echo the input", zflag.OptShorthand('t'))
var rootCmd = &zulu.Command{Use: "app"}
rootCmd.AddCommand(cmdPrint, cmdEcho)
cmdEcho.AddCommand(cmdTimes)
rootCmd.Execute()
}
Help Command
Zulu automatically adds a help command to your application when you have subcommands. This will be called when a user runs app help
. Additionally, help will also support all other commands as input. Say, for instance, you have a command called create
without any additional configuration; Zulu will work when app help create
is called. Every command will automatically have the --help
flag added.
Example
The following output is automatically generated by Zulu. Nothing beyond the command and flag definitions are needed.
$ zulu help
Zulu is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Zulu application.
Usage:
zulu [command]
Available Commands:
add Add a command to a Zulu Application
help Help about any command
init Initialize a Zulu Application
Flags:
-a, --author string author name for copyright attribution (default "YOUR NAME")
--config string config file (default is $HOME/.zulu.yaml)
-h, --help help for zulu
-l, --license string name of license for the project
Use "zulu [command] --help" for more information about a command.
Help is just a command like any other. There is no special logic or behavior around it. In fact, you can provide your own if you want.
Grouping commands in help
Zulu supports grouping of available commands. Groups can either be explicitly defined by AddGroup
and set by the Group
element of a subcommand. If Groups are not explicitly defined they are implicitly defined.
Defining your own help
You can provide your own Help command or your own template for the default command to use with following functions:
cmd.SetHelpCommand(cmd *Command)
cmd.SetHelpFunc(f func(*Command, []string))
cmd.SetHelpTemplate(s string)
The latter two will also apply to any children commands.
Usage Message
When the user provides an invalid flag or invalid command, Zulu responds by showing the user the ‘usage’.
Example
You may recognize this from the help above. That’s because the default help embeds the usage as part of its output.
$ zulu --invalid
Error: unknown flag: --invalid
Usage:
zulu [command]
Available Commands:
add Add a command to a Zulu Application
help Help about any command
init Initialize a Zulu Application
Flags:
-a, --author string author name for copyright attribution (default "YOUR NAME")
--config string config file (default is $HOME/.zulu.yaml)
-h, --help help for zulu
-l, --license string name of license for the project
Use "zulu [command] --help" for more information about a command.
Defining your own usage
You can provide your own usage function or template for Zulu to use. Like help, the function and template are overridable through public methods:
cmd.SetUsageFunc(f func(*Command) error)
cmd.SetUsageTemplate(s string)
Version Flag
Zulu adds a top-level --version
flag if the Version field is set on the root command. Running an application with the --version
flag will print the version to stdout using the version template. The template can be customized using the cmd.SetVersionTemplate(s string)
function.
PreRun and PostRun Hooks
It is possible to run functions before or after the main RunE
function of your command. The PersistentPreRunE
and PreRunE
functions will be executed before RunE
. PersistentPostRunE
and PostRunE
will be executed after RunE
. An InitializeE
will run prior to attempting to parse any flags. A FinalizeE
runs at the end very regardless at all times, even any of the *RunE
produce an error. The Persistent*RunE
functions will be inherited by children.
These functions are run in the following order.
PersistentInitializeE
InitializeE
PersistentPreRunE
PreRunE
RunE
PostRunE
PersistentPostRunE
FinalizeE
PersistentFinalizeE
An example of two commands which use all of these features is below.
package zulu_test
import (
"fmt"
"github.com/zulucmd/zulu/v2"
)
func ExampleHookFuncE() {
var rootCmd = &zulu.Command{
Use: "root [sub]",
Short: "My root command",
PersistentInitializeE: func(cmd *zulu.Command, args []string) error {
fmt.Printf("Inside rootCmd PersistentInitializeE with args: %v\n", args)
return nil
},
InitializeE: func(cmd *zulu.Command, args []string) error {
fmt.Printf("Inside rootCmd InitializeE with args: %v\n", args)
return nil
},
PersistentPreRunE: func(cmd *zulu.Command, args []string) error {
fmt.Printf("Inside rootCmd PersistentPreRunE with args: %v\n", args)
return nil
},
PreRunE: func(cmd *zulu.Command, args []string) error {
fmt.Printf("Inside rootCmd PreRunE with args: %v\n", args)
return nil
},
RunE: func(cmd *zulu.Command, args []string) error {
fmt.Printf("Inside rootCmd RunE with args: %v\n", args)
return nil
},
PostRunE: func(cmd *zulu.Command, args []string) error {
fmt.Printf("Inside rootCmd PostRunE with args: %v\n", args)
return nil
},
PersistentPostRunE: func(cmd *zulu.Command, args []string) error {
fmt.Printf("Inside rootCmd PersistentPostRunE with args: %v\n", args)
return nil
},
FinalizeE: func(cmd *zulu.Command, args []string) error {
fmt.Printf("Inside rootCmd FinalizeE with args: %v\n", args)
return nil
},
PersistentFinalizeE: func(cmd *zulu.Command, args []string) error {
fmt.Printf("Inside rootCmd PersistentFinalizeE with args: %v\n", args)
return nil
},
}
var subCmd = &zulu.Command{
Use: "sub [no options!]",
Short: "My subcommand",
PersistentInitializeE: func(cmd *zulu.Command, args []string) error {
fmt.Printf("Inside subCmd PersistentInitializeE with args: %v\n", args)
return nil
},
InitializeE: func(cmd *zulu.Command, args []string) error {
fmt.Printf("Inside subCmd InitializeE with args: %v\n", args)
return nil
},
PreRunE: func(cmd *zulu.Command, args []string) error {
fmt.Printf("Inside subCmd PreRunE with args: %v\n", args)
return nil
},
RunE: func(cmd *zulu.Command, args []string) error {
fmt.Printf("Inside subCmd RunE with args: %v\n", args)
return nil
},
PostRunE: func(cmd *zulu.Command, args []string) error {
fmt.Printf("Inside subCmd PostRunE with args: %v\n", args)
return nil
},
PersistentPostRunE: func(cmd *zulu.Command, args []string) error {
fmt.Printf("Inside subCmd PersistentPostRunE with args: %v\n", args)
return nil
},
FinalizeE: func(cmd *zulu.Command, args []string) error {
fmt.Printf("Inside subCmd FinalizeE with args: %v\n", args)
return nil
},
PersistentFinalizeE: func(cmd *zulu.Command, args []string) error {
fmt.Printf("Inside subCmd PersistentFinalizeE with args: %v\n", args)
return nil
},
}
rootCmd.AddCommand(subCmd)
rootCmd.SetArgs([]string{""})
_ = rootCmd.Execute()
fmt.Println()
rootCmd.SetArgs([]string{"sub", "arg1", "arg2"})
_ = rootCmd.Execute()
// Output:
// Inside rootCmd PersistentInitializeE with args: []
// Inside rootCmd InitializeE with args: []
// Inside rootCmd PersistentPreRunE with args: []
// Inside rootCmd PreRunE with args: []
// Inside rootCmd RunE with args: []
// Inside rootCmd PostRunE with args: []
// Inside rootCmd PersistentPostRunE with args: []
// Inside rootCmd FinalizeE with args: []
// Inside rootCmd PersistentFinalizeE with args: []
//
// Inside subCmd PersistentInitializeE with args: []
// Inside rootCmd PersistentInitializeE with args: []
// Inside subCmd InitializeE with args: []
// Inside rootCmd PersistentPreRunE with args: [arg1 arg2]
// Inside subCmd PreRunE with args: [arg1 arg2]
// Inside subCmd RunE with args: [arg1 arg2]
// Inside subCmd PostRunE with args: [arg1 arg2]
// Inside subCmd PersistentPostRunE with args: [arg1 arg2]
// Inside rootCmd PersistentPostRunE with args: [arg1 arg2]
// Inside subCmd FinalizeE with args: [arg1 arg2]
// Inside subCmd PersistentFinalizeE with args: [arg1 arg2]
// Inside rootCmd PersistentFinalizeE with args: [arg1 arg2]
}
Suggestions when “unknown command” happens
Zulu will print automatic suggestions when “unknown command” errors happen. This allows Zulu to behave similarly to the git
command when a typo happens. For example:
$ hugo srever
Error: unknown command "srever" for "hugo"
Did you mean this?
server
Run 'hugo --help' for usage.
Suggestions are automatic based on every subcommand registered and use an implementation of Levenshtein distance. Every registered command that matches a minimum distance of 2 (ignoring case) will be displayed as a suggestion.
If you need to disable suggestions or tweak the string distance in your command, use:
command.DisableSuggestions = true
or
command.SuggestionsMinimumDistance = 1
You can also explicitly set names for which a given command will be suggested using the SuggestFor
attribute. This allows suggestions for strings that are not close in terms of string distance, but makes sense in your set of commands and for some which you don’t want aliases. Example:
$ kubectl remove
Error: unknown command "remove" for "kubectl"
Did you mean this?
delete
Run 'kubectl help' for usage.
Shell completions
Zulu can generate shell completions for multiple shells. The currently supported shells are:
- Bash
- Zsh
- fish
- PowerShell
Zulu will automatically provide your program with a fully functional completion
sub-command,
similarly to how it provides the help
command.
Zulu’s completion scripts provide the following features:
- Supports completion descriptions (like the other shells).
- Small completion script of less than 300 lines.
- Streamlined user experience thanks to a completion behavior aligned with the other shells.
Default completion sub-command
Zulu provides a few options for the default completion
command. To configure such options you must set
the CompletionOptions
field on the root command.
Please look at the documentation for the CompletionOptions
struct to see what can be configured.
Customizing completions
The generated completion scripts will automatically handle completing commands and flags. However, you can make your completions much more powerful by providing information to complete your program’s nouns and flag values.
Completion of nouns
Static completion of nouns
Zulu allows you to provide a pre-defined list of completion choices for your nouns using the ValidArgs
field.
For example, if you want kubectl get [tab][tab]
to show a list of valid “nouns” you have to set them.
Some simplified code from kubectl get
looks like:
validArgs []string = { "pod", "node", "service", "replicationcontroller" }
cmd := &zulu.Command{
Use: "get [(-o|--output=)json|yaml|template|...] (RESOURCE [NAME] | RESOURCE/NAME ...)",
Short: "Display one or many resources",
Long: get_long,
Example: get_example,
RunE: func(cmd *zulu.Command, args []string) error {
if err := RunGet(f, out, cmd, args); err != nil {
panic(err)
}
return nil
},
ValidArgs: validArgs,
}
Notice we put the ValidArgs
field on the get
sub-command. Doing so will give results like:
$ kubectl get [tab][tab]
node pod replicationcontroller service
Aliases for nouns
If your nouns have aliases, you can define them alongside ValidArgs
using ArgAliases
:
argAliases []string = { "pods", "nodes", "services", "svc", "replicationcontrollers", "rc" }
cmd := &zulu.Command{
...
ValidArgs: validArgs,
ArgAliases: argAliases
}
The aliases are not shown to the user on tab completion, but they are accepted as valid nouns by the completion algorithm if entered manually, e.g. in:
$ kubectl get rc [tab][tab]
backend frontend database
Note that without declaring rc
as an alias, the completion algorithm would not know to show the list of
replication controllers following rc
.
Dynamic completion of nouns
In some cases it is not possible to provide a list of completions in advance. Instead, the list of completions must be determined at execution-time. In a similar fashion as for static completions, you can use the ValidArgsFunction
field to provide a Go function that Zulu will execute when it needs the list of completion choices for the nouns of a command. Note that either ValidArgs
or ValidArgsFunction
can be used for a single zulu command, but not both.
Simplified code from helm status
looks like:
cmd := &zulu.Command{
Use: "status RELEASE_NAME",
Short: "Display the status of the named release",
Long: status_long,
RunE: func(cmd *zulu.Command, args []string) error {
RunStatus(args[0])
return nil
},
ValidArgsFunction: func(cmd *zulu.Command, args []string, toComplete string) ([]string, zulu.ShellCompDirective) {
if len(args) != 0 {
return nil, zulu.ShellCompDirectiveNoFileComp
}
return getReleasesFromCluster(toComplete), zulu.ShellCompDirectiveNoFileComp
},
}
Where getReleasesFromCluster()
is a Go function that returns the list of current Helm release names running on the Kubernetes cluster.
Similarly as for RunE
, the args
parameter represents the arguments present on the command-line, while the toComplete
parameter represents the final argument which the user is trying to complete (e.g., helm status th<TAB>
will have toComplete
be "th"
); the toComplete
parameter will be empty when the user has requested completions right after typing a space (e.g., helm status <TAB>
). Notice we put the ValidArgsFunction
on the status
sub-command, as it provides completions for this sub-command specifically. Let’s assume the Helm releases on the cluster are: harbor
, notary
, rook
and thanos
then this dynamic completion will give results like:
$ helm status [tab][tab]
harbor notary rook thanos
You may have noticed the use of zulu.ShellCompDirective
. These directives are bit fields allowing to control some shell completion behaviors for your particular completion. You can combine them with the bit-or operator such as zulu.ShellCompDirectiveNoSpace | zulu.ShellCompDirectiveNoFileComp
// ShellCompDirectiveError indicates an error occurred and completions should be ignored.
ShellCompDirectiveError
// ShellCompDirectiveNoSpace indicates that the shell should not add a space
// after the completion even if there is a single completion provided.
ShellCompDirectiveNoSpace
// ShellCompDirectiveNoFileComp indicates that the shell should not provide
// file completion even when no completion is provided.
ShellCompDirectiveNoFileComp
// ShellCompDirectiveFilterFileExt indicates that the provided completions
// should be used as file extension filters.
// For example, to complete only files of the form *.json or *.yaml:
// return []string{"yaml", "json"}, ShellCompDirectiveFilterFileExt
// The BashCompFilenameExt annotation can also be used to obtain
// the same behavior for flags. For flags, using FlagOptFilename() is a shortcut
// to using this directive explicitly.
ShellCompDirectiveFilterFileExt
// ShellCompDirectiveFilterDirs indicates that only directory names should
// be provided in file completion.
// For example:
// return nil, ShellCompDirectiveFilterDirs
// To request directory names within another directory, the returned completions
// should specify a single directory name within which to search. For example,
// to complete directories within "themes/":
// return []string{"themes"}, ShellCompDirectiveFilterDirs
// The BashCompSubdirsInDir annotation can be used to
// obtain the same behavior but only for flags. The function FlagOptDirname
// zflag option has been provided as a convenience.
ShellCompDirectiveFilterDirs
// ShellCompDirectiveKeepOrder indicates that the shell should preserve the order
// in which the completions are provided.
ShellCompDirectiveKeepOrder
// ShellCompDirectiveDefault indicates to let the shell perform its default
// behavior after completions have been provided.
// This one must be last to avoid messing up the iota count.
ShellCompDirectiveDefault
Note: When using the ValidArgsFunction
, Zulu will call your registered function after having parsed all flags and arguments provided in the command-line. You therefore don’t need to do this parsing yourself. For example, when a user calls helm status --namespace my-rook-ns [tab][tab]
, Zulu will call your registered ValidArgsFunction
after having parsed the --namespace
flag, as it would have done when calling the RunE
function.
Debugging completion
Zulu achieves dynamic completion through the use of a hidden command called by the completion script. To debug your Go completion code, you can call this hidden command directly:
$ helm __complete status har<ENTER>
harbor
:4
Completion ended with directive: ShellCompDirectiveNoFileComp # This is on stderr
Important: If the noun to complete is empty (when the user has not yet typed any letters of that noun), you must pass an empty parameter to the __complete
command:
$ helm __complete status ""<ENTER>
harbor
notary
rook
thanos
:4
Completion ended with directive: ShellCompDirectiveNoFileComp # This is on stderr
Calling the __complete
command directly allows you to run the Go debugger to troubleshoot your code. You can also add printouts to your code; Zulu provides the following functions to use for printouts in Go completion code:
// Prints to the completion script debug file (if BASH_COMP_DEBUG_FILE
// is set to a file path) and optionally prints to stderr.
zulu.CompDebug(msg string, printToStdErr bool) {
zulu.CompDebugln(msg string, printToStdErr bool)
// Prints to the completion script debug file (if BASH_COMP_DEBUG_FILE
// is set to a file path) and to stderr.
zulu.CompError(msg string)
zulu.CompErrorln(msg string)
Important: You should not leave traces that print directly to stdout in your completion code as they will be interpreted as completion choices by the completion script. Instead, use the zulu-provided debugging traces functions mentioned above.
Completions for flags
Mark flags as required
Most of the time completions will only show sub-commands. But if a flag is required to make a sub-command work, you probably want it to show up when the user types [tab][tab]. You can mark a flag as ‘Required’ using the zulu.FlagOptRequired()
option.
flagSet.String("pod", "", "pod usage", zulu.FlagOptRequired())
flagSet.String("container", "", "container usage", zulu.FlagOptRequired())
and you’ll get something like
$ kubectl exec [tab][tab]
-c --container= -p --pod=
Specify dynamic flag completion
As for nouns, Zulu provides a way of defining dynamic completion of flags. To provide a Go function that Zulu will execute when it needs the list of completion choices for a flag, you must register the function using the command.RegisterFlagCompletionFunc()
function.
flagName := "output"
cmd.RegisterFlagCompletionFunc(flagName, func(cmd *zulu.Command, args []string, toComplete string) ([]string, zulu.ShellCompDirective) {
return []string{"json", "table", "yaml"}, zulu.ShellCompDirectiveDefault
})
Notice that calling RegisterFlagCompletionFunc()
is done through the command
with which the flag is associated. In our example this dynamic completion will give results like so:
$ helm status --output [tab][tab]
json table yaml
Debugging
You can also easily debug your Go completion code for flags:
$ helm __complete status --output ""
json
table
yaml
:4
Completion ended with directive: ShellCompDirectiveNoFileComp # This is on stderr
Important: You should not leave traces that print to stdout in your completion code as they will be interpreted as completion choices by the completion script. Instead, use the zulu-provided debugging traces functions mentioned further above.
Specify valid filename extensions for flags that take a filename
To limit completions of flag values to file names with certain extensions you can either use the zulu.FlagOptFilename()
function or a combination of RegisterFlagCompletionFunc()
and ShellCompDirectiveFilterFileExt
, like so:
flagSet.String("output", "", "output usage", zulu.FlagOptFilename("yaml", "json"))
or
flagName := "output"
cmd.RegisterFlagCompletionFunc(flagName, func(cmd *zulu.Command, args []string, toComplete string) ([]string, zulu.ShellCompDirective) {
return []string{"yaml", "json"}, ShellCompDirectiveFilterFileExt})
Limit flag completions to directory names
To limit completions of flag values to directory names you can either use the zulu.FlagOptDirname()
functions or a combination of RegisterFlagCompletionFunc()
and ShellCompDirectiveFilterDirs
, like so:
flagSet.String("output", "", "output usage", zulu.FlagOptDirname())
or
flagName := "output"
cmd.RegisterFlagCompletionFunc(flagName, func(cmd *zulu.Command, args []string, toComplete string) ([]string, zulu.ShellCompDirective) {
return nil, zulu.ShellCompDirectiveFilterDirs
})
To limit completions of flag values to directory names within another directory you can use a combination of RegisterFlagCompletionFunc()
and ShellCompDirectiveFilterDirs
like so:
flagName := "output"
cmd.RegisterFlagCompletionFunc(flagName, func(cmd *zulu.Command, args []string, toComplete string) ([]string, zulu.ShellCompDirective) {
return []string{"themes"}, zulu.ShellCompDirectiveFilterDirs
})
Descriptions for completions
Zulu provides support for completion descriptions. Such descriptions are supported for each shell. For commands and flags, Zulu will provide the descriptions automatically, based on usage information. For example, using zsh:
$ helm s[tab]
search -- search for a keyword in charts
show -- show information of a chart
status -- displays the status of the named release
while using fish:
$ helm s[tab]
search (search for a keyword in charts) show (show information of a chart) status (displays the status of the named release)
Zulu allows you to add descriptions to your own completions. Simply add the description text after each completion, following a \t
separator. This technique applies to completions returned by ValidArgs
, ValidArgsFunction
and RegisterFlagCompletionFunc()
. For example:
ValidArgsFunction: func(cmd *zulu.Command, args []string, toComplete string) ([]string, zulu.ShellCompDirective) {
return []string{"harbor\tAn image registry", "thanos\tLong-term metrics"}, zulu.ShellCompDirectiveNoFileComp
}}
or
ValidArgs: []string{"bash\tCompletions for bash", "zsh\tCompletions for zsh"}
Bash
Bash completion can be used by calling command.GenBashCompletion()
or command.GenBashCompletionFile()
.
It supports descriptions for completions. When calling the functions you must provide it with a parameter indicating if the completions should be annotated with a description; Zulu
will provide the description automatically based on usage information. You can choose to make this option configurable by your users.
# With descriptions
$ helm s[tab][tab]
search (search for a keyword in charts) status (display the status of the named release)
show (show information of a chart)
# Without descriptions
$ helm s[tab][tab]
search show status
Dependencies
The bash completion script generated by Zulu requires the bash_completion
package. You should update the help text of your completion command to show how to install the bash_completion
package (Kubectl docs)
Aliases
You can also configure bash
aliases for your program and they will also support completions.
alias aliasname=origcommand
complete -o default -F __start_origcommand aliasname
# and now when you run `aliasname` completion will make
# suggestions as it did for `origcommand`.
$ aliasname <tab><tab>
completion firstcommand secondcommand
Zsh
Zsh completion can be used by calling command.GenZshCompletion()
or command.GenZshCompletionFile()
.
It supports descriptions for completions. When calling the functions you must provide it with a parameter indicating if the completions should be annotated with a description; Zulu
will provide the description automatically based on usage information. You can choose to make this option configurable by your users.
The generated completion script should be put somewhere in your $fpath
and be named
_<yourProgram>
. You will need to start a new shell for the completions to become available.
# With descriptions
$ helm s[tab]
search -- search for a keyword in charts
show -- show information of a chart
status -- displays the status of the named release
# Without descriptions
$ helm s[tab]
search show status
Fish
Fish completion can be used by calling command.GenFishCompletion()
or command.GenFishCompletionFile()
.
It supports descriptions for completions. When calling the functions you must provide it with a parameter indicating if the completions should be annotated with a description; Zulu
will provide the description automatically based on usage information. You can choose to make this option configurable by your users.
# With descriptions
$ helm s[tab]
search (search for a keyword in charts) show (show information of a chart) status (displays the status of the named release)
# Without descriptions
$ helm s[tab]
search show status
Note: Because of backward-compatibility requirements, we were forced to have a different API to disable completion descriptions between zsh
and fish
.
Limitations
- The following flag completion annotations are not supported and will be ignored for
fish
:BashCompFilenameExt
(filtering by file extension)BashCompSubdirsInDir
(filtering by directory)
- The functions corresponding to the above annotations are consequently not supported and will be ignored for
fish
:FlagOptFilename()
(filtering by file extension)FlagOptDirname()
(filtering by directory)
- Similarly, the following completion directives are not supported and will be ignored for
fish
:ShellCompDirectiveFilterFileExt
(filtering by file extension)ShellCompDirectiveFilterDirs
(filtering by directory)
PowerShell
PowerShell
completion can be used by calling the command.GenPowerShellCompletion()
or command.GenPowerShellCompletionFile()
functions.
It supports descriptions for completions. When calling the functions you must provide it with a parameter indicating if the completions should be annotated with a description; Zulu
will provide the description automatically based on usage information. You can choose to make this option configurable by your users.
The script is designed to support all three PowerShell completion modes:
- TabCompleteNext (default windows style - on each key press the next option is displayed)
- Complete (works like bash)
- MenuComplete (works like zsh)
You set the mode with Set-PSReadLineKeyHandler -Key Tab -Function <mode>
. Descriptions are only displayed when using the Complete
or MenuComplete
mode.
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.
# With descriptions and Mode 'Complete'
$ helm s[tab]
search (search for a keyword in charts) show (show information of a chart) status (displays the status of the named release)
# With descriptions and Mode 'MenuComplete' The description of the current selected value will be displayed below the suggestions.
$ helm s[tab]
search show status
search for a keyword in charts
# Without descriptions
$ helm s[tab]
search show status
Aliases
You can also configure powershell
aliases for your program, and they will also support completions.
$ sal aliasname origcommand
$ Register-ArgumentCompleter -CommandName 'aliasname' -ScriptBlock $__origcommandCompleterBlock
# and now when you run `aliasname` completion will make
# suggestions as it did for `origcommand`.
$ aliasname <tab>
completion firstcommand secondcommand
The name of the completer block variable is of the form $__<programName>CompleterBlock
where every -
and :
in the program name have been replaced with _
, to respect powershell naming syntax.
Limitations
- The following flag completion annotations are not supported and will be ignored for
powershell
:BashCompFilenameExt
(filtering by file extension)BashCompSubdirsInDir
(filtering by directory)
- The functions corresponding to the above annotations are consequently not supported and will be ignored for
powershell
:FlagOptFilename()
(filtering by file extension)FlagOptDirname()
(filtering by directory)
- Similarly, the following completion directives are not supported and will be ignored for
powershell
:ShellCompDirectiveFilterFileExt
(filtering by file extension)ShellCompDirectiveFilterDirs
(filtering by directory)
Documentation generation
Zulu supports generating different types of documentation. This section explain how to generate man pages, markdown, and others.
Options
DisableAutoGenTag
. You may setcmd.DisableAutoGenTag = true
to entirely remove the auto generated string “Auto generated by zulucmd/zulu…” from any documentation source.
Man Pages
Generating man pages from a zulu command is incredibly easy. An example is as follows:
package main
import (
"log"
"github.com/zulucmd/zulu/v2"
"github.com/zulucmd/zulu/v2/doc"
)
func main() {
cmd := &zulu.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
Markdown Docs
Generating Markdown pages from a zulu command is incredibly easy. An example is as follows:
package main
import (
"log"
"github.com/zulucmd/zulu/v2"
"github.com/zulucmd/zulu/v2/doc"
)
func main() {
cmd := &zulu.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
For the entire command tree
This program can actually generate docs for the kubectl command in the kubernetes project
package main
import (
"log"
"io"
"os"
"k8s.io/kubernetes/pkg/kubectl/cmd"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"github.com/zulucmd/zulu/v2/doc"
)
func main() {
kubectl := cmd.NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, io.Discard, io.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 “./”)
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
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:
func GenMarkdownTreeCustom(cmd *Command, dir string, filePrepender, linkHandler func(string) string) error {
//...
}
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:
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:
linkHandler := func(name string) string {
base := strings.TrimSuffix(name, path.Ext(name))
return "/commands/" + strings.ToLower(base) + "/"
}
ReStructured Text Docs
Generating ReST pages from a zulu command is incredibly easy. An example is as follows:
package main
import (
"log"
"github.com/zulucmd/zulu/v2"
"github.com/zulucmd/zulu/v2/doc"
)
func main() {
cmd := &zulu.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
For the entire command tree
This program can actually generate docs for the kubectl command in the kubernetes project
package main
import (
"log"
"io"
"os"
"k8s.io/kubernetes/pkg/kubectl/cmd"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"github.com/zulucmd/zulu/v2/doc"
)
func main() {
kubectl := cmd.NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, io.Discard, io.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 “./”)
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
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:
func GenReSTTreeCustom(cmd *Command, dir string, filePrepender func(string) string, linkHandler func(string, string) string) error {
//...
}
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:
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:
// Sphinx cross-referencing format
linkHandler := func(name, ref string) string {
return fmt.Sprintf(":ref:`%s <%s>`", name, ref)
}
Yaml Docs
Generating yaml files from a zulu command is incredibly easy. An example is as follows:
package main
import (
"log"
"github.com/zulucmd/zulu/v2"
"github.com/zulucmd/zulu/v2/doc"
)
func main() {
cmd := &zulu.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
For the entire command tree
This program can actually generate docs for the kubectl command in the kubernetes project
package main
import (
"io"
"log"
"os"
"k8s.io/kubernetes/pkg/kubectl/cmd"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"github.com/zulucmd/zulu/v2/doc"
)
func main() {
kubectl := cmd.NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, io.Discard, io.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 “./”)
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
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:
func GenYamlTreeCustom(cmd *Command, dir string, filePrepender, linkHandler func(string) string) error {
//...
}
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:
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:
linkHandler := func(name string) string {
base := strings.TrimSuffix(name, path.Ext(name))
return "/commands/" + strings.ToLower(base) + "/"
}
AsciiDoc Docs
Generating AsciiDoc pages from a zulu command is incredibly easy. An example is as follows:
package main
import (
"log"
"github.com/zulucmd/zulu/v2"
"github.com/zulucmd/zulu/v2/doc"
)
func main() {
cmd := &zulu.Command{
Use: "test",
Short: "my test program",
}
err := doc.GenAsciidocTree(cmd, "/tmp")
if err != nil {
log.Fatal(err)
}
}
That will get you a AsciiDoc document /tmp/test.adoc
Generate for the entire command tree
This program can actually generate docs for the kubectl command in the kubernetes project
package main
import (
"log"
"io"
"os"
"k8s.io/kubernetes/pkg/kubectl/cmd"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"github.com/zulucmd/zulu/v2/doc"
)
func main() {
kubectl := cmd.NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, io.Discard, io.Discard)
err := doc.GenAsciidocTree(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 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 GenAsciidoc
instead of GenAsciidocTree
out := new(bytes.Buffer)
err := doc.GenAsciidoc(cmd, out)
if err != nil {
log.Fatal(err)
}
This will write the AsciiDoc doc for ONLY “cmd” into the out, buffer.
Customize the output
Both GenAsciidoc
and GenAsciidocTree
have alternate versions with callbacks to get some control of the output:
func GenAsciidocTreeCustom(cmd *Command, dir string, filePrepender, linkHandler func(string) string) error {
//...
}
func GenAsciidocCustom(cmd *Command, out *bytes.Buffer, linkHandler func(string) string) error {
//...
}
The filePrepender
will prepend the return value given the full filepath to the rendered Asciidoc file. A common use case is to add front matter to use the generated documentation with Hugo:
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:
linkHandler := func(name string) string {
base := strings.TrimSuffix(name, path.Ext(name))
return "/commands/" + strings.ToLower(base) + "/"
}
Contributing to Zulu
Thank you so much for contributing to Zulu. We appreciate your time and help. Here are some guidelines to help you get started.
Code of Conduct
Be kind and respectful to the members of the community. Take time to educate others who are seeking help. Harassment of any kind will not be tolerated.
Questions
If you have questions regarding Zulu, feel free to raise a ticket to ask a question.
Filing a bug or feature
- Before filing an issue, please check the existing issues to see if a similar one was already opened. If there is one already opened, feel free to comment on it.
- If you believe you’ve found a bug, please provide detailed steps of reproduction, the version of Zulu and anything else you believe will be useful to help troubleshoot it (e.g. OS environment, environment variables, etc.). Also state the current behaviour vs. the expected behaviour.
- If you’d like to see a feature or an enhancement, please open an issue with a clear title and description of what the feature is and why it would be beneficial to the project and its users.
Submitting changes
- CLA: Upon submitting a Pull Request (PR), contributors will be prompted to sign a CLA. Please sign the CLA :slightly_smiling_face:
- You should use
pre-commit
which will ensure all follow required formatting. - Tests: If you are submitting code, please ensure you have adequate tests
for the feature. Tests can be run via
go test ./...
ormake test
. - Ensure the new code is properly formatted to ensure code consistency.
Run
make all
.
Quick steps to contribute
- Fork the project.
- Clone your fork to your PC (
git clone https://github.com/your_username/zulu && cd zulu
) - Create your feature branch (
git checkout -b my-new-feature
) - Make changes and run tests (
make test
) - Add them to staging (
git add .
) - Commit your changes (
git commit -m 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new pull request
Changelog
No releases yet.
Conduct
Versioning
Zulu will follow a steady release cadence. Non-breaking changes will be released as minor versions quarterly. Patch bug releases are at the discretion of the maintainers. Users can expect security patch fixes to be released within relatively short order of a CVE becoming known. For more information on security patch fixes see the CVE section below. Releases will follow Semantic Versioning. Users tracking the Master branch should expect unpredictable breaking changes as the project continues to move forward. For stability, it is highly recommended to use a release.
Backward Compatibility
We will maintain two major releases in a moving window. The N-1 release will only receive bug fixes and security updates and will be dropped once N+1 is released.
Deprecation
Deprecation of Go versions or dependent packages will only occur in major releases. To reduce the change of this taking users by surprise, any large deprecation will be preceded by an announcement in an issue on GitHub.
CVE
Maintainers will make every effort to release security patches in the case of a medium to high severity CVE directly impacting the library. The speed in which these patches reach a release is up to the discretion of the maintainers. A low severity CVE may be a lower priority than a high severity one.
Communication
Zulu maintainers will use GitHub issues, and discussions as the primary means of communication with the community. This is to foster open communication with all users and contributors.
Breaking Changes
Breaking changes are generally allowed in the master branch, as this is the branch used to develop the next release of Zulu.
There may be times, however, when master is closed for breaking changes. This is likely to happen as we near the release of a new version.
Breaking changes are not allowed in release branches, as these represent minor versions that have already been released. These version have consumers who expect the APIs, behaviors, etc, to remain stable during the lifetime of the patch stream for the minor release.
Examples of breaking changes include:
- Removing or renaming exported constant, variable, type, or function.
- Updating the version of critical libraries such as
zulucmd/pflag
etc…- Some version updates may be acceptable for picking up bug fixes, but maintainers must exercise caution when reviewing.
There may, at times, need to be exceptions where breaking changes are allowed in release branches. These are at the discretion of the project’s maintainers, and must be carefully considered before merging.
CI Testing
Maintainers will ensure the Zulu test suite utilizes the current supported versions of Golang.
Disclaimer
Changes to this document and the contents therein are at the discretion of the maintainers. None of the contents of this document are legally binding in any way to the maintainers or the users.
License
Zulu is released under the Apache 2.0 license. See LICENSE.txt