2828// import (
2929// "fmt"
3030// "github.com/valentin-kaiser/go-core/config"
31+ // "github.com/valentin-kaiser/go-core/flag"
3132// "github.com/fsnotify/fsnotify"
3233// )
3334//
6061// Port: 8080,
6162// }
6263//
63- // if err := config.Register("server", cfg); err != nil {
64+ // // Register config - path parameter is ignored, flag.Path will be used
65+ // if err := config.Register("", "server", cfg); err != nil {
6466// fmt.Println("Error registering config:", err)
6567// return
6668// }
6769//
70+ // // Parse flags (including --path and config-specific flags)
71+ // flag.Init()
72+ //
73+ // // Read config using the parsed --path flag
6874// if err := config.Read(); err != nil {
6975// fmt.Println("Error reading config:", err)
7076// return
@@ -98,35 +104,68 @@ import (
98104)
99105
100106var (
101- logger = logging .GetPackageLogger ("config" )
102- mutex = & sync.RWMutex {}
107+ logger = logging .GetPackageLogger ("config" )
108+ mutex = & sync.RWMutex {}
109+ cm = & manager {
110+ name : "config" ,
111+ defaults : make (map [string ]interface {}),
112+ values : make (map [string ]interface {}),
113+ flags : make (map [string ]* pflag.Flag ),
114+ }
115+ )
116+
117+ // Config is the interface that all configuration structs must implement
118+ // It should contain a Validate method that checks the configuration for errors
119+ type Config interface {
120+ Validate () error
121+ }
122+
123+ type manager struct {
124+ path string
125+ name string
103126 config Config
104- configName string
105- configPath string
106127 lastChange atomic.Int64
107128 prefix string
108129 defaults map [string ]interface {}
109130 values map [string ]interface {}
110131 flags map [string ]* pflag.Flag
111132 onChange []func (o Config , n Config ) error
112133 watcher * fsnotify.Watcher
113- )
134+ }
114135
115- func init () {
116- defaults = make (map [string ]interface {})
117- values = make (map [string ]interface {})
118- flags = make (map [string ]* pflag.Flag )
136+ func new () * manager {
137+ return & manager {
138+ name : "config" ,
139+ defaults : make (map [string ]interface {}),
140+ values : make (map [string ]interface {}),
141+ flags : make (map [string ]* pflag.Flag ),
142+ }
119143}
120144
121- // Config is the interface that all configuration structs must implement
122- // It should contain a Validate method that checks the configuration for errors
123- type Config interface {
124- Validate () error
145+ func Manager () * manager {
146+ mutex .Lock ()
147+ defer mutex .Unlock ()
148+ return cm
149+ }
150+
151+ func (m * manager ) WithPath (path string ) * manager {
152+ mutex .Lock ()
153+ defer mutex .Unlock ()
154+ m .path = path
155+ return m
156+ }
157+
158+ func (m * manager ) WithName (name string ) * manager {
159+ mutex .Lock ()
160+ defer mutex .Unlock ()
161+ m .name = name
162+ m .prefix = strings .ToUpper (strings .ReplaceAll (name , "-" , "_" ))
163+ return m
125164}
126165
127166// Register registers a configuration struct and parses its tags
128167// The name is used as the name of the configuration file and the prefix for the environment variables
129- func Register ( path , name string , c Config ) error {
168+ func ( m * manager ) Register ( c Config ) error {
130169 if c == nil {
131170 return apperror .NewError ("the configuration provided is nil" )
132171 }
@@ -135,65 +174,66 @@ func Register(path, name string, c Config) error {
135174 return apperror .NewErrorf ("the configuration provided is not a pointer to a struct, got %T" , c )
136175 }
137176
138- mutex .Lock ()
139- configName = name
140- configPath = path
141- prefix = strings .ToUpper (strings .ReplaceAll (configName , "-" , "_" ))
142- mutex .Unlock ()
143-
144- err := parseStructTags (reflect .ValueOf (c ), "" )
177+ err := m .parseStructTags (reflect .ValueOf (c ), "" )
145178 if err != nil {
146179 return apperror .Wrap (err )
147180 }
148181
149- set (c )
182+ m . set (c )
150183 return nil
151184}
152185
153186// OnChange registers a function that is called when the configuration changes
154187func OnChange (f func (o Config , n Config ) error ) {
155- onChange = append (onChange , f )
188+ mutex .Lock ()
189+ defer mutex .Unlock ()
190+ cm .onChange = append (cm .onChange , f )
156191}
157192
158193// Get returns the current configuration
159194func Get () Config {
160195 mutex .RLock ()
161196 defer mutex .RUnlock ()
162- return config
197+ return cm . config
163198}
164199
165200// Read reads the configuration from the file, validates it and applies it
166201// If the file does not exist, it creates a new one with the default values
202+ // The config path is resolved from flag.Path when this function is called
167203func Read () error {
168- setConfigName (configName )
169- addConfigPath (flag .Path )
204+ // Resolve the config path from flag.Path now that flags should be parsed
205+ if cm .path == "" {
206+ mutex .Lock ()
207+ cm .path = flag .Path
208+ mutex .Unlock ()
209+ }
170210
171- err := read ()
211+ err := cm . read ()
172212 if err != nil {
173- err := os .MkdirAll (flag . Path , 0750 )
213+ err := os .MkdirAll (cm . path , 0750 )
174214 if err != nil {
175215 return apperror .NewError ("creating configuration directory failed" ).AddError (err )
176216 }
177217
178- err = save ()
218+ err = cm . save ()
179219 if err != nil {
180220 return apperror .NewError ("writing default configuration file failed" ).AddError (err )
181221 }
182222
183- err = read ()
223+ err = cm . read ()
184224 if err != nil {
185225 return apperror .NewError ("reading configuration file after creation failed" ).AddError (err )
186226 }
187227 }
188228
189- change , ok := reflect .New (reflect .TypeOf (config ).Elem ()).Interface ().(Config )
229+ change , ok := reflect .New (reflect .TypeOf (cm . config ).Elem ()).Interface ().(Config )
190230 if ! ok {
191- return apperror .NewErrorf ("creating new instance of %T failed" , config )
231+ return apperror .NewErrorf ("creating new instance of %T failed" , cm . config )
192232 }
193233
194- err = unmarshal (change )
234+ err = cm . unmarshal (change )
195235 if err != nil {
196- return apperror .NewErrorf ("unmarshalling configuration data in %T failed" , config ).AddError (err )
236+ return apperror .NewErrorf ("unmarshalling configuration data in %T failed" , cm . config ).AddError (err )
197237 }
198238
199239 err = change .Validate ()
@@ -202,8 +242,8 @@ func Read() error {
202242 }
203243
204244 o := Get ()
205- set (change )
206- for _ , f := range onChange {
245+ cm . set (change )
246+ for _ , f := range cm . onChange {
207247 err = f (o , change )
208248 if err != nil {
209249 return apperror .Wrap (err )
@@ -215,42 +255,43 @@ func Read() error {
215255
216256// Write writes the configuration to the file, validates it and applies it
217257// If the file does not exist, it creates a new one with the default values
258+ // The config path is resolved from flag.Path when this function is called
259+ // Write will not trigger any OnChange handlers unless the configuration is Read again
218260func Write (change Config ) error {
219261 if change == nil {
220262 return apperror .NewError ("the configuration provided is nil" )
221263 }
222264
265+ // Resolve the config path from flag.Path if not already set
266+ if cm .path == "" {
267+ mutex .Lock ()
268+ cm .path = flag .Path
269+ mutex .Unlock ()
270+ }
271+
223272 err := change .Validate ()
224273 if err != nil {
225274 return apperror .Wrap (err )
226275 }
227276
228- o := Get ()
229- set (change )
230- err = save ()
277+ cm .set (change )
278+ err = cm .save ()
231279 if err != nil {
232280 return apperror .Wrap (err )
233281 }
234282
235- for _ , f := range onChange {
236- err = f (o , change )
237- if err != nil {
238- return apperror .Wrap (err )
239- }
240- }
241-
242283 return nil
243284}
244285
245- // Watch watches the configuration file for changes and calls the provided function when it changes
286+ // Watch watches the configuration file for changes and calls Read when it changes
246287// It ignores changes that happen within 1 second of each other
247288// This is to prevent multiple calls when the file is saved
248289func Watch () {
249- err := watch (func (_ fsnotify.Event ) {
250- if time .Now ().UnixMilli ()- lastChange .Load () < 1000 {
290+ err := cm . watch (func (_ fsnotify.Event ) {
291+ if time .Now ().UnixMilli ()- cm . lastChange .Load () < 1000 {
251292 return
252293 }
253- lastChange .Store (time .Now ().UnixMilli ())
294+ cm . lastChange .Store (time .Now ().UnixMilli ())
254295 err := Read ()
255296 if err != nil {
256297 logger .Error ().Err (err ).Msg ("failed to read configuration" )
@@ -268,20 +309,12 @@ func Reset() {
268309 mutex .Lock ()
269310 defer mutex .Unlock ()
270311
271- if watcher != nil {
272- watcher .Close ()
273- watcher = nil
312+ if cm . watcher != nil {
313+ cm . watcher .Close ()
314+ cm . watcher = nil
274315 }
275316
276- config = nil
277- configName = ""
278- configPath = ""
279- prefix = ""
280- defaults = make (map [string ]interface {})
281- values = make (map [string ]interface {})
282- flags = make (map [string ]* pflag.Flag )
283- onChange = nil
284- lastChange .Store (0 )
317+ cm = new ()
285318}
286319
287320// Changed checks if two configuration values are different by comparing their reflection values.
@@ -314,8 +347,8 @@ func Changed(o, n any) bool {
314347}
315348
316349// set applies the configuration to the global variable
317- func set (appConfig Config ) {
350+ func ( m * manager ) set (appConfig Config ) {
318351 mutex .Lock ()
319352 defer mutex .Unlock ()
320- config = appConfig
353+ m . config = appConfig
321354}
0 commit comments