pkg.re/essentialkaos/ek.10@v12.41.0+incompatible/knf/knf.go (about) 1 // Package knf provides methods for working with configuration files in KNF format 2 package knf 3 4 // ////////////////////////////////////////////////////////////////////////////////// // 5 // // 6 // Copyright (c) 2022 ESSENTIAL KAOS // 7 // Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0> // 8 // // 9 // ////////////////////////////////////////////////////////////////////////////////// // 10 11 import ( 12 "errors" 13 "os" 14 "strconv" 15 "strings" 16 "sync" 17 "time" 18 ) 19 20 // ////////////////////////////////////////////////////////////////////////////////// // 21 22 // Config is basic config struct 23 type Config struct { 24 sections []string 25 props []string 26 data map[string]string 27 file string 28 29 mx *sync.RWMutex 30 } 31 32 // Validator is config property validator struct 33 type Validator struct { 34 Property string // Property name 35 Func PropertyValidator // Validation function 36 Value interface{} // Expected value 37 } 38 39 // PropertyValidator is default type of property validation function 40 type PropertyValidator func(config *Config, prop string, value interface{}) error 41 42 // ////////////////////////////////////////////////////////////////////////////////// // 43 44 var ( 45 ErrConfigIsNil = errors.New("Config struct is nil") 46 ErrFileNotSet = errors.New("Path to config file is empty (non initialized struct?)") 47 ) 48 49 // ////////////////////////////////////////////////////////////////////////////////// // 50 51 // global is global configuration file 52 var global *Config 53 54 // ////////////////////////////////////////////////////////////////////////////////// // 55 56 // Global reads and parses configuration file 57 // Global instance is accessible from any part of the code 58 func Global(file string) error { 59 config, err := Read(file) 60 61 if err != nil { 62 return err 63 } 64 65 global = config 66 67 return nil 68 } 69 70 // Read reads and parses configuration file 71 func Read(file string) (*Config, error) { 72 return readKNFFile(file) 73 } 74 75 // Reload reloads global configuration file 76 func Reload() (map[string]bool, error) { 77 if global == nil { 78 return nil, ErrConfigIsNil 79 } 80 81 return global.Reload() 82 } 83 84 // GetS returns configuration value as string 85 func GetS(name string, defvals ...string) string { 86 if global == nil { 87 if len(defvals) == 0 { 88 return "" 89 } 90 91 return defvals[0] 92 } 93 94 return global.GetS(name, defvals...) 95 } 96 97 // GetI returns configuration value as int 98 func GetI(name string, defvals ...int) int { 99 if global == nil { 100 if len(defvals) == 0 { 101 return 0 102 } 103 104 return defvals[0] 105 } 106 107 return global.GetI(name, defvals...) 108 } 109 110 // GetI64 returns configuration value as int64 111 func GetI64(name string, defvals ...int64) int64 { 112 if global == nil { 113 if len(defvals) == 0 { 114 return 0 115 } 116 117 return defvals[0] 118 } 119 120 return global.GetI64(name, defvals...) 121 } 122 123 // GetU returns configuration value as uint 124 func GetU(name string, defvals ...uint) uint { 125 if global == nil { 126 if len(defvals) == 0 { 127 return 0 128 } 129 130 return defvals[0] 131 } 132 133 return global.GetU(name, defvals...) 134 } 135 136 // GetU64 returns configuration value as uint64 137 func GetU64(name string, defvals ...uint64) uint64 { 138 if global == nil { 139 if len(defvals) == 0 { 140 return 0 141 } 142 143 return defvals[0] 144 } 145 146 return global.GetU64(name, defvals...) 147 } 148 149 // GetF returns configuration value as floating number 150 func GetF(name string, defvals ...float64) float64 { 151 if global == nil { 152 if len(defvals) == 0 { 153 return 0.0 154 } 155 156 return defvals[0] 157 } 158 159 return global.GetF(name, defvals...) 160 } 161 162 // GetB returns configuration value as boolean 163 func GetB(name string, defvals ...bool) bool { 164 if global == nil { 165 if len(defvals) == 0 { 166 return false 167 } 168 169 return defvals[0] 170 } 171 172 return global.GetB(name, defvals...) 173 } 174 175 // GetM returns configuration value as file mode 176 func GetM(name string, defvals ...os.FileMode) os.FileMode { 177 if global == nil { 178 if len(defvals) == 0 { 179 return os.FileMode(0) 180 } 181 182 return defvals[0] 183 } 184 185 return global.GetM(name, defvals...) 186 } 187 188 // GetD returns configuration values as duration 189 func GetD(name string, defvals ...time.Duration) time.Duration { 190 if global == nil { 191 if len(defvals) == 0 { 192 return time.Duration(0) 193 } 194 195 return defvals[0] 196 } 197 198 return global.GetD(name, defvals...) 199 } 200 201 // HasSection checks if section exist 202 func HasSection(section string) bool { 203 if global == nil { 204 return false 205 } 206 207 return global.HasSection(section) 208 } 209 210 // HasProp checks if property exist 211 func HasProp(name string) bool { 212 if global == nil { 213 return false 214 } 215 216 return global.HasProp(name) 217 } 218 219 // Sections returns slice with section names 220 func Sections() []string { 221 if global == nil { 222 return nil 223 } 224 225 return global.Sections() 226 } 227 228 // Props returns slice with properties names in some section 229 func Props(section string) []string { 230 if global == nil { 231 return nil 232 } 233 234 return global.Props(section) 235 } 236 237 // Validate executes all given validators and 238 // returns slice with validation errors 239 func Validate(validators []*Validator) []error { 240 if global == nil { 241 return []error{ErrConfigIsNil} 242 } 243 244 return global.Validate(validators) 245 } 246 247 // ////////////////////////////////////////////////////////////////////////////////// // 248 249 // Reload reloads configuration file 250 func (c *Config) Reload() (map[string]bool, error) { 251 if c == nil || c.mx == nil { 252 return nil, ErrConfigIsNil 253 } 254 255 if c.file == "" { 256 return nil, ErrFileNotSet 257 } 258 259 nc, err := Read(c.file) 260 261 if err != nil { 262 return nil, err 263 } 264 265 changes := make(map[string]bool) 266 267 c.mx.RLock() 268 269 for _, prop := range c.props { 270 changes[prop] = c.GetS(prop) != nc.GetS(prop) 271 } 272 273 c.mx.RUnlock() 274 c.mx.Lock() 275 276 // Update current config data 277 c.data, c.sections, c.props = nc.data, nc.sections, nc.props 278 279 c.mx.Unlock() 280 return changes, nil 281 } 282 283 // GetS returns configuration value as string 284 func (c *Config) GetS(name string, defvals ...string) string { 285 if c == nil || c.mx == nil { 286 if len(defvals) == 0 { 287 return "" 288 } 289 290 return defvals[0] 291 } 292 293 c.mx.RLock() 294 val := c.data[strings.ToLower(name)] 295 c.mx.RUnlock() 296 297 if val == "" { 298 if len(defvals) == 0 { 299 return "" 300 } 301 302 return defvals[0] 303 } 304 305 return val 306 } 307 308 // GetI64 returns configuration value as int64 309 func (c *Config) GetI64(name string, defvals ...int64) int64 { 310 if c == nil || c.mx == nil { 311 if len(defvals) == 0 { 312 return 0 313 } 314 315 return defvals[0] 316 } 317 318 c.mx.RLock() 319 val := c.data[strings.ToLower(name)] 320 c.mx.RUnlock() 321 322 if val == "" { 323 if len(defvals) == 0 { 324 return 0 325 } 326 327 return defvals[0] 328 } 329 330 // HEX Parsing 331 if len(val) >= 3 && val[0:2] == "0x" { 332 valHex, err := strconv.ParseInt(val[2:], 16, 0) 333 334 if err != nil { 335 return 0 336 } 337 338 return valHex 339 } 340 341 valInt, err := strconv.ParseInt(val, 10, 0) 342 343 if err != nil { 344 return 0 345 } 346 347 return valInt 348 } 349 350 // GetI returns configuration value as int 351 func (c *Config) GetI(name string, defvals ...int) int { 352 if len(defvals) != 0 { 353 return int(c.GetI64(name, int64(defvals[0]))) 354 } 355 356 return int(c.GetI64(name)) 357 } 358 359 // GetU returns configuration value as uint 360 func (c *Config) GetU(name string, defvals ...uint) uint { 361 if len(defvals) != 0 { 362 return uint(c.GetI64(name, int64(defvals[0]))) 363 } 364 365 return uint(c.GetI64(name)) 366 } 367 368 // GetU64 returns configuration value as uint64 369 func (c *Config) GetU64(name string, defvals ...uint64) uint64 { 370 if len(defvals) != 0 { 371 return uint64(c.GetI64(name, int64(defvals[0]))) 372 } 373 374 return uint64(c.GetI64(name)) 375 } 376 377 // GetF returns configuration value as floating number 378 func (c *Config) GetF(name string, defvals ...float64) float64 { 379 if c == nil || c.mx == nil { 380 if len(defvals) == 0 { 381 return 0.0 382 } 383 384 return defvals[0] 385 } 386 387 c.mx.RLock() 388 val := c.data[strings.ToLower(name)] 389 c.mx.RUnlock() 390 391 if val == "" { 392 if len(defvals) == 0 { 393 return 0.0 394 } 395 396 return defvals[0] 397 } 398 399 valFl, err := strconv.ParseFloat(val, 64) 400 401 if err != nil { 402 return 0.0 403 } 404 405 return valFl 406 } 407 408 // GetB returns configuration value as boolean 409 func (c *Config) GetB(name string, defvals ...bool) bool { 410 if c == nil || c.mx == nil { 411 if len(defvals) == 0 { 412 return false 413 } 414 415 return defvals[0] 416 } 417 418 c.mx.RLock() 419 val := c.data[strings.ToLower(name)] 420 c.mx.RUnlock() 421 422 if val == "" { 423 if len(defvals) == 0 { 424 return false 425 } 426 427 return defvals[0] 428 } 429 430 switch val { 431 case "", "0", "false", "no": 432 return false 433 default: 434 return true 435 } 436 } 437 438 // GetM returns configuration value as file mode 439 func (c *Config) GetM(name string, defvals ...os.FileMode) os.FileMode { 440 if c == nil || c.mx == nil { 441 if len(defvals) == 0 { 442 return os.FileMode(0) 443 } 444 445 return defvals[0] 446 } 447 448 c.mx.RLock() 449 val := c.data[strings.ToLower(name)] 450 c.mx.RUnlock() 451 452 if val == "" { 453 if len(defvals) == 0 { 454 return os.FileMode(0) 455 } 456 457 return defvals[0] 458 } 459 460 valM, err := strconv.ParseUint(val, 8, 32) 461 462 if err != nil { 463 return 0 464 } 465 466 return os.FileMode(valM) 467 } 468 469 // GetD returns configuration value as duration 470 func (c *Config) GetD(name string, defvals ...time.Duration) time.Duration { 471 if c == nil || c.mx == nil { 472 if len(defvals) == 0 { 473 return time.Duration(0) 474 } 475 476 return defvals[0] 477 } 478 479 c.mx.RLock() 480 val := c.data[strings.ToLower(name)] 481 c.mx.RUnlock() 482 483 if val == "" { 484 if len(defvals) == 0 { 485 return time.Duration(0) 486 } 487 488 return defvals[0] 489 } 490 491 return time.Duration(c.GetI64(name)) * time.Second 492 } 493 494 // HasSection checks if section exist 495 func (c *Config) HasSection(section string) bool { 496 if c == nil || c.mx == nil { 497 return false 498 } 499 500 c.mx.RLock() 501 defer c.mx.RUnlock() 502 503 return c.data[strings.ToLower(section)] == "!" 504 } 505 506 // HasProp checks if property exist 507 func (c *Config) HasProp(name string) bool { 508 if c == nil || c.mx == nil { 509 return false 510 } 511 512 c.mx.RLock() 513 defer c.mx.RUnlock() 514 515 return c.data[strings.ToLower(name)] != "" 516 } 517 518 // Sections returns slice with section names 519 func (c *Config) Sections() []string { 520 if c == nil || c.mx == nil { 521 return nil 522 } 523 524 c.mx.RLock() 525 defer c.mx.RUnlock() 526 527 return c.sections 528 } 529 530 // Props returns slice with properties names in some section 531 func (c *Config) Props(section string) []string { 532 var result []string 533 534 if c == nil || !c.HasSection(section) { 535 return result 536 } 537 538 // Section name + delimiter 539 snLength := len(section) + 1 540 541 c.mx.RLock() 542 543 for _, prop := range c.props { 544 if len(prop) <= snLength { 545 continue 546 } 547 548 if prop[:snLength] == section+_PROP_DELIMITER { 549 result = append(result, prop[snLength:]) 550 } 551 } 552 553 defer c.mx.RUnlock() 554 555 return result 556 } 557 558 // Validate executes all given validators and 559 // returns slice with validation errors 560 func (c *Config) Validate(validators []*Validator) []error { 561 if c == nil || c.mx == nil { 562 return []error{ErrConfigIsNil} 563 } 564 565 var result []error 566 567 c.mx.RLock() 568 569 for _, v := range validators { 570 err := v.Func(c, v.Property, v.Value) 571 572 if err != nil { 573 result = append(result, err) 574 } 575 } 576 577 defer c.mx.RUnlock() 578 579 return result 580 } 581 582 // ////////////////////////////////////////////////////////////////////////////////// //