### Define CLI Command with Options and Handler Source: https://context7.com/coder/serpent/llms.txt Defines a root command with a verbose flag and a subcommand 'greet' that takes a name and an output option. The 'greet' command uses middleware to require one argument and its handler formats a greeting. Example invocations show flag and environment variable usage. ```go package main import ( "fmt" "os" "github.com/coder/serpent" ) func main() { var ( verbose bool output string ) root := serpent.Command{ Use: "myapp", Short: "A sample multi-command CLI.", Long: "myapp demonstrates Serpent's command tree and option system.", Options: serpent.OptionSet{ { Name: "verbose", Flag: "verbose", FlagShorthand: "v", Env: "MYAPP_VERBOSE", Value: serpent.BoolOf(&verbose), Description: "Enable verbose logging.", }, }, // No Handler → prints help automatically. } greet := &serpent.Command{ Use: "greet ", Short: "Greet someone by name.", Options: serpent.OptionSet{ { Name: "output", Flag: "output", Env: "MYAPP_OUTPUT", YAML: "output", Default: "Hello", Value: serpent.StringOf(&output), Description: "Greeting word to use.", }, }, Middleware: serpent.Chain( serpent.RequireNArgs(1), ), Handler: func(inv *serpent.Invocation) error { if verbose { fmt.Fprintln(inv.Stdout, "[verbose] handler called") } fmt.Fprintf(inv.Stdout, "%s, %s!\n", output, inv.Args[0]) return nil }, } root.AddSubcommands(greet) if err := root.Invoke().WithOS().Run(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } // $ myapp greet World // Hello, World! // // $ MYAPP_OUTPUT=Hi myapp greet --verbose Alice // [verbose] handler called // Hi, Alice! ``` -------------------------------- ### Install Shell Completion for CLI Application Source: https://context7.com/coder/serpent/llms.txt Installs shell completion scripts for Bash, Zsh, Fish, or PowerShell. Detects the user's shell automatically or allows explicit specification. ```go package main import ( "fmt" "os" "github.com/coder/serpent" "github.com/coder/serpent/completion" ) func main() { var shellName string root := serpent.Command{ Use: "myapp", Short: "My application.", Children: []*serpent.Command{ { Use: "completion", Short: "Install shell completion scripts.", Options: serpent.OptionSet{ { Name: "shell", Flag: "shell", Value: completion.ShellOptions(&shellName), Description: "Shell to install completions for (bash, zsh, fish, powershell).", }, }, Handler: func(inv *serpent.Invocation) error { var shell completion.Shell var err error if shellName == "" { shell, err = completion.DetectUserShell("myapp") } else { shell, err = completion.ShellByName(shellName, "myapp") } if err != nil { return fmt.Errorf("detecting shell: %w", err) } if err = completion.InstallShellCompletion(shell); err != nil { return fmt.Errorf("installing completion: %w", err) } fmt.Fprintf(inv.Stdout, "Completion installed for %s\n", shell.Name()) return nil }, }, }, } if err := root.Invoke().WithOS().Run(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } // $ myapp completion --shell zsh // Completion installed for zsh ``` -------------------------------- ### Parse and Manage Environment Variables Source: https://context7.com/coder/serpent/llms.txt Filters and parses environment variables from os.Environ() with a specified prefix, stripping it from keys. Provides methods to Get and Set variables. ```go package main import ( "fmt" "os" "github.com/coder/serpent" ) func main() { // Simulate an OS environment with a prefix. osEnv := []string{ "MYAPP_HOST=localhost", "MYAPP_PORT=9090", "MYAPP_DEBUG=true", "PATH=/usr/bin", // no prefix, will be excluded } // Parse only MYAPP_ vars, stripping the prefix. env := serpent.ParseEnviron(osEnv, "MYAPP_") fmt.Println(env.Get("HOST")) // localhost fmt.Println(env.Get("PORT")) // 9090 fmt.Println(env.Get("PATH")) // "" (not present) // Mutate and round-trip back to []string for os.Setenv. env.Set("HOST", "0.0.0.0") fmt.Println(env.Get("HOST")) // 0.0.0.0 // In a real command test: _ = os.Environ // replaced by env in inv.Environ } ``` -------------------------------- ### Test CLI Command with Injected Stdio Source: https://context7.com/coder/serpent/llms.txt Demonstrates testing a Serpent command by injecting custom Stdio buffers and environment variables. This pattern avoids reliance on the actual OS, making tests more predictable and isolated. The example shows overriding stdio and environment before running the command. ```go package main_test import ( "bytes" "strings" "testing" "github.com/coder/serpent" ) func TestGreetCommand(t *testing.T) { var name string cmd := serpent.Command{ Use: "greet ", Short: "Greets a name.", Middleware: serpent.RequireNArgs(1), Handler: func(inv *serpent.Invocation) error { name = inv.Args[0] inv.Stdout.Write([]byte("Hello, " + name + "!\n")) return nil }, } var stdout bytes.Buffer err := cmd.Invoke("Alice"). WithOS(). // use OS env & signals, but we override stdio below // Override stdio for testing: // (WithOS sets them; reassign after) Run() // runs with discarded stdio since WithOS wasn't called yet // Preferred test pattern: inject buffers before Run stdout.Reset() inv := cmd.Invoke("Bob") inv.Stdout = &stdout inv.Stderr = strings.NewReader("") // satisfy interface via cast, or use io.Discard inv.Stdin = strings.NewReader("") inv.Environ = serpent.Environ{} if err = inv.Run(); err != nil { t.Fatalf("unexpected error: %v", err) } if got := stdout.String(); got != "Hello, Bob!\n" { t.Fatalf("unexpected output: %q", got) } } ``` -------------------------------- ### Define Multi-Source Configuration Options Source: https://context7.com/coder/serpent/llms.txt Use OptionSet to define configuration options that can be sourced from flags, environment variables, YAML, or defaults. The priority order is flag > env > yaml > default. Specify the target variable using ValueOf and provide a description. ```go package main import ( "fmt" "net/url" "time" "github.com/coder/serpent" ) func buildOptions() (serpent.OptionSet, *string, *url.URL, *time.Duration, *bool) { var ( token string endpoint url.URL timeout time.Duration dryRun bool ) opts := serpent.OptionSet{ { Name: "token", Flag: "token", Env: "APP_TOKEN", YAML: "token", Required: true, // must be provided by some source Value: serpent.StringOf(&token), Description: "Authentication token for the API.", }, { Name: "endpoint", Flag: "endpoint", Env: "APP_ENDPOINT", YAML: "endpoint", Default: "https://api.example.com", Value: serpent.URLOf(&endpoint), Description: "Base URL of the remote API.", }, { Name: "timeout", Flag: "timeout", Env: "APP_TIMEOUT", YAML: "timeout", Default: "30s", Value: serpent.DurationOf(&timeout), Description: "Request timeout duration.", }, { Name: "dry-run", Flag: "dry-run", Env: "APP_DRY_RUN", Value: serpent.BoolOf(&dryRun), Description: "Print actions without executing them.", }, } return opts, &token, &endpoint, &timeout, &dryRun } func main() { opts, token, endpoint, timeout, _ := buildOptions() cmd := serpent.Command{ Use: "deploy", Short: "Deploy the application.", Options: opts, Handler: func(inv *serpent.Invocation) error { fmt.Fprintf(inv.Stdout, "token source: %s\n", opts.ByName("token").ValueSource) fmt.Fprintf(inv.Stdout, "endpoint: %s, timeout: %s\n", endpoint, timeout) _ = token return nil }, } _ = cmd.Invoke().WithOS().Run() } // APP_TOKEN=secret APP_TIMEOUT=1m ./deploy --endpoint https://staging.example.com // token source: env // endpoint: https://staging.example.com, timeout: 1m0s ``` -------------------------------- ### Customize Help Rendering with DefaultHelpFn Source: https://context7.com/coder/serpent/llms.txt Overrides the default help handler to add custom preamble text before rendering the standard Go-toolchain-style help. Supports options, deprecation notices, and terminal width adaptation. ```go package main import ( "bytes" "fmt" "github.com/coder/serpent" ) func main() { var count int64 cmd := serpent.Command{ Use: "list [flags]", Short: "List items in the store.", Long: "list queries the backing store and prints matching items.", Options: serpent.OptionSet{ { Name: "count", Flag: "count", FlagShorthand: "n", Default: "10", Value: serpent.Int64Of(&count), Description: "Maximum number of items to return.", }, }, // Override help to add a preamble. HelpHandler: func(inv *serpent.Invocation) error { fmt.Fprintln(inv.Stdout, "=== myapp list help ===") return serpent.DefaultHelpFn()(inv) }, Handler: func(inv *serpent.Invocation) error { fmt.Fprintf(inv.Stdout, "listing up to %d items\n", count) return nil }, } var buf bytes.Buffer inv := cmd.Invoke("--help") inv.Stdout = &buf inv.Stderr = &buf inv.Environ = serpent.Environ{} _ = inv.Run() fmt.Println(buf.String()) } ``` -------------------------------- ### Basic Echo Command with Serpent Source: https://github.com/coder/serpent/blob/main/README.md Demonstrates a simple echo command using serpent.Command, including defining options and handling invocation logic. Ensure serpent is imported. ```go package main import ( "os" "strings" "github.com/coder/serpent" ) func main() { var upper bool cmd := serpent.Command{ Use: "echo ", Short: "Prints the given text to the console.", Options: serpent.OptionSet{ { Name: "upper", Value: serpent.BoolOf(&upper), Flag: "upper", Description: "Prints the text in upper case.", }, }, Handler: func(inv *serpent.Invocation) error { if len(inv.Args) == 0 { inv.Stderr.Write([]byte("error: missing text\n")) os.Exit(1) } text := inv.Args[0] if upper { text = strings.ToUpper(text) } inv.Stdout.Write([]byte(text)) return nil }, } err := cmd.Invoke().WithOS().Run() if err != nil { panic(err) } } ``` -------------------------------- ### Extend Commands and Options with Annotations Source: https://context7.com/coder/serpent/llms.txt Shows how to use `Annotations` (a `map[string]string`) to attach arbitrary metadata to `Command` and `Option` types. `Mark` returns a new copy, enabling safe chaining of annotations. ```go package main import ( "fmt" "github.com/coder/serpent" ) const ( annotationExperimental = "experimental" annotationCategory = "category" ) func main() { cmd := serpent.Command{ Use: "beta-feature", Short: "An experimental command.", Annotations: serpent.Annotations{}. Mark(annotationExperimental, "true"). Mark(annotationCategory, "networking"), Handler: func(inv *serpent.Invocation) error { if inv.Command.Annotations.IsSet(annotationExperimental) { fmt.Fprintln(inv.Stderr, "warning: this feature is experimental") } cat, _ := inv.Command.Annotations.Get(annotationCategory) fmt.Fprintf(inv.Stdout, "category: %s\n", cat) return nil }, } inv := cmd.Invoke() inv.Stdout, inv.Stderr = new(nopWriter), new(nopWriter) _ = inv.Run() } type nopWriter struct{} func (nopWriter) Write(p []byte) (int, error) { return len(p), nil } ``` -------------------------------- ### Load Options from YAML Config File Source: https://context7.com/coder/serpent/llms.txt Illustrates how to automatically load configuration options from a YAML file using `YAMLConfigPath`. The file is parsed, and matching keys are applied before defaults but after flags and environment variables. ```go package main import ( "fmt" "os" "github.com/coder/serpent" ) func main() { // Write a sample config file. cfgContent := []byte("output: Bonjour\n") f, _ := os.CreateTemp("", "config-*.yaml") _, _ = f.Write(cfgContent) _ = f.Close() defer os.Remove(f.Name()) var ( cfgPath serpent.YAMLConfigPath output string ) cmd := serpent.Command{ Use: "greet ", Short: "Greet using a YAML-configured greeting.", Options: serpent.OptionSet{ { Name: "config", Flag: "config", Value: &cfgPath, Description: "Path to YAML configuration file.", }, { Name: "output", YAML: "output", Flag: "output", Default: "Hello", Value: serpent.StringOf(&output), Description: "Greeting word.", }, }, Middleware: serpent.RequireNArgs(1), Handler: func(inv *serpent.Invocation) error { fmt.Fprintf(inv.Stdout, "%s, %s!\n", output, inv.Args[0]) return nil }, } // Pass --config so Serpent loads the YAML automatically. inv := cmd.Invoke("--config", f.Name(), "World") inv.Stdout = os.Stdout inv.Environ = serpent.Environ{} if err := inv.Run(); err != nil { panic(err) } // Output: Bonjour, World! } ``` -------------------------------- ### Organize Options Hierarchically with Group Source: https://context7.com/coder/serpent/llms.txt Demonstrates how to use the `Group` type to create hierarchical sections for options in help output and YAML config files. Options are nested under named sections, and `FullName()` provides the full path. ```go package main import ( "fmt" "gopkg.in/yaml.v3" "github.com/coder/serpent" ) func main() { lsGroup := &serpent.Group{ Name: "TLS", YAML: "tls", Description: "TLS configuration options.", } var ( lsCert string lsKey string port int64 ) opts := serpent.OptionSet{ { Name: "tls-cert", Flag: "tls-cert", YAML: "cert", Env: "APP_TLS_CERT", Group: tlsGroup, Value: serpent.StringOf(&tlsCert), Default: "/etc/tls/server.crt", Description: "Path to TLS certificate file.", }, { Name: "tls-key", Flag: "tls-key", YAML: "key", Env: "APP_TLS_KEY", Group: tlsGroup, Value: serpent.StringOf(&tlsKey), Default: "/etc/tls/server.key", Description: "Path to TLS key file.", }, { Name: "port", Flag: "port", YAML: "port", Env: "APP_PORT", Value: serpent.Int64Of(&port), Default: "443", Description: "Port to listen on.", }, } _ = opts.SetDefaults() // Marshal to YAML — options with Group.YAML are nested under "tls:" node, err := opts.MarshalYAML() if err != nil { panic(err) } byt, _ := yaml.Marshal(node) fmt.Println(string(byt)) // port: 443 // tls: // # Path to TLS certificate file. // # (default: /etc/tls/server.crt, type: string) // cert: /etc/tls/server.crt // # Path to TLS key file. // # (default: /etc/tls/server.key, type: string) // key: /etc/tls/server.key } ``` -------------------------------- ### Compose Middleware with Chain Source: https://context7.com/coder/serpent/llms.txt Demonstrates chaining multiple middleware functions, including custom ones like auth and timing, along with built-in argument validators. Middleware is executed in the order they are declared. ```go package main import ( "fmt" "time" "github.com/coder/serpent" ) // timingMiddleware logs how long a handler takes. func timingMiddleware(next serpent.HandlerFunc) serpent.HandlerFunc { return func(inv *serpent.Invocation) error { start := time.Now() err := next(inv) fmt.Fprintf(inv.Stderr, "duration: %s\n", time.Since(start)) return err } } // authMiddleware rejects the call if AUTH_TOKEN env is not set. func authMiddleware(next serpent.HandlerFunc) serpent.HandlerFunc { return func(inv *serpent.Invocation) error { if inv.Environ.Get("AUTH_TOKEN") == "" return fmt.Errorf("AUTH_TOKEN is required") } return next(inv) } } func main() { cmd := serpent.Command{ Use: "process ", Short: "Process a file.", Middleware: serpent.Chain( authMiddleware, timingMiddleware, serpent.RequireNArgs(1), ), Handler: func(inv *serpent.Invocation) error { fmt.Fprintf(inv.Stdout, "processing: %s\n", inv.Args[0]) return nil }, } inv := cmd.Invoke("myfile.txt") inv.Environ = serpent.Environ{} inv.Environ.Set("AUTH_TOKEN", "secret") inv.Stdout = inv.Stderr _ = inv.Run() } ``` -------------------------------- ### Manage and Filter OptionSet Source: https://context7.com/coder/serpent/llms.txt OptionSet provides methods to parse environment variables, filter options by their source, and look up options by name. It can also be marshaled to YAML to generate default configuration files. ```go package main import ( "fmt" "os" "github.com/coder/serpent" ) func main() { var level string globalOpts := serpent.OptionSet{ { Name: "log-level", Flag: "log-level", Env: "LOG_LEVEL", YAML: "log_level", Default: "info", Value: serpent.EnumOf(&level, "debug", "info", "warn", "error"), Description: "Log verbosity level.", }, } // Filter to only options from env source after parsing. _ = globalOpts.ParseEnv(serpent.ParseEnviron(os.Environ(), "")) envOpts := globalOpts.Filter(func(opt serpent.Option) bool { return opt.ValueSource == serpent.ValueSourceEnv }) fmt.Printf("options set via env: %d\n", len(envOpts)) // Look up by name. if opt := globalOpts.ByName("log-level"); opt != nil { fmt.Printf("log-level value: %s (source: %s)\n", opt.Value, opt.ValueSource) } // Marshal to YAML (for writing a default config file). // (requires yaml.Marshal on the OptionSet) // byt, _ := yaml.Marshal(&globalOpts) // fmt.Println(string(byt)) } // LOG_LEVEL=debug ./app // options set via env: 1 // log-level value: debug (source: env) ``` -------------------------------- ### Serpent Option Struct Definition Source: https://github.com/coder/serpent/blob/main/README.md Illustrates the structure of an Option in Serpent, showing fields for name, flag, environment variable, default value, and value binding. ```go type Option struct { Name string Flag string Env string Default string Value pflag.Value // ... } ``` -------------------------------- ### Serpent Typed Option Values Source: https://context7.com/coder/serpent/llms.txt Utilizes Serpent's typed wrappers for pflag.Value to define command-line options with specific data types like strings, integers, durations, and URLs. Demonstrates setting defaults and handling various value types including Enums and Regexp. ```go package main import ( "fmt" "net/url" "regexp" "time" "github.com/coder/serpent" ) func main() { var ( mode string tags []string addr serpent.HostPort rateLimit float64 pattern = new(serpent.Regexp) wait time.Duration apiURL url.URL ) opts := serpent.OptionSet{ {Name: "mode", Flag: "mode", Value: serpent.EnumOf(&mode, "read", "write", "rw"), Default: "read", Description: "Access mode."}, {Name: "tags", Flag: "tags", Value: serpent.StringArrayOf(&tags), Description: "Comma-separated tag list."}, {Name: "addr", Flag: "addr", Value: &addr, Default: "0.0.0.0:8080", Description: "Listen address."}, {Name: "rate", Flag: "rate", Value: serpent.Float64Of(&rateLimit), Default: "100.0", Description: "Max requests per second."}, {Name: "pattern", Flag: "pattern", Value: pattern, Default: ".*", Description: "Filter pattern (regex)."}, {Name: "wait", Flag: "wait", Value: serpent.DurationOf(&wait), Default: "5s", Description: "Startup wait time."}, {Name: "api-url", Flag: "api-url", Value: serpent.URLOf(&apiURL), Default: "https://api.io", Description: "Remote API URL."}, } _ = opts.SetDefaults() fmt.Println("mode:", mode) fmt.Println("addr:", addr.String()) fmt.Println("wait:", wait) fmt.Println("pattern:", pattern.Value().String()) fmt.Println("api-url:", apiURL.String()) // EnumArray example var levels []string ea := serpent.EnumArrayOf(&levels, "debug", "info", "warn", "error") _ = ea.Set("debug,warn") fmt.Println("levels:", levels) // Validator wrapping any value validated := serpent.Validate(serpent.StringOf(new(string)), func(s *serpent.String) error { if len(s.Value()) < 3 { return fmt.Errorf("must be at least 3 characters") } return nil }) _ = validated.Set("hi") _ = regexp.MustCompile } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.