This document covers generating and customizing usage/help messages for command-line applications using pflag.
import "github.com/spf13/pflag"func PrintDefaults()
func (f *FlagSet) PrintDefaults()Print to standard error (or configured output) the default values of all defined flags. This generates a formatted list of flags with their descriptions and default values.
Usage:
pflag.String("config", "default.conf", "configuration file")
pflag.IntP("port", "p", 8080, "server port")
pflag.BoolP("verbose", "v", false, "verbose output")
pflag.Parse()
// In response to --help or error
pflag.PrintDefaults()Example Output:
--config string configuration file (default "default.conf")
-p, --port int server port (default 8080)
-v, --verbose verbose outputfunc (f *FlagSet) FlagUsages() stringReturn a string containing the usage information for all flags. This is the same output as PrintDefaults() but returned as a string instead of being printed.
Usage:
fs := pflag.NewFlagSet("app", pflag.ExitOnError)
fs.String("config", "default.conf", "configuration file")
fs.IntP("port", "p", 8080, "server port")
usageText := fs.FlagUsages()
fmt.Println("Available flags:")
fmt.Print(usageText)
// Or use in custom formatting
lines := strings.Split(usageText, "\n")
for _, line := range lines {
if line != "" {
fmt.Printf(" %s\n", line)
}
}func (f *FlagSet) FlagUsagesWrapped(cols int) stringReturn usage information with text wrapped at the specified column width. Useful for ensuring help text fits within terminal width.
Usage:
fs := pflag.NewFlagSet("app", pflag.ExitOnError)
fs.String("description", "", "a very long flag description that should be wrapped to fit within the terminal width nicely")
// Get usage wrapped at 80 columns
usageText := fs.FlagUsagesWrapped(80)
fmt.Print(usageText)
// Or auto-detect terminal width
import "golang.org/x/term"
width, _, _ := term.GetSize(int(os.Stdout.Fd()))
if width == 0 {
width = 80 // Default fallback
}
usageText = fs.FlagUsagesWrapped(width)var Usage func()The Usage function is called when a parsing error occurs or when --help is invoked (if a help flag is defined). You can customize this to provide application-specific help messages.
Usage:
pflag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS] <files...>\n\n", os.Args[0])
fmt.Fprintf(os.Stderr, "Description:\n")
fmt.Fprintf(os.Stderr, " Process files with various options.\n\n")
fmt.Fprintf(os.Stderr, "Options:\n")
pflag.PrintDefaults()
fmt.Fprintf(os.Stderr, "\nExamples:\n")
fmt.Fprintf(os.Stderr, " %s -v --output=result.txt input.txt\n", os.Args[0])
fmt.Fprintf(os.Stderr, " %s --config=prod.conf data/*.txt\n", os.Args[0])
}
pflag.Parse()type FlagSet struct {
Usage func()
// ...
}Each FlagSet has its own Usage function that can be customized.
Usage:
fs := pflag.NewFlagSet("mycommand", pflag.ExitOnError)
fs.String("input", "", "input file")
fs.String("output", "", "output file")
fs.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: mycommand [OPTIONS]\n\n")
fmt.Fprintf(os.Stderr, "A tool that processes files.\n\n")
fmt.Fprintf(os.Stderr, "Options:\n")
fs.PrintDefaults()
}
fs.Parse(os.Args[1:])pflag doesn't automatically add a --help flag. You need to implement it yourself:
help := pflag.BoolP("help", "h", false, "show help message")
pflag.Parse()
if *help {
pflag.Usage()
os.Exit(0)
}package main
import (
"fmt"
"os"
"github.com/spf13/pflag"
)
const usageText = `
Usage: myapp [OPTIONS] <command> [args...]
A powerful command-line application.
Commands:
process Process input files
analyze Analyze data
export Export results
Options:
`
func main() {
help := pflag.BoolP("help", "h", false, "show this help message")
verbose := pflag.BoolP("verbose", "v", false, "verbose output")
config := pflag.String("config", "default.conf", "configuration file")
pflag.Usage = func() {
fmt.Fprint(os.Stderr, usageText)
pflag.PrintDefaults()
fmt.Fprintf(os.Stderr, "\nFor more information, visit: https://example.com/docs\n")
}
pflag.Parse()
if *help {
pflag.Usage()
os.Exit(0)
}
// Continue with application logic
}type FlagSet struct {
SortFlags bool
// ...
}Control whether flags are sorted lexicographically in help output. Default is true.
Usage:
// Maintain definition order in help text
fs := pflag.NewFlagSet("app", pflag.ExitOnError)
fs.SortFlags = false
fs.IntP("zebra", "z", 0, "zebra option")
fs.IntP("apple", "a", 0, "apple option")
fs.IntP("mango", "m", 0, "mango option")
fs.PrintDefaults()
// Output maintains order: zebra, apple, mango// Alphabetical order (default)
fs := pflag.NewFlagSet("app", pflag.ExitOnError)
fs.SortFlags = true // This is the default
fs.IntP("zebra", "z", 0, "zebra option")
fs.IntP("apple", "a", 0, "apple option")
fs.IntP("mango", "m", 0, "mango option")
fs.PrintDefaults()
// Output is sorted: apple, mango, zebrafunc UnquoteUsage(flag *Flag) (name string, usage string)Extract a back-quoted name from the usage string and return it along with the un-quoted usage. This allows you to specify a semantic name for the flag value in the help text.
Usage:
// Define flag with back-quoted type name
pflag.String("output", "", "write results to `file`")
pflag.Int("count", 0, "repeat `N` times")
pflag.Duration("timeout", 0, "request timeout `duration`")
pflag.PrintDefaults()
// Output shows semantic names instead of types:
// --output file write results to file
// --count N repeat N times
// --timeout duration request timeout durationWithout back quotes, the type is used:
pflag.String("output", "", "write results")
// Output:
// --output string write resultspackage main
import (
"fmt"
"os"
"github.com/spf13/pflag"
)
func printHelp() {
fmt.Fprintf(os.Stderr, `Usage: %s [OPTIONS] <input> <output>
Process input file and write to output file.
Options:
`, os.Args[0])
pflag.PrintDefaults()
}
func main() {
help := pflag.BoolP("help", "h", false, "show help")
verbose := pflag.BoolP("verbose", "v", false, "verbose output")
format := pflag.StringP("format", "f", "json", "output format (json|xml|yaml)")
pflag.Usage = printHelp
pflag.Parse()
if *help {
printHelp()
os.Exit(0)
}
if pflag.NArg() < 2 {
fmt.Fprintln(os.Stderr, "Error: input and output files required")
printHelp()
os.Exit(1)
}
}Organize flags into logical groups using annotations:
package main
import (
"fmt"
"os"
"strings"
"github.com/spf13/pflag"
)
func printGroupedHelp(fs *pflag.FlagSet) {
groups := make(map[string][]*pflag.Flag)
ungrouped := []*pflag.Flag{}
// Collect flags by group
fs.VisitAll(func(flag *pflag.Flag) {
if flag.Hidden {
return
}
if group, ok := flag.Annotations["group"]; ok && len(group) > 0 {
groups[group[0]] = append(groups[group[0]], flag)
} else {
ungrouped = append(ungrouped, flag)
}
})
// Print grouped flags
for _, groupName := range []string{"server", "client", "logging"} {
if flags, ok := groups[groupName]; ok && len(flags) > 0 {
fmt.Fprintf(os.Stderr, "%s Options:\n", strings.Title(groupName))
for _, flag := range flags {
printFlag(flag)
}
fmt.Fprintln(os.Stderr)
}
}
// Print ungrouped flags
if len(ungrouped) > 0 {
fmt.Fprintf(os.Stderr, "General Options:\n")
for _, flag := range ungrouped {
printFlag(flag)
}
}
}
func printFlag(flag *pflag.Flag) {
s := fmt.Sprintf(" --%s", flag.Name)
if flag.Shorthand != "" {
s = fmt.Sprintf(" -%s, --%s", flag.Shorthand, flag.Name)
}
name, usage := pflag.UnquoteUsage(flag)
if len(name) > 0 {
s += " " + name
}
fmt.Fprintf(os.Stderr, "%-25s %s", s, usage)
if flag.DefValue != "" && flag.DefValue != "false" {
fmt.Fprintf(os.Stderr, " (default %s)", flag.DefValue)
}
fmt.Fprintln(os.Stderr)
}
func main() {
fs := pflag.NewFlagSet("app", pflag.ExitOnError)
// Server flags
fs.IntP("port", "p", 8080, "server port")
fs.SetAnnotation("port", "group", []string{"server"})
fs.String("host", "localhost", "server host")
fs.SetAnnotation("host", "group", []string{"server"})
// Client flags
fs.String("server", "http://localhost", "server URL")
fs.SetAnnotation("server", "group", []string{"client"})
fs.Duration("timeout", 30*time.Second, "request timeout")
fs.SetAnnotation("timeout", "group", []string{"client"})
// Logging flags
fs.BoolP("verbose", "v", false, "verbose logging")
fs.SetAnnotation("verbose", "group", []string{"logging"})
fs.String("log-file", "", "log file path")
fs.SetAnnotation("log-file", "group", []string{"logging"})
// General flags
help := fs.BoolP("help", "h", false, "show this help")
fs.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: app [OPTIONS]\n\n")
printGroupedHelp(fs)
}
fs.Parse(os.Args[1:])
if *help {
fs.Usage()
os.Exit(0)
}
}package main
import (
"fmt"
"os"
"github.com/spf13/pflag"
)
const (
colorReset = "\033[0m"
colorBold = "\033[1m"
colorGreen = "\033[32m"
colorYellow = "\033[33m"
)
func printColoredHelp() {
fmt.Fprintf(os.Stderr, "%sUsage:%s %s [OPTIONS] <files...>\n\n", colorBold, colorReset, os.Args[0])
fmt.Fprintf(os.Stderr, "%sDescription:%s\n", colorBold, colorReset)
fmt.Fprintf(os.Stderr, " A powerful file processing tool.\n\n")
fmt.Fprintf(os.Stderr, "%sOptions:%s\n", colorBold, colorReset)
pflag.VisitAll(func(flag *pflag.Flag) {
if flag.Hidden {
return
}
var s string
if flag.Shorthand != "" {
s = fmt.Sprintf(" %s-%s%s, %s--%s%s",
colorGreen, flag.Shorthand, colorReset,
colorGreen, flag.Name, colorReset)
} else {
s = fmt.Sprintf(" %s--%s%s", colorGreen, flag.Name, colorReset)
}
name, usage := pflag.UnquoteUsage(flag)
if name != "" {
s += fmt.Sprintf(" %s%s%s", colorYellow, name, colorReset)
}
fmt.Fprintf(os.Stderr, "%-45s %s", s, usage)
if flag.DefValue != "" && flag.DefValue != "false" {
fmt.Fprintf(os.Stderr, " (default: %s)", flag.DefValue)
}
fmt.Fprintln(os.Stderr)
})
}
func main() {
help := pflag.BoolP("help", "h", false, "show help message")
verbose := pflag.BoolP("verbose", "v", false, "verbose output")
config := pflag.String("config", "default.conf", "config file")
pflag.Usage = printColoredHelp
pflag.Parse()
if *help {
pflag.Usage()
os.Exit(0)
}
}pflag.Parse()
// Validate arguments
if pflag.NArg() == 0 {
fmt.Fprintln(os.Stderr, "Error: no input files specified")
fmt.Fprintln(os.Stderr)
pflag.Usage()
os.Exit(1)
}
// Validate flag values
port, _ := pflag.GetInt("port")
if port < 1 || port > 65535 {
fmt.Fprintf(os.Stderr, "Error: port must be between 1 and 65535, got %d\n", port)
pflag.Usage()
os.Exit(1)
}var ErrHelp errorErrHelp is the error returned if the flag -help is invoked but no such flag is defined.
Usage:
fs := pflag.NewFlagSet("app", pflag.ContinueOnError)
fs.String("config", "", "config file")
err := fs.Parse(os.Args[1:])
if err != nil {
if err == pflag.ErrHelp {
// Help was requested but no help flag defined
fmt.Fprintln(os.Stderr, "Help requested but not available")
} else {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
}
os.Exit(1)
}func (f *FlagSet) SetOutput(output io.Writer)Set the destination for usage and error messages. By default, output goes to os.Stderr.
Usage:
import "bytes"
// Capture help output for testing
var buf bytes.Buffer
fs := pflag.NewFlagSet("app", pflag.ContinueOnError)
fs.SetOutput(&buf)
fs.String("test", "", "test flag")
fs.PrintDefaults()
helpText := buf.String()
// Now you can assert on helpText in testsfunc (f *FlagSet) Output() io.WriterReturn the destination for usage and error messages. Returns os.Stderr if output was not set or set to nil.
Here's a comprehensive example with professional help output:
package main
import (
"fmt"
"os"
"github.com/spf13/pflag"
)
const version = "1.0.0"
func printVersion() {
fmt.Printf("myapp version %s\n", version)
os.Exit(0)
}
func printUsage() {
fmt.Fprintf(os.Stderr, `myapp - A powerful file processing tool
Usage:
myapp [OPTIONS] <command> [args...]
Commands:
process Process input files
analyze Analyze data and generate reports
export Export results in various formats
Global Options:
`)
pflag.PrintDefaults()
fmt.Fprintf(os.Stderr, `
Examples:
# Process files with custom config
myapp --config=prod.conf process input.txt
# Analyze data verbosely
myapp -v analyze data/*.csv
# Export with specific format
myapp export --format=json --output=results.json
For more information, visit: https://github.com/example/myapp
`)
}
func main() {
// Global flags
help := pflag.BoolP("help", "h", false, "show help message")
versionFlag := pflag.Bool("version", false, "show version information")
verbose := pflag.BoolP("verbose", "v", false, "enable verbose output")
config := pflag.StringP("config", "c", "", "path to configuration `file`")
output := pflag.StringP("output", "o", "", "write output to `file`")
// Customize usage
pflag.Usage = printUsage
// Parse flags
pflag.Parse()
// Handle special flags
if *versionFlag {
printVersion()
}
if *help {
printUsage()
os.Exit(0)
}
// Validate required configuration
if *config == "" {
fmt.Fprintln(os.Stderr, "Error: --config is required")
printUsage()
os.Exit(1)
}
// Get command
if pflag.NArg() == 0 {
fmt.Fprintln(os.Stderr, "Error: no command specified")
printUsage()
os.Exit(1)
}
command := pflag.Arg(0)
args := pflag.Args()[1:]
// Execute command
if *verbose {
fmt.Printf("Executing command: %s\n", command)
fmt.Printf("Arguments: %v\n", args)
fmt.Printf("Config: %s\n", *config)
if *output != "" {
fmt.Printf("Output: %s\n", *output)
}
}
// ... command execution logic ...
}