or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

advanced-features.mdflag-access.mdflag-definition.mdflag-types.mdflagset-methods.mdgo-integration.mdindex.mdusage-help.md
tile.json

advanced-features.mddocs/

Advanced Features

This document covers advanced pflag features including flag normalization, deprecation, hidden flags, annotations, and NoOptDefVal.

Import

import "github.com/spf13/pflag"

Flag Name Normalization

Flag name normalization allows you to define custom rules for matching flag names, enabling features like treating hyphens and underscores as equivalent.

SetNormalizeFunc

func (f *FlagSet) SetNormalizeFunc(n func(*FlagSet, string) NormalizedName)

Set a custom function to normalize flag names. The normalization function is applied to both flag definitions and lookups, allowing flexible name matching.

GetNormalizeFunc

func (f *FlagSet) GetNormalizeFunc() func(*FlagSet, string) NormalizedName

Return the previously set normalize function, or an identity function if none was set.

NormalizedName Type

type NormalizedName string

A flag name that has been normalized according to FlagSet rules.

Example: Treating Hyphens and Underscores as Equivalent

import (
	"strings"
	"github.com/spf13/pflag"
)

func normalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName {
	// Replace underscores with hyphens
	from := []string{"_"}
	to := "-"
	for _, sep := range from {
		name = strings.Replace(name, sep, to, -1)
	}
	return pflag.NormalizedName(name)
}

func main() {
	fs := pflag.NewFlagSet("app", pflag.ExitOnError)
	fs.SetNormalizeFunc(normalizeFunc)

	fs.String("my-flag", "default", "a flag")

	// All of these will match the same flag:
	fs.Parse([]string{"--my-flag=value"})   // Works
	fs.Parse([]string{"--my_flag=value"})   // Also works
	fs.Parse([]string{"--my__flag=value"})  // Also works
}

Example: Flag Name Aliasing

func aliasNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName {
	switch name {
	case "old-flag-name":
		name = "new-flag-name"
	case "deprecated-name":
		name = "current-name"
	}
	return pflag.NormalizedName(name)
}

fs := pflag.NewFlagSet("app", pflag.ExitOnError)
fs.SetNormalizeFunc(aliasNormalizeFunc)
fs.String("new-flag-name", "default", "a flag")

// Both old and new names work
fs.Parse([]string{"--old-flag-name=value"})  // Works
fs.Parse([]string{"--new-flag-name=value"})  // Also works

Flag Deprecation

MarkDeprecated

func (f *FlagSet) MarkDeprecated(name string, usageMessage string) error

Mark a flag as deprecated. The flag continues to function but will not appear in help/usage messages. When the deprecated flag is used, the usageMessage is printed.

Usage:

fs := pflag.NewFlagSet("app", pflag.ExitOnError)
fs.String("config", "default.conf", "configuration file")
fs.String("cfg", "default.conf", "configuration file (use --config instead)")

// Deprecate the old flag
err := fs.MarkDeprecated("cfg", "please use --config instead")
if err != nil {
	fmt.Printf("Error marking flag deprecated: %v\n", err)
}

// Now when user runs: myapp --cfg=file.conf
// Output: Flag --cfg has been deprecated, please use --config instead

MarkShorthandDeprecated

func (f *FlagSet) MarkShorthandDeprecated(name string, usageMessage string) error

Mark the shorthand of a flag as deprecated. The shorthand continues to function but won't appear in help/usage messages. When used, the usageMessage is printed.

Usage:

fs := pflag.NewFlagSet("app", pflag.ExitOnError)
fs.StringP("verbose-output", "v", "normal", "output verbosity")

// Deprecate the shorthand but keep the long form
err := fs.MarkShorthandDeprecated("verbose-output", "please use --verbose-output only")
if err != nil {
	fmt.Printf("Error marking shorthand deprecated: %v\n", err)
}

// Now when user runs: myapp -v=high
// Output: Flag shorthand -v has been deprecated, please use --verbose-output only

Checking Deprecation Status

fs.String("old-flag", "default", "deprecated flag")
fs.MarkDeprecated("old-flag", "use --new-flag instead")

flag := fs.Lookup("old-flag")
if flag.Deprecated != "" {
	fmt.Printf("Flag is deprecated: %s\n", flag.Deprecated)
}
if flag.ShorthandDeprecated != "" {
	fmt.Printf("Shorthand is deprecated: %s\n", flag.ShorthandDeprecated)
}

Hidden Flags

MarkHidden

func (f *FlagSet) MarkHidden(name string) error

Mark a flag as hidden. The flag continues to function normally but will not appear in help/usage messages. Useful for internal or debug flags.

Usage:

fs := pflag.NewFlagSet("app", pflag.ExitOnError)
fs.Bool("debug", false, "enable debug mode")
fs.String("internal-config", "", "internal configuration (hidden)")

