SkillAgentSearch skills...

Flagstruct

define flagset with struct and reflect

Install / Use

/learn @podhmo/Flagstruct
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

flagstruct

A Go library that automatically builds pflag.FlagSet from struct definitions using reflection. Define your CLI options as a struct, and flagstruct handles all the flag registration, environment variable binding, and parsing for you.

features

  • Automatic flag generation - Builds pflag.FlagSet by struct definition with minimal boilerplate
  • Nested structure support - Organize flags hierarchically (example, shared common option)
  • Environment variable support - Automatic binding with customizable prefix
  • Short flags - Define shorthand flags via struct tags
  • Required flags - Mark flags as required with validation
  • Custom help text - Customize help messages via struct tags or interfaces
  • Interspersed arguments - Supports interspersed flags and non-flag arguments by default

install

$ go get -v github.com/podhmo/flagstruct

how to use

Basic usage

package main

import (
	"fmt"
	"os"

	"github.com/podhmo/flagstruct"
)

type Options struct {
	Name    string `flag:"name" help:"name of greeting"`
	Verbose bool   `flag:"verbose" short:"v"`
}

func main() {
	options := &Options{Name: "foo"} // default value

	flagstruct.Parse(options, func(b *flagstruct.Builder) {
		b.Name = "hello"
		b.EnvPrefix = "X_"
	})

	fmt.Printf("parsed: %#+v\n", options)
}

default help message.

$ hello --help
Usage of hello:
      --name string   ENV: X_NAME	name of greeting (default "foo")
  -v, --verbose       ENV: X_VERBOSE	-
pflag: help requested
exit status 2

run it.

$ hello --verbose
parsed: &main.Options{Name:"foo", Verbose:true}
$ hello -v --name bar
parsed: &main.Options{Name:"bar", Verbose:true}

# envvar support
$ X_NAME=bar hello -v
parsed: &main.Options{Name:"bar", Verbose:true}

Supported types

flagstruct supports the following types out of the box:

Basic types:

  • string
  • bool
  • int, int64
  • uint, uint64
  • float64
  • time.Duration

Slice types:

  • []string
  • []bool
  • []int, []int64
  • []uint
  • []float64
  • []time.Duration

Map types:

  • map[string]string
  • map[string]bool
  • map[string]int, map[string]int64
  • map[string]uint, map[string]uint64
  • map[string]float64
  • map[string]time.Duration

Map values are passed as a single comma-separated string of key=value pairs. For example: --metadata project=alpha,owner=john.doe

Custom types:

  • Types implementing flag.Value interface (Set, String, Type methods)
  • Types implementing encoding.TextUnmarshaler and encoding.TextMarshaler interfaces

In addition to individual custom types, slices and maps of types that implement encoding.TextUnmarshaler are also supported. For example, you can use []net.IP or map[string]net.IP directly in your configuration struct.

type Config struct {
    IPAddresses []net.IP `flag:"ips"`
    IPMapping   map[string]net.IP `flag:"ip-map"`
}

You can then provide these flags on the command line:

$ go run main.go --ips "192.168.1.1,10.0.0.1" --ip-map "private=192.168.1.1,public=8.8.8.8"

Struct tags

flag tag

Defines the flag name. If omitted, unexported fields are ignored.

type Options struct {
    Name string `flag:"name"`        // --name
    Age  int    `flag:"age"`         // --age
    Skip string `flag:"-"`           // ignored
    Port int                         // ignored (no flag tag, unexported not supported)
}

short tag

Defines a short flag alias. Only works for top-level flags (not nested).

type Options struct {
    Verbose bool `flag:"verbose" short:"v"`  // -v, --verbose
    Debug   bool `flag:"debug" short:"d"`    // -d, --debug
}

help tag

Provides help text for the flag.

type Options struct {
    Name string `flag:"name" help:"Your name"`
    Port int    `flag:"port" help:"Server port number"`
}

required tag

Marks a flag as required. Validation fails if not provided.

type Options struct {
    APIKey string `flag:"api-key" required:"true" help:"API key for authentication"`
}

envname tag

Specifies a custom environment variable name for a flag.

type Options struct {
    // This flag will be bound to the `MY_APP_PORT` environment variable
    Port int `flag:"port" envname:"MY_APP_PORT" help:"Server port number"`
}

Advanced usage

Nested structures

Organize related flags into nested structures. Flag names are prefixed with the struct field name.

type DBConfig struct {
    URI   string `flag:"uri"`
    Debug bool   `flag:"debug"`
}

type Options struct {
    DB        DBConfig `flag:"db"`         // --db.uri, --db.debug
    AnotherDB DBConfig `flag:"another-db"` // --another-db.uri, --another-db.debug
}

func main() {
    options := &Options{}
    flagstruct.Parse(options)
}

Embedded fields

Embedded structs allow you to compose configuration from multiple sources. Fields from embedded structs are promoted to the parent level.

type DatabaseConfig struct {
    Host string `flag:"host"`
    Port int    `flag:"port"`
}

