v0.2.0 toml-only, harden concurrency and env export, dep updates
Test / test (push) Canceled after 0s
Test / test (push) Canceled after 0s
This commit is contained in:
1 parent
3c35601179
commit
e08467bf38
42 files changed
+2533
-4274
No files matched your search
+15
-184
@@ -1,188 +1,19 @@
|
||||
# Command Line Arguments
|
||||
# Command-line arguments
|
||||
|
||||
The config package supports command-line argument parsing with flexible formats and automatic type conversion.
|
||||
`LoadCLI` accepts `--server.port=9000`, `--server.port 9000`, and bare boolean flags
|
||||
such as `--debug`. Arguments after `--` are positional and are not parsed. Other
|
||||
positional arguments are ignored. There is no short-flag parser; use Go's flag package
|
||||
when that interface is needed. Paths must obey registration's dot-separated key syntax.
|
||||
|
||||
## Argument Formats
|
||||
Values remain strings and are validated against registered types before publication.
|
||||
Each successful call replaces the complete CLI source, including an empty argument
|
||||
list. Omitted flags lose their previous overrides. Unknown paths are recorded in
|
||||
`UnknownCLIKeys` in sorted order and are not registered. The returned list is a copy.
|
||||
|
||||
### Key-Value Pairs
|
||||
`GenerateFlags` creates a flag.FlagSet from defaults. `BindFlags` atomically replaces
|
||||
the CLI source with explicitly visited flags, rejects unregistered flags, and uses
|
||||
the same checked conversions. Repeated flags resolve to the last value. Invalid
|
||||
values leave the prior CLI source and unknown-key list intact.
|
||||
|
||||
```bash
|
||||
# Space-separated
|
||||
./myapp --server.port 8080 --database.url "postgres://localhost/db"
|
||||
|
||||
# Equals-separated
|
||||
./myapp --server.port=8080 --database.url=postgres://localhost/db
|
||||
|
||||
# Mixed formats
|
||||
./myapp --server.port 8080 --debug=true
|
||||
```
|
||||
|
||||
### Boolean Flags
|
||||
|
||||
```bash
|
||||
# Boolean flags don't require a value (assumed true)
|
||||
./myapp --debug --verbose
|
||||
|
||||
# Explicit boolean values
|
||||
./myapp --debug=true --verbose=false
|
||||
```
|
||||
|
||||
### Nested Paths
|
||||
|
||||
Use dot notation for nested configuration:
|
||||
|
||||
```bash
|
||||
./myapp --server.host=0.0.0.0 --server.port=9090 --server.tls.enabled=true
|
||||
```
|
||||
|
||||
## Type Conversion
|
||||
|
||||
Command-line values are automatically converted to match registered types:
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
Port int64 `toml:"port"`
|
||||
Timeout time.Duration `toml:"timeout"`
|
||||
Ratio float64 `toml:"ratio"`
|
||||
Enabled bool `toml:"enabled"`
|
||||
Tags []string `toml:"tags"`
|
||||
}
|
||||
|
||||
// All these are parsed correctly:
|
||||
// --port=8080 → int64(8080)
|
||||
// --timeout=30s → time.Duration(30 * time.Second)
|
||||
// --ratio=0.95 → float64(0.95)
|
||||
// --enabled=true → bool(true)
|
||||
// --tags=prod,stable → []string{"prod", "stable"}
|
||||
```
|
||||
|
||||
## Integration with flag Package
|
||||
|
||||
### Generate flag.FlagSet
|
||||
|
||||
```go
|
||||
// Generate flags from registered configuration
|
||||
fs := cfg.GenerateFlags()
|
||||
|
||||
// Parse command line
|
||||
if err := fs.Parse(os.Args[1:]); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Apply parsed flags to configuration
|
||||
if err := cfg.BindFlags(fs); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Flag Registration
|
||||
|
||||
```go
|
||||
fs := flag.NewFlagSet("myapp", flag.ContinueOnError)
|
||||
|
||||
// Add custom flags
|
||||
verbose := fs.Bool("v", false, "verbose output")
|
||||
configFile := fs.String("config", "config.toml", "config file path")
|
||||
|
||||
// Parse
|
||||
fs.Parse(os.Args[1:])
|
||||
|
||||
// Use custom flags
|
||||
if *verbose {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
|
||||
// Load config with custom file path
|
||||
cfg, _ := config.NewBuilder().
|
||||
WithFile(*configFile).
|
||||
Build()
|
||||
|
||||
// Bind remaining flags
|
||||
cfg.BindFlags(fs)
|
||||
```
|
||||
|
||||
## Precedence and Overrides
|
||||
|
||||
Command-line arguments have the highest precedence by default:
|
||||
|
||||
```go
|
||||
// Default precedence: CLI > Env > File > Default
|
||||
cfg, _ := config.Quick(defaults, "APP_", "config.toml")
|
||||
|
||||
// Even if config.toml sets port=8080 and APP_PORT=9090,
|
||||
// --port=7070 will win
|
||||
```
|
||||
|
||||
Change precedence if needed:
|
||||
|
||||
```go
|
||||
cfg, _ := config.NewBuilder().
|
||||
WithSources(
|
||||
config.SourceEnv, // Env highest
|
||||
config.SourceCLI, // Then CLI
|
||||
config.SourceFile, // Then file
|
||||
config.SourceDefault, // Finally defaults
|
||||
).
|
||||
Build()
|
||||
```
|
||||
|
||||
## Argument Parsing Details
|
||||
|
||||
### Validation
|
||||
|
||||
- Paths must use valid identifiers (letters, numbers, underscore, dash)
|
||||
- No leading/trailing dots in paths
|
||||
- Empty segments not allowed (no `..` in paths)
|
||||
|
||||
### Special Cases
|
||||
|
||||
```bash
|
||||
# Double dash stops flag parsing
|
||||
./myapp --port=8080 -- --not-a-flag
|
||||
|
||||
# Single dash flags are ignored (not GNU-style)
|
||||
./myapp -p 8080 # Ignored, use --port
|
||||
|
||||
# Quoted values preserve spaces
|
||||
./myapp --message="Hello World" --name='John Doe'
|
||||
|
||||
# Escape quotes in values
|
||||
./myapp --json="{\"key\": \"value\"}"
|
||||
```
|
||||
|
||||
### Value Parsing Rules
|
||||
|
||||
1. **Booleans**: `true`, `false` (case-sensitive)
|
||||
2. **Numbers**: Standard decimal notation
|
||||
3. **Strings**: Quoted or unquoted (quotes removed if present)
|
||||
4. **Lists**: Comma-separated (when target type is slice)
|
||||
|
||||
## Override Arguments
|
||||
|
||||
```go
|
||||
// Parse custom arguments instead of os.Args
|
||||
customArgs := []string{"--debug", "--port=9090"}
|
||||
|
||||
cfg, _ := config.NewBuilder().
|
||||
WithArgs(customArgs).
|
||||
Build()
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
CLI parsing errors are returned from `Build()` or `LoadCLI()`:
|
||||
|
||||
```go
|
||||
cfg, err := config.NewBuilder().
|
||||
WithDefaults(&Config{}).
|
||||
Build()
|
||||
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, config.ErrCLIParse):
|
||||
log.Fatal("Invalid command line arguments:", err)
|
||||
default:
|
||||
log.Fatal("Configuration error:", err)
|
||||
}
|
||||
}
|
||||
```
|
||||
The Builder reads os.Args by default. Use `WithArgs(nil)` to disable CLI loading for
|
||||
that build or supply an explicit argument slice for deterministic initialization.
|
||||
Reference in new issue
Block a user