v0.1.3 added unknown cli flag handling, dependency update

This commit is contained in:
2026-07-18 17:42:35 -04:00
parent f4a19aa72a
commit 389121f7ad
5 changed files with 30 additions and 13 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
BSD 3-Clause License BSD 3-Clause License
Copyright (c) 2025, Lixen Wraith Copyright (c) 2026, Lixen Wraith
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
+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 -4
View File
@@ -1,9 +1,9 @@
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= 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.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= 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 ""
} }