Documentation
¶
Overview ¶
Package cli provides utilities for building CLI applications with Cobra and Viper.
It simplifies the creation of command-line applications by integrating Cobra's command framework with Viper's configuration management, providing sensible defaults for:
- Configuration loading from files, environment variables, and command-line flags - Type-safe command execution using Go generics - Structured logging via zerolog - Path management with Docker support - Context propagation for application metadata
Basic Usage ¶
Create a configuration struct:
type ServerConfig struct {
Port int `mapstructure:"port"`
Host string `mapstructure:"host"`
}
Create a command handler:
func serveCommand(ctx context.Context, cfg *ServerConfig) error {
fmt.Printf("Starting server on %s:%d\n", cfg.Host, cfg.Port)
return nil
}
Register the command with NewCommand:
cfg := &ServerConfig{Port: 8080, Host: "localhost"}
cmd := cli.NewCommand(
&cobra.Command{
Use: "serve",
Short: "Start the server",
},
serveCommand,
cfg,
cli.WithConfigFlag("config.yaml"),
)
Configuration File Format ¶
Configuration files are YAML with support for multiple sections:
port: 8080 host: localhost log: level: debug file: "~/.app/logs/app.log" max-size: 100 compress: true
Configuration Loading Order ¶
Configurations are loaded and merged in this order (last wins):
1. Config file at /etc/{org}/{app}/config.yaml 2. Config file at $HOME/.config/{org}/{app}/config.yaml 3. Command-line flags 4. Environment variables with prefix {APPNAME}_ 5. Config file specified by --config flag
Context Management ¶
Store application metadata in context for propagation through command execution:
ctx := cli.Context(
context.Background(),
cli.SetOrgName("myorg"),
cli.SetAppName("myapp"),
)
cmd.SetContext(ctx)
Advanced Features ¶
Path helpers detect and adapt to Docker environments:
configPath, err := cli.DefaultConfigPath("myorg", "myapp")
// Returns /config in Docker, $HOME/.config/myorg/myapp otherwise
Error handling patterns follow Go best practices with wrapped errors:
err := cli.InitViperConfig("org", "app", cfg)
if err != nil {
return fmt.Errorf("initialization failed: %w", err)
}
See Also ¶
Package logging: provides structured logging configuration via zerolog.
Index ¶
- func AppNameFromContext(ctx context.Context) string
- func CobraRunE[T any](execFunc func(*T) error, opt ...CobraOpt[T]) func(cmd *cobra.Command, args []string) error
- func CobraRunEWithConfig[T any](execFunc func(context.Context, *T) error, cfg *T) func(cmd *cobra.Command, args []string) error
- func Context(ctx context.Context, contextOpts ...ContextOpt) context.Context
- func DefaultConfigFile(orgName, appName, baseName string) (string, error)
- func DefaultConfigPath(orgName, appName string) (string, error)
- func DefaultPersistenceFile(orgName, appName, baseName string) (string, error)
- func DefaultPersistencePath(orgName, appName string) (string, error)
- func DefaultUserConfigPath(orgName, appName string) (string, error)
- func InitViperConfig(orgName, appName string, cfg interface{}) error
- func InitViperConfigWithFlagSet(orgName, appName string, cfg interface{}, parsedFlagSet *pflag.FlagSet) error
- func IsDocker() bool
- func NewCommand[T any](cmd *cobra.Command, runFunc func(context.Context, *T) error, defaultConfig *T, ...) *cobra.Command
- func OrgNameFromContext(ctx context.Context) string
- func UnmarshalConfig(v *viper.Viper, c interface{}) error
- func ValidateName(name string) error
- func ValidateOrgAndAppName(orgName, appName string) error
- func WithConfigFlag(defaultConfigFile string) func(*cobra.Command)
- type CobraOpt
- type CommandOpt
- type CommonConfig
- type Config
- type ContextOpt
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AppNameFromContext ¶ added in v0.7.0
AppNameFromContext retrieves the application name from the context.
func CobraRunE ¶
func CobraRunE[T any](execFunc func(*T) error, opt ...CobraOpt[T]) func(cmd *cobra.Command, args []string) error
CobraRunE returns a Cobra RunE function with configuration management and functional options.
The returned function handles configuration initialization with org and app names from context, applies functional options to modify configuration, and passes configured values to the execution function.
func CobraRunEWithConfig ¶
func CobraRunEWithConfig[T any](execFunc func(context.Context, *T) error, cfg *T) func(cmd *cobra.Command, args []string) error
CobraRunEWithConfig returns a Cobra RunE function that loads configuration before execution.
The returned function handles configuration loading, application metadata retrieval, and passes configured values to the execution function.
func Context ¶
func Context(ctx context.Context, contextOpts ...ContextOpt) context.Context
Context creates a new context with optional application metadata.
It accepts functional options to populate the context with organization and app names.
Example ¶
ExampleContext demonstrates creating a context with application metadata.
package main
import (
"context"
"fmt"
"github.com/dioad/cli"
)
func main() {
ctx := cli.Context(
context.Background(),
cli.SetOrgName("myorg"),
cli.SetAppName("myapp"),
)
if ctx != nil {
fmt.Println("Context created with metadata")
}
}
Output: Context created with metadata
func DefaultConfigFile ¶
DefaultConfigFile returns the full path to the default configuration file.
The file is placed in DefaultConfigPath and named {baseName}.yaml.
Example ¶
ExampleDefaultConfigFile demonstrates getting a default config file path.
package main
import (
"fmt"
"github.com/dioad/cli"
)
func main() {
file, err := cli.DefaultConfigFile("myorg", "myapp", "config")
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
if file != "" {
fmt.Println("Config file path obtained successfully")
}
}
Output: Config file path obtained successfully
func DefaultConfigPath ¶
DefaultConfigPath returns the default directory for configuration files.
For Docker containers, this returns /config. For other environments, it returns $HOME/.config/{orgName}/{appName}.
Example ¶
ExampleDefaultConfigPath demonstrates getting the default config path.
package main
import (
"fmt"
"github.com/dioad/cli"
)
func main() {
path, err := cli.DefaultConfigPath("myorg", "myapp")
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
if path != "" {
fmt.Println("Config path obtained successfully")
}
}
Output: Config path obtained successfully
func DefaultPersistenceFile ¶
DefaultPersistenceFile returns the full path to a persistence file.
The file is placed in DefaultPersistencePath and named {baseName}.yaml.
func DefaultPersistencePath ¶
DefaultPersistencePath returns the default directory for persistent application data.
For Docker containers, this returns /persist. For other environments, it returns the same as DefaultUserConfigPath.
Example ¶
ExampleDefaultPersistencePath demonstrates getting the persistence path.
package main
import (
"fmt"
"github.com/dioad/cli"
)
func main() {
path, err := cli.DefaultPersistencePath("myorg", "myapp")
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
if path != "" {
fmt.Println("Persistence path obtained successfully")
}
}
Output: Persistence path obtained successfully
func DefaultUserConfigPath ¶
DefaultUserConfigPath returns the default configuration directory for the user.
For non-Docker environments, it returns $HOME/.config/{orgName}/{appName} and creates the directory if it doesn't exist with 0700 permissions.
func InitViperConfig ¶
InitViperConfig initializes Viper configuration management with parsed command-line flags.
It sets up Viper to: - Bind command-line flags - Search for configuration files in standard locations - Support environment variables with the given appName prefix - Unmarshal configuration into the provided cfg struct
Configuration sources are merged with this precedence (highest to lowest): 1. Command-line flags 2. Explicit config file (--config flag) 3. Environment variables (prefixed with appName) 4. Config files in standard locations
func InitViperConfigWithFlagSet ¶
func InitViperConfigWithFlagSet(orgName, appName string, cfg interface{}, parsedFlagSet *pflag.FlagSet) error
InitViperConfigWithFlagSet initializes Viper with a custom FlagSet.
Similar to InitViperConfig but allows specifying a custom pflag.FlagSet instead of using the global command line flags. Useful for embedding configuration initialization in library code or tests.
func IsDocker ¶
func IsDocker() bool
IsDocker detects if the application is running inside a Docker container.
It checks for the presence of /.dockerenv file, which is a standard indicator that the process is running in a Docker container.
Example ¶
ExampleIsDocker demonstrates Docker detection.
package main
import (
"fmt"
"github.com/dioad/cli"
)
func main() {
inDocker := cli.IsDocker()
if inDocker {
fmt.Println("Running in Docker")
} else {
fmt.Println("Running on host machine")
}
}
Output: Running on host machine
func NewCommand ¶
func NewCommand[T any](cmd *cobra.Command, runFunc func(context.Context, *T) error, defaultConfig *T, opts ...CommandOpt) *cobra.Command
NewCommand creates a new Cobra command with type-safe configuration handling.
It wraps the provided Cobra command with automatic configuration loading, populating flags from the config struct, and executing the command with configuration management.
Example ¶
ExampleNewCommand demonstrates creating a type-safe command with configuration.
package main
import (
"context"
"fmt"
"github.com/dioad/cli"
"github.com/spf13/cobra"
)
func main() {
type Config struct {
Name string `mapstructure:"name"`
Port int `mapstructure:"port"`
}
cfg := &Config{
Name: "example-service",
Port: 8080,
}
cmd := cli.NewCommand(
&cobra.Command{
Use: "serve",
Short: "Start the service",
},
func(ctx context.Context, c *Config) error {
fmt.Printf("Service %s listening on port %d\n", c.Name, c.Port)
return nil
},
cfg,
cli.WithConfigFlag("config.yaml"),
)
if cmd != nil {
fmt.Println("Command created successfully")
}
}
Output: Command created successfully
func OrgNameFromContext ¶ added in v0.7.0
OrgNameFromContext retrieves the organization name from the context.
func UnmarshalConfig ¶ added in v0.7.0
UnmarshalConfig unmarshals Viper configuration into the provided struct with custom decode hooks.
It uses a composed decode hook to handle special types like MaskedString, time.Duration, net.IP, and net.IPNet. This allows for seamless unmarshalling of complex configuration fields.
func ValidateName ¶ added in v0.7.0
ValidateName checks that the provided name is valid for use in configuration paths.
It ensures that the name is not empty, does not contain path separators, does not start or end with spaces, and does not contain special characters. This validation helps prevent directory traversal issues and ensures clean config paths.
func ValidateOrgAndAppName ¶ added in v0.7.0
ValidateOrgAndAppName validates that orgName and appName are acceptable names.
It delegates to ValidateName, which ensures that names are non-empty, do not contain path separators, spaces (including leading or trailing spaces), or various special characters. This helps prevent issues when constructing configuration paths and other filesystem-related operations.
func WithConfigFlag ¶
WithConfigFlag adds a --config/-c flag to the command for specifying a config file path.
Example ¶
ExampleWithConfigFlag demonstrates adding a config file flag to a command.
package main
import (
"fmt"
"github.com/dioad/cli"
"github.com/spf13/cobra"
)
func main() {
cmd := &cobra.Command{Use: "serve"}
opt := cli.WithConfigFlag("~/.app/config.yaml")
opt(cmd)
configFlag := cmd.Flag("config")
if configFlag != nil {
fmt.Printf("Config flag default: %s\n", configFlag.DefValue)
}
}
Output: Config flag default: ~/.app/config.yaml
Types ¶
type CobraOpt ¶
type CobraOpt[T any] func(*T)
CobraOpt is a functional option for configuring command execution.
type CommandOpt ¶
CommandOpt is a functional option for customizing a Cobra command.
type CommonConfig ¶
type CommonConfig struct {
// Config string `mapstructure:"config"`
Logging logging.Config `mapstructure:"log"`
}
CommonConfig contains configuration shared across all applications.
It includes logging configuration and can be extended in application-specific config structs via embedding.
func InitConfig ¶
func InitConfig(orgName, appName string, cmd *cobra.Command, cfgFile string, cfg interface{}) (*CommonConfig, error)
InitConfig loads and initializes configuration from multiple sources.
It integrates Cobra commands with Viper configuration management, supporting: - Hierarchical command-based config file naming - Flag binding from the Cobra command - Environment variable overrides - Automatic logging configuration - Configuration hot-reloading via Viper watchers
type Config ¶
type Config[T any] struct { CommonConfig Config *T }
type ContextOpt ¶
ContextOpt is a functional option for building a context with application metadata.
func SetAppName ¶
func SetAppName(appName string) ContextOpt
SetAppName returns a ContextOpt that stores the application name in the context.
Example ¶
ExampleSetAppName demonstrates setting app name in context.
package main
import (
"context"
"fmt"
"github.com/dioad/cli"
)
func main() {
opt := cli.SetAppName("myservice")
ctx := opt(context.Background())
if ctx != nil {
fmt.Println("App name set in context")
}
}
Output: App name set in context
func SetOrgName ¶
func SetOrgName(orgName string) ContextOpt
SetOrgName returns a ContextOpt that stores the organization name in the context.
Example ¶
ExampleSetOrgName demonstrates setting organization name in context.
package main
import (
"context"
"fmt"
"github.com/dioad/cli"
)
func main() {
opt := cli.SetOrgName("acme-corp")
ctx := opt(context.Background())
if ctx != nil {
fmt.Println("Organization name set in context")
}
}
Output: Organization name set in context