type Options struct {
    DatabaseConfig  // embedded struct
}

func main() {
    options := &Options{}
    flagstruct.Parse(options)
    // flags: --host, --port (fields are promoted to parent level)
}

The --host and --port flags from the embedded DatabaseConfig struct are available directly at the top level, not as --databaseconfig.host or --databaseconfig.port.

inline option in flag tag

Alternatively, you can achieve the same result using the inline option without embedding:

type DatabaseConfig struct {
    Host string `flag:"host"`
    Port int    `flag:"port"`
}

type Options struct {
    DB DatabaseConfig `flag:",inline"`  // flags: --host, --port (not --db.host, --db.port)
}

Both approaches flatten nested struct fields to the parent level without adding a prefix. Use embedded structs when you want field promotion in Go code, or use inline when you want to keep the nested structure in code but flatten flags.

Shared common options

Share common configuration across multiple nested structs using embedded pointers. This pattern is useful when you want a single flag to control the same setting across multiple components.

type BaseConfig struct {
    Debug bool `json:"debug"`
}

type AConfig struct {
    *BaseConfig
    Name string `json:"name"`
}

type BConfig struct {
    *BaseConfig
    Verbose bool `json:"verbose"`
}

type Config struct {
    BaseConfig  // top-level embedded
    A AConfig `json:"a"`
    B BConfig `json:"b"`
}

func main() {
    config := &Config{}
    flagstruct.Parse(config, flagstruct.WithMoreFlagnameTags("json"))
    // --debug flag is shared between BaseConfig, AConfig.BaseConfig, and BConfig.BaseConfig
    // When you set --debug=true, all three embedded BaseConfig instances point to the same value
    fmt.Printf("Debug: %v, A.Debug: %v, B.Debug: %v\n", 
        config.Debug, config.A.Debug, config.B.Debug)
    // Output: Debug: true, A.Debug: true, B.Debug: true
}

Interspersed Arguments

By default, flagstruct allows flags and non-flag arguments to be interspersed. This means you can mix flags and other arguments on the command line.

For example, with the following struct:

type Options struct {
    Name    string `flag:"name"`
    Verbose bool   `flag:"verbose" short:"v"`
}

You can run your program with arguments like this:

$ my-app --name foo arg1 --verbose arg2

flagstruct will correctly parse --name as "foo" and --verbose as true, while the remaining non-flag arguments (arg1, arg2) can be retrieved from the *pflag.FlagSet.

options := &Options{}
fs := flagstruct.Build(options)
fs.Parse(os.Args[1:])

fmt.Printf("options: %#v\n", options)
fmt.Printf("args: %v\n", fs.Args())

Output:

options: &main.Options{Name:"foo", Verbose:true}
args: [arg1 arg2]

Using with Cobra

flagstruct works seamlessly with Cobra commands:

import (
    "github.com/podhmo/flagstruct"
    "github.com/spf13/cobra"
)

type Options struct {
    Name    string `flag:"name" help:"name of greeting"`
    Verbose bool   `flag:"verbose" short:"v"`
}

func main() {
    options := &Options{Name: "default"}
    
    cmd := &cobra.Command{
        Use:   "hello",
        Short: "A brief description",
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Printf("Hello %s\n", options.Name)
        },
    }
    
    fs := flagstruct.Build(options)
    cmd.Flags().AddFlagSet(fs.FlagSet)
    
    cmd.Execute()
}

Error handling

Use Build and ParseArgs for custom error handling:

options := &Options{}
fs := flagstruct.Build(options, func(b *flagstruct.Builder) {
    b.EnvPrefix = "MYAPP_"
})

if err := fs.Parse(os.Args[1:]); err != nil {
    // Handle error
    fmt.Fprintf(os.Stderr, "Error: %v\n", err)
    os.Exit(1)
}

Environment variables

Environment variables are automatically supported with a customizable prefix. Command-line arguments take precedence over environment variables.

You can customize the environment variable name for a specific field using the envname tag.

flagstruct.Parse(options, func(b *flagstruct.Builder) {
    b.EnvPrefix = "MYAPP_"  // flags will check MYAPP_<FLAGNAME> env vars
})

If envname tag is not provided, flag names are converted to environment variable names by:

  1. Converting to uppercase
  2. Replacing - and . with _
  3. Adding the prefix

Example: --db.uri becomes MYAPP_DB_URI

Additional flag name tags

Use multiple tag names (e.g., combine with json tags):

type Options struct {
    Name string `json:"name" help:"Your name"`
}

flagstruct.Parse(options, flagstruct.WithMoreFlagnameTags("json"))
// This creates --name flag from the json tag

API reference

Parse[T any](o *T, options ...func(*Builder))

Parse command line arguments with auto

View on GitHub
GitHub Stars5
CategoryDevelopment
Updated5mo ago
Forks0

Languages

Go

Security Score

82/100

Audited on Oct 13, 2025

No findings