github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/config/config.go (about) 1 package config 2 3 import ( 4 "fmt" 5 "sync" 6 7 mxapp "github.com/lmorg/murex/app" 8 "github.com/lmorg/murex/lang/ref" 9 "github.com/lmorg/murex/lang/types" 10 ) 11 12 // InitConf is a table of global config options 13 var InitConf = newGlobal() 14 15 // Properties is the Config defaults and descriptions 16 type Properties struct { 17 Description string 18 Default interface{} 19 DataType string 20 Options []string 21 Global bool 22 Dynamic DynamicProperties 23 GoFunc GoFuncProperties 24 FileRefDef *ref.File 25 } 26 27 // DynamicProperties is used for dynamic values written in murex 28 type DynamicProperties struct { 29 Read string 30 Write string 31 GetDynamic func() (interface{}, error) `json:"-"` 32 SetDynamic func(interface{}) error `json:"-"` 33 } 34 35 // GoFuncProperties are used for dynamic values written in Go 36 type GoFuncProperties struct { 37 Read func() (interface{}, error) `json:"-"` 38 Write func(interface{}) error `json:"-"` 39 } 40 41 // Config is used to store all the configuration settings, `config`, in a thread-safe API 42 type Config struct { 43 mutex sync.RWMutex 44 properties map[string]map[string]Properties // This will be the main configuration metadata for each configuration option 45 fileRefSet map[string]map[string]*ref.File // This is separate from Properties because it gets updated more frequently (eg custom setters) 46 values map[string]map[string]interface{} // This stores the values when no custom getter and setter have been defined 47 global *Config 48 } 49 50 func newGlobal() *Config { 51 conf := new(Config) 52 conf.properties = make(map[string]map[string]Properties) 53 conf.fileRefSet = make(map[string]map[string]*ref.File) 54 conf.values = make(map[string]map[string]interface{}) 55 return conf 56 } 57 58 func newConfiguration(global *Config) *Config { 59 conf := new(Config) 60 conf.properties = make(map[string]map[string]Properties) 61 conf.fileRefSet = make(map[string]map[string]*ref.File) 62 conf.values = make(map[string]map[string]interface{}) 63 conf.global = global 64 return conf 65 } 66 67 // Copy creates a new *Config instance referenced to the parent 68 func (conf *Config) Copy() *Config { 69 if conf.global == nil { 70 return newConfiguration(conf) 71 } 72 73 return newConfiguration(conf.global) 74 } 75 76 // ExistsAndGlobal checks if a config option exists and/or is global 77 func (conf *Config) ExistsAndGlobal(app, key string) (exists, global bool) { 78 conf.mutex.RLock() 79 exists = conf.properties[app] != nil && conf.properties[app][key].DataType != "" && conf.properties[app][key].Description != "" 80 global = exists && conf.properties[app][key].Global 81 conf.mutex.RUnlock() 82 return 83 } 84 85 // Get retrieves a setting from the Config. Returns an interface{} for the value and err for any failures 86 // 87 // app == tooling name 88 // key == name of setting 89 // dataType == what `types.dataType` to cast the return value into 90 func (conf *Config) Get(app, key, dataType string) (interface{}, error) { 91 v, _, err := conf.GetFileRef(app, key, dataType) 92 return v, err 93 } 94 95 // GetFileRef retrieves a setting from the Config. Returns an interface{} for the value and err for any failures 96 func (conf *Config) GetFileRef(app, key, dataType string) (interface{}, *ref.File, error) { 97 conf.mutex.RLock() 98 99 if conf.global != nil && conf.values[app] != nil && conf.values[app][key] != nil { 100 v := conf.values[app][key] 101 fileRef := conf.fileRefSet[app][key] 102 conf.mutex.RUnlock() 103 value, err := types.ConvertGoType(v, dataType) 104 return value, fileRef, err 105 } 106 107 if conf.properties[app] == nil || conf.properties[app][key].DataType == "" || conf.properties[app][key].Description == "" { 108 conf.mutex.RUnlock() 109 110 if conf.global != nil { 111 return conf.global.GetFileRef(app, key, dataType) 112 } 113 return nil, nil, fmt.Errorf("cannot get config. No config has been defined for app `%s`, key `%s`", app, key) 114 } 115 116 var ( 117 v interface{} 118 err error 119 fileRef = conf.fileRefSet[app][key] 120 ) 121 122 switch { 123 case conf.properties[app][key].Dynamic.GetDynamic != nil: 124 v, err = conf.properties[app][key].Dynamic.GetDynamic() 125 conf.mutex.RUnlock() 126 if err != nil { 127 return nil, nil, err 128 } 129 130 case conf.properties[app][key].GoFunc.Read != nil: 131 v, err = conf.properties[app][key].GoFunc.Read() 132 conf.mutex.RUnlock() 133 if err != nil { 134 return nil, nil, err 135 } 136 137 default: 138 v = conf.values[app][key] 139 if v == nil { 140 v = conf.properties[app][key].Default 141 } 142 conf.mutex.RUnlock() 143 } 144 145 value, err := types.ConvertGoType(v, dataType) 146 return value, fileRef, err 147 } 148 149 // Set changes a setting in the Config object 150 // 151 // app == tooling name 152 // key == name of setting 153 // value == the setting itself 154 func (conf *Config) Set(app string, key string, value interface{}, fileRef *ref.File) error { 155 // first check if we're in a global, and whether we should be 156 if conf.global != nil { 157 exists, global := conf.global.ExistsAndGlobal(app, key) 158 if !exists || global { 159 return conf.global.Set(app, key, value, fileRef) 160 } 161 } 162 163 conf.mutex.Lock() 164 165 if conf.global == nil { 166 if conf.properties[app] == nil || conf.properties[app][key].DataType == "" || conf.properties[app][key].Description == "" { 167 conf.mutex.Unlock() 168 return fmt.Errorf("cannot set config. No config has been defined for app `%s`, key `%s`", app, key) 169 } 170 } 171 172 switch { 173 case conf.properties[app][key].Dynamic.SetDynamic != nil: 174 conf.mutex.Unlock() 175 err := conf.properties[app][key].Dynamic.SetDynamic(value) 176 if err == nil { 177 conf.mutex.Lock() 178 conf.fileRefSet[app][key] = fileRef 179 conf.mutex.Unlock() 180 } 181 return err 182 183 case conf.properties[app][key].GoFunc.Write != nil: 184 conf.mutex.Unlock() 185 err := conf.properties[app][key].GoFunc.Write(value) 186 if err == nil { 187 conf.mutex.Lock() 188 conf.fileRefSet[app][key] = fileRef 189 conf.mutex.Unlock() 190 } 191 return err 192 193 default: 194 if len(conf.values) == 0 { 195 conf.values = make(map[string]map[string]interface{}) 196 conf.fileRefSet = make(map[string]map[string]*ref.File) 197 } 198 if len(conf.values[app]) == 0 { 199 conf.values[app] = make(map[string]interface{}) 200 conf.fileRefSet[app] = make(map[string]*ref.File) 201 } 202 203 conf.fileRefSet[app][key] = fileRef 204 conf.values[app][key] = value 205 206 conf.mutex.Unlock() 207 return nil 208 } 209 } 210 211 // Default resets a config option back to its default 212 func (conf *Config) Default(app string, key string, fileRef *ref.File) error { 213 c := conf.global 214 if c == nil { 215 c = conf 216 } 217 218 exists, _ := c.ExistsAndGlobal(app, key) 219 220 if !exists { 221 return fmt.Errorf("cannot default config. No config has been defined for app `%s`, key `%s`", app, key) 222 } 223 224 c.mutex.RLock() 225 v := c.properties[app][key].Default 226 c.mutex.RUnlock() 227 228 return conf.Set(app, key, v, fileRef) 229 } 230 231 // DataType retrieves the murex data type for a given Config property 232 func (conf *Config) DataType(app, key string) string { 233 if conf.global != nil { 234 return conf.global.DataType(app, key) 235 } 236 237 conf.mutex.Lock() 238 dt := conf.properties[app][key].DataType 239 conf.mutex.Unlock() 240 return dt 241 } 242 243 // Define allows new properties to be created in the Config object 244 func (conf *Config) Define(app string, key string, properties Properties) { 245 if properties.FileRefDef == nil { 246 properties.FileRefDef = ref.NewModule(mxapp.ShellModule) 247 } 248 if conf.global != nil { 249 conf.global.Define(app, key, properties) 250 return 251 } 252 253 conf.mutex.Lock() 254 if conf.properties[app] == nil { 255 conf.properties[app] = make(map[string]Properties) 256 conf.fileRefSet[app] = make(map[string]*ref.File) 257 conf.values[app] = make(map[string]interface{}) 258 } 259 260 // don't set the value to the default if it's a dynamic property 261 if properties.Dynamic.Read == "" && properties.GoFunc.Read == nil { 262 conf.values[app][key] = properties.Default 263 } else { 264 properties.Global = true 265 } 266 conf.properties[app][key] = properties 267 conf.fileRefSet[app][key] = properties.FileRefDef 268 conf.mutex.Unlock() 269 } 270 271 // DumpRuntime returns an object based on Config which is optimised for JSON 272 // serialisation for the `runtime --config` CLI command 273 func (conf *Config) DumpRuntime() (obj map[string]map[string]map[string]interface{}) { 274 if conf.global != nil { 275 return conf.global.DumpRuntime() 276 } 277 278 conf.mutex.RLock() 279 obj = make(map[string]map[string]map[string]interface{}) 280 for app := range conf.properties { 281 obj[app] = make(map[string]map[string]interface{}) 282 for key := range conf.properties[app] { 283 obj[app][key] = make(map[string]interface{}) 284 obj[app][key]["Description"] = conf.properties[app][key].Description 285 obj[app][key]["Data-Type"] = conf.properties[app][key].DataType 286 obj[app][key]["Default"] = conf.properties[app][key].Default 287 obj[app][key]["Value"] = conf.values[app][key] 288 obj[app][key]["FileRefDefined"] = conf.properties[app][key].FileRefDef 289 obj[app][key]["FileRefSet"] = conf.fileRefSet[app][key] 290 291 //if conf.properties[app][key].Global { 292 obj[app][key]["Global"] = conf.properties[app][key].Global 293 //} 294 295 //if len(conf.properties[app][key].Options) != 0 { 296 obj[app][key]["Options"] = conf.properties[app][key].Options 297 //} 298 299 //if len(conf.properties[app][key].Dynamic.Read) != 0 { 300 obj[app][key]["Dynamic"] = conf.properties[app][key].Dynamic 301 //} 302 303 //if conf.properties[app][key].GoFunc.Read != nil { 304 obj[app][key]["GoFunc"] = map[string]bool{ 305 "Read": conf.properties[app][key].GoFunc.Read != nil, 306 "Write": conf.properties[app][key].GoFunc.Write != nil, 307 } 308 //} 309 310 } 311 } 312 conf.mutex.RUnlock() 313 return 314 } 315 316 // DumpConfig returns an object based on Config which is optimised for JSON 317 // serialisation for the `config` CLI command 318 func (conf *Config) DumpConfig() (obj map[string]map[string]map[string]interface{}) { 319 if conf.global != nil { 320 return conf.global.DumpConfig() 321 } 322 323 conf.mutex.RLock() 324 obj = make(map[string]map[string]map[string]interface{}) 325 for app := range conf.properties { 326 obj[app] = make(map[string]map[string]interface{}) 327 for key := range conf.properties[app] { 328 obj[app][key] = make(map[string]interface{}) 329 obj[app][key]["Description"] = conf.properties[app][key].Description 330 obj[app][key]["Data-Type"] = conf.properties[app][key].DataType 331 obj[app][key]["Default"] = conf.properties[app][key].Default 332 333 switch { 334 case conf.properties[app][key].GoFunc.Read != nil: 335 v, err := conf.properties[app][key].GoFunc.Read() 336 if err == nil { 337 obj[app][key]["Value"] = v 338 } 339 case len(conf.properties[app][key].Dynamic.Read) == 0: 340 obj[app][key]["Value"] = conf.values[app][key] 341 } 342 343 obj[app][key]["Global"] = conf.properties[app][key].Global 344 345 if len(conf.properties[app][key].Options) != 0 { 346 obj[app][key]["Options"] = conf.properties[app][key].Options 347 } 348 349 obj[app][key]["Dynamic"] = len(conf.properties[app][key].Dynamic.Read) != 0 350 351 } 352 } 353 conf.mutex.RUnlock() 354 return 355 }