v0.1.3 added unknown cli flag handling, dependency update

This commit is contained in:
2026-07-12 13:22:28 -04:00
parent f4a19aa72a
commit f53c004155
4 changed files with 29 additions and 8 deletions
+13 -2
View File
@@ -1,4 +1,3 @@
// FILE: lixenwraith/config/config.go
// Package config provides thread-safe configuration management for Go applications // Package config provides thread-safe configuration management for Go applications
// with support for multiple sources: TOML files, environment variables, command-line // with support for multiple sources: TOML files, environment variables, command-line
// arguments, and default values with configurable precedence. // arguments, and default values with configurable precedence.
@@ -7,6 +6,7 @@ package config
import ( import (
"fmt" "fmt"
"reflect" "reflect"
"slices"
"sync" "sync"
"sync/atomic" "sync/atomic"
) )
@@ -50,6 +50,9 @@ type Config struct {
version atomic.Int64 version atomic.Int64
structCache *structCache structCache *structCache
// CLI paths that matched no registered path (reset on each CLI load)
unknownCLIKeys []string
// File watching support // File watching support
watcher *watcher watcher *watcher
configFilePath string // Track loaded file path configFilePath string // Track loaded file path
@@ -249,6 +252,7 @@ func (c *Config) SetSource(source Source, path string, value any) error {
c.envData[path] = value c.envData[path] = value
case SourceCLI: case SourceCLI:
c.cliData[path] = value c.cliData[path] = value
c.unknownCLIKeys = nil
} }
c.invalidateCache() // Invalidate cache after changes c.invalidateCache() // Invalidate cache after changes
@@ -337,6 +341,13 @@ func (c *Config) AsStruct() (any, error) {
return c.structCache.target, nil return c.structCache.target, nil
} }
// UnknownCLIKeys returns CLI paths that matched no registered config path
func (c *Config) UnknownCLIKeys() []string {
c.mutex.RLock()
defer c.mutex.RUnlock()
return slices.Clone(c.unknownCLIKeys)
}
// computeValue determines the current value based on precedence // computeValue determines the current value based on precedence
func (c *Config) computeValue(item configItem) any { func (c *Config) computeValue(item configItem) any {
// Check sources in precedence order // Check sources in precedence order
@@ -372,4 +383,4 @@ func (c *Config) populateStruct() error {
// invalidateCache override Set methods to invalidate cache // invalidateCache override Set methods to invalidate cache
func (c *Config) invalidateCache() { func (c *Config) invalidateCache() {
c.version.Add(1) c.version.Add(1)
} }
+3 -3
View File
@@ -1,10 +1,10 @@
module github.com/lixenwraith/config module github.com/lixenwraith/config
go 1.25.4 go 1.26.0
require ( require (
github.com/BurntSushi/toml v1.5.0 github.com/BurntSushi/toml v1.6.0
github.com/go-viper/mapstructure/v2 v2.4.0 github.com/go-viper/mapstructure/v2 v2.5.0
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
+4
View File
@@ -1,9 +1,13 @@
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+9 -3
View File
@@ -435,7 +435,7 @@ func (c *Config) loadEnv(opts LoadOptions) error {
transform = defaultEnvTransform(opts.EnvPrefix) transform = defaultEnvTransform(opts.EnvPrefix)
} }
// -- 1. Prepare data (Read-Lock to get paths) // 1. Prepare data (Read-Lock to get paths)
c.mutex.RLock() c.mutex.RLock()
paths := make([]string, 0, len(c.items)) paths := make([]string, 0, len(c.items))
for p := range c.items { for p := range c.items {
@@ -443,7 +443,7 @@ func (c *Config) loadEnv(opts LoadOptions) error {
} }
c.mutex.RUnlock() c.mutex.RUnlock()
// -- 2. Process env vars (No Lock) // 2. Process env vars (No Lock)
foundEnvVars := make(map[string]string) foundEnvVars := make(map[string]string)
for _, path := range paths { for _, path := range paths {
if opts.EnvWhitelist != nil && !opts.EnvWhitelist[path] { if opts.EnvWhitelist != nil && !opts.EnvWhitelist[path] {
@@ -506,6 +506,9 @@ func (c *Config) loadCLI(args []string) error {
c.cliData = flattenedCLI c.cliData = flattenedCLI
// Reset per load; reloads must not accumulate duplicates
c.unknownCLIKeys = c.unknownCLIKeys[:0]
for path, value := range flattenedCLI { for path, value := range flattenedCLI {
if item, exists := c.items[path]; exists { if item, exists := c.items[path]; exists {
if item.values == nil { if item.values == nil {
@@ -514,6 +517,9 @@ func (c *Config) loadCLI(args []string) error {
item.values[SourceCLI] = value item.values[SourceCLI] = value
item.currentValue = c.computeValue(item) item.currentValue = c.computeValue(item)
c.items[path] = item c.items[path] = item
} else {
// Record CLI keys that match no registered path
c.unknownCLIKeys = append(c.unknownCLIKeys, path)
} }
} }
@@ -693,4 +699,4 @@ func detectFormatFromContent(data []byte) string {
} }
return "" return ""
} }