// Hide the internal flag from help output
err := fs.MarkHidden("internal-config")
if err != nil {
	fmt.Printf("Error marking flag hidden: %v\n", err)
}

fs.Parse(os.Args[1:])

// The flag still works:
config, _ := fs.GetString("internal-config")

Checking Hidden Status

flag := fs.Lookup("internal-config")
if flag.Hidden {
	fmt.Println("This flag is hidden from help text")
}

HasAvailableFlags

func (f *FlagSet) HasAvailableFlags() bool

Return true if the FlagSet has any flags that are not hidden. Useful for determining whether to show help output.

Usage:

if fs.HasAvailableFlags() {
	fmt.Println("Available options:")
	fs.PrintDefaults()
} else {
	fmt.Println("No user-facing options available")
}

Flag Annotations

Annotations provide extensible metadata for flags, commonly used for shell completion hints and other tooling.

SetAnnotation

func (f *FlagSet) SetAnnotation(name, key string, values []string) error

Add an annotation to a flag. Annotations are stored as map[string][]string and can contain arbitrary metadata.

Usage:

fs := pflag.NewFlagSet("app", pflag.ExitOnError)
fs.String("output", "", "output file")

// Add filename completion hint for bash
err := fs.SetAnnotation("output", "cobra_annotation_bash_completion", []string{"file"})
if err != nil {
	fmt.Printf("Error setting annotation: %v\n", err)
}

// Add custom metadata
fs.SetAnnotation("output", "category", []string{"io", "file"})
fs.SetAnnotation("output", "required", []string{"true"})

Accessing Annotations

flag := fs.Lookup("output")
if flag.Annotations != nil {
	if category, ok := flag.Annotations["category"]; ok {
		fmt.Printf("Flag category: %v\n", category)
	}

	if required, ok := flag.Annotations["required"]; ok {
		fmt.Printf("Required: %v\n", required[0] == "true")
	}
}

Common Annotation Use Cases

Shell Completion Hints

// File completion
fs.SetAnnotation("config", "cobra_annotation_bash_completion", []string{"file"})

// Directory completion
fs.SetAnnotation("output-dir", "cobra_annotation_bash_completion", []string{"dir"})

// Custom completion
fs.SetAnnotation("format", "cobra_annotation_bash_completion_custom",
	[]string{"__myapp_custom_format_func"})

Flag Grouping

fs.SetAnnotation("port", "group", []string{"server"})
fs.SetAnnotation("host", "group", []string{"server"})
fs.SetAnnotation("timeout", "group", []string{"client"})

// Later, print flags by group
fs.VisitAll(func(flag *pflag.Flag) {
	if group, ok := flag.Annotations["group"]; ok {
		fmt.Printf("Flag %s belongs to group: %s\n", flag.Name, group[0])
	}
})

Validation Metadata

fs.SetAnnotation("port", "min", []string{"1"})
fs.SetAnnotation("port", "max", []string{"65535"})
fs.SetAnnotation("email", "pattern", []string{"^[a-z]+@[a-z]+\\.[a-z]+$"})

NoOptDefVal

The NoOptDefVal field allows a flag to have a special default value when the flag is present on the command line without an explicit argument.

Setting NoOptDefVal

fs := pflag.NewFlagSet("app", pflag.ExitOnError)
port := fs.Int("port", 8080, "server port")

// Set NoOptDefVal
portFlag := fs.Lookup("port")
portFlag.NoOptDefVal = "9090"

fs.Parse(args)

Behavior Table

Command LineResultExplanation
myappport = 8080No flag, use default
myapp --port=3000port = 3000Explicit value
myapp --portport = 9090Flag without value, use NoOptDefVal

Use Cases

Boolean-like Optional Arguments

verbosity := pflag.String("verbosity", "normal", "output verbosity")
pflag.Lookup("verbosity").NoOptDefVal = "high"

// myapp                  => verbosity = "normal"
// myapp --verbosity=low  => verbosity = "low"
// myapp --verbosity      => verbosity = "high"

Optional Port Numbers

port := pflag.Int("port", 0, "server port (0=disabled)")
pflag.Lookup("port").NoOptDefVal = "8080"

// myapp                => port = 0 (disabled)
// myapp --port=3000    => port = 3000
// myapp --port         => port = 8080 (default when enabled)

Feature Toggles with Levels

debug := pflag.String("debug", "off", "debug level (off/basic/verbose)")
pflag.Lookup("debug").NoOptDefVal = "basic"

// myapp                  => debug = "off"
// myapp --debug=verbose  => debug = "verbose"
// myapp --debug          => debug = "basic"

Parse Error Allowlist

Configure which parsing errors should be ignored rather than causing parse failures.

ParseErrorsAllowlist Type

type ParseErrorsAllowlist struct {
	UnknownFlags bool  // Ignore unknown flags errors
}

type FlagSet struct {
	ParseErrorsAllowlist ParseErrorsAllowlist
	// ...
}

Ignoring Unknown Flags

fs := pflag.NewFlagSet("app", pflag.ContinueOnError)
fs.Int("known-flag", 0, "a known flag")

// Configure to ignore unknown flags
fs.ParseErrorsAllowlist.UnknownFlags = true

// This will not error even though --unknown-flag is not defined
err := fs.Parse([]string{"--known-flag=42", "--unknown-flag=value"})
if err != nil {
	// Only non-ignored errors will be returned
	fmt.Printf("Parse error: %v\n", err)
}

// The known flag was still parsed successfully
value, _ := fs.GetInt("known-flag")
fmt.Printf("Known flag: %d\n", value)  // Prints: 42

Use Cases

Forward Compatibility

Allows older versions of applications to ignore flags added in newer versions:

fs.ParseErrorsAllowlist.UnknownFlags = true
fs.Parse(os.Args[1:])
// Older app ignores newer flags it doesn't understand

Plugin Systems

When combining flags from multiple sources (plugins), unknown flags can be tolerated:

// Core app flags
coreFlags := pflag.NewFlagSet("core", pflag.ContinueOnError)
coreFlags.ParseErrorsAllowlist.UnknownFlags = true
coreFlags.String("config", "", "config file")

// Parse all args, ignoring plugin-specific flags
coreFlags.Parse(os.Args[1:])

// Plugins can parse their own flags from the same args
pluginFlags := pflag.NewFlagSet("plugin", pflag.ContinueOnError)
pluginFlags.String("plugin-option", "", "plugin option")
pluginFlags.Parse(os.Args[1:])

Utility Functions

UnquoteUsage

func UnquoteUsage(flag *Flag) (name string, usage string)

Extract a back-quoted name from the usage string for a flag and return it along with the un-quoted usage. This helps in generating more informative help messages.

Usage:

fs := pflag.NewFlagSet("app", pflag.ExitOnError)
fs.String("output", "", "write output to `file`")

flag := fs.Lookup("output")
name, usage := pflag.UnquoteUsage(flag)

fmt.Printf("Name: %s\n", name)    // Prints: file
fmt.Printf("Usage: %s\n", usage)  // Prints: write output to file

If no back-quoted name is present, the name is inferred from the flag's type:

fs.Int("count", 0, "number of iterations")
flag := fs.Lookup("count")
name, usage := pflag.UnquoteUsage(flag)
fmt.Printf("Name: %s\n", name)  // Prints: int (inferred from type)

ParseIPv4Mask

func ParseIPv4Mask(s string) net.IPMask

Parse an IPv4 mask written in IP form (e.g., "255.255.255.0"). This is a utility function that should belong to the net package but is provided here.

Usage:

import "net"

mask := pflag.ParseIPv4Mask("255.255.255.0")
fmt.Printf("Mask: %v\n", mask)  // Prints: ffffff00

Complete Advanced Example

Here's a comprehensive example combining multiple advanced features:

package main

import (
	"fmt"
	"os"
	"strings"
	"github.com/spf13/pflag"
)

func main() {
	fs := pflag.NewFlagSet("myapp", pflag.ExitOnError)

	// Set up normalization for hyphen/underscore equivalence
	fs.SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName {
		name = strings.ReplaceAll(name, "_", "-")
		return pflag.NormalizedName(name)
	})

	// Define flags
	fs.Int("server-port", 8080, "server port")
	fs.String("config-file", "default.conf", "configuration file")
	fs.String("old-config", "", "deprecated configuration file")
	fs.Bool("debug-mode", false, "enable debug output")
	fs.String("internal-token", "", "internal auth token")

	// Mark deprecated flag
	fs.MarkDeprecated("old-config", "please use --config-file instead")

	// Hide internal flag
	fs.MarkHidden("internal-token")

	// Set up NoOptDefVal for debug
	fs.Lookup("debug-mode").NoOptDefVal = "true"

	// Add annotations for tooling
	fs.SetAnnotation("config-file", "cobra_annotation_bash_completion", []string{"file"})
	fs.SetAnnotation("config-file", "required", []string{"true"})

	// Allow unknown flags for forward compatibility
	fs.ParseErrorsAllowlist.UnknownFlags = true

	// Parse
	fs.Parse(os.Args[1:])

	// Access values
	port, _ := fs.GetInt("server-port")
	config, _ := fs.GetString("config-file")
	debug, _ := fs.GetBool("debug-mode")

	fmt.Printf("Starting server:\n")
	fmt.Printf("  Port: %d\n", port)
	fmt.Printf("  Config: %s\n", config)
	fmt.Printf("  Debug: %v\n", debug)

	// Check for required flags
	if flag := fs.Lookup("config-file"); !flag.Changed {
		if annotations, ok := flag.Annotations["required"]; ok && annotations[0] == "true" {
			fmt.Fprintln(os.Stderr, "Error: --config-file is required")
			os.Exit(1)
		}
	}
}