github.com/codingeasygo/util@v0.0.0-20231206062002-1ce2f004b7d9/xprop/xprop.go (about) 1 package xprop 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "net/http" 10 "net/url" 11 "os" 12 "path" 13 "path/filepath" 14 "regexp" 15 "sort" 16 "strconv" 17 "strings" 18 "time" 19 20 "github.com/codingeasygo/util/converter" 21 "github.com/codingeasygo/util/xmap" 22 ) 23 24 type sconf map[string]string 25 26 func (s sconf) autoPath(path ...string) (all []string) { 27 for _, p := range path { 28 if strings.Contains(p, "/") { 29 p = strings.Trim(p, "/") 30 all = append(all, p) 31 continue 32 } 33 all = append(all, p, "loc/"+p) 34 } 35 return 36 } 37 38 //ValueVal will get value by path 39 func (s sconf) ValueVal(path ...string) (v interface{}, err error) { 40 ps := s.autoPath(path...) 41 for _, p := range ps { 42 val, ok := s[p] 43 if ok { 44 v = val 45 break 46 } 47 } 48 return 49 } 50 51 //SetValue will set value to path 52 func (s sconf) SetValue(path string, val interface{}) (err error) { 53 path = strings.TrimPrefix(path, "/") 54 path = strings.TrimSpace(path) 55 s[path] = converter.String(val) 56 return 57 } 58 59 //Delete will delete value on path 60 func (s sconf) Delete(path string) (err error) { 61 delete(s, path) 62 return 63 } 64 65 //Clear will clear all key on map 66 func (s sconf) Clear() (err error) { 67 for key := range s { 68 delete(s, key) 69 } 70 return 71 } 72 73 //Length will return value count 74 func (s sconf) Length() (l int) { 75 l = len(s) 76 return 77 } 78 79 //Exist will check path whether exist 80 func (s sconf) Exist(path ...string) (ok bool) { 81 for _, p := range path { 82 _, ok = s[p] 83 if ok { 84 break 85 } 86 } 87 return 88 } 89 90 //Config is parser for properties file 91 type Config struct { 92 xmap.Valuable 93 config sconf 94 ShowLog bool 95 sec string 96 Lines []string 97 Seces []string 98 SecLn map[string]int 99 Base string 100 Masks map[string]string 101 } 102 103 //NewConfig will return new config 104 func NewConfig() (config *Config) { 105 config = &Config{ 106 config: sconf{}, 107 ShowLog: true, 108 SecLn: map[string]int{}, 109 Masks: map[string]string{}, 110 } 111 config.Valuable, _ = xmap.Parse(config.config) 112 return 113 } 114 115 //LoadConf will load new config by uri 116 func LoadConf(uri string) (config *Config, err error) { 117 config, err = LoadConfWait(uri, true) 118 return 119 } 120 121 //LoadConfWait will load new config by uri 122 func LoadConfWait(uri string, wait bool) (config *Config, err error) { 123 config = NewConfig() 124 err = config.LoadWait(uri, wait) 125 return 126 } 127 128 func (c *Config) slog(fs string, args ...interface{}) { 129 if c.ShowLog { 130 fmt.Println(fmt.Sprintf(fs, args...)) 131 } 132 } 133 134 //FileModeDef read file mode value 135 func (c *Config) FileModeDef(def os.FileMode, path ...string) (mode os.FileMode) { 136 mode, err := c.FileModeVal(path...) 137 if err != nil { 138 mode = def 139 } 140 return 141 } 142 143 //FileModeVal read file mode value 144 func (c *Config) FileModeVal(path ...string) (mode os.FileMode, err error) { 145 data, err := c.StrVal(path...) 146 if err != nil { 147 return 148 } 149 data = strings.TrimSpace(data) 150 val, err := strconv.ParseUint(data, 8, 32) 151 if err != nil { 152 return 153 } 154 mode = os.FileMode(val) 155 return 156 } 157 158 //Print all configure 159 func (c *Config) Print() { 160 fmt.Println(c.String()) 161 } 162 163 //PrintSection print session to stdout 164 func (c *Config) PrintSection(section string) { 165 mask := map[*regexp.Regexp]*regexp.Regexp{} 166 for k, v := range c.Masks { 167 mask[regexp.MustCompile(k)] = regexp.MustCompile(v) 168 } 169 sdata := "" 170 for k, v := range c.config { 171 if !strings.HasPrefix(k, section) { 172 continue 173 } 174 val := fmt.Sprintf("%v", v) 175 for maskKey, maskVal := range mask { 176 if maskKey.MatchString(k) { 177 val = maskVal.ReplaceAllString(val, "***") 178 } 179 } 180 val = strings.Replace(val, "\n", "\\n", -1) 181 sdata = fmt.Sprintf("%v %v=%v\n", sdata, k, val) 182 } 183 fmt.Println(sdata) 184 } 185 186 //Range the section key-value by callback 187 func (c *Config) Range(section string, callback func(key string, val interface{})) { 188 for k, v := range c.config { 189 if strings.HasPrefix(k, section) { 190 callback(strings.TrimPrefix(k, section+"/"), v) 191 } 192 } 193 } 194 195 func (c *Config) exec(base, line string, wait bool) error { 196 ps := strings.Split(line, "#") 197 if len(ps) < 1 || len(ps[0]) < 1 { 198 return nil 199 } 200 line = strings.TrimSpace(ps[0]) 201 if len(line) < 1 { 202 return nil 203 } 204 if regexp.MustCompile("^\\[[^\\]]*\\][\t ]*$").MatchString(line) { 205 sec := strings.Trim(line, "\t []") 206 c.sec = sec + "/" 207 c.Seces = append(c.Seces, sec) 208 c.SecLn[sec] = len(c.Lines) 209 return nil 210 } 211 if !strings.HasPrefix(line, "@") { 212 ps = strings.SplitN(line, "=", 2) 213 if len(ps) < 2 { 214 c.slog("not value key found:%v", ps[0]) 215 } else { 216 key := c.sec + c.EnvReplace(strings.Trim(ps[0], " ")) 217 val := c.EnvReplace(strings.Trim(ps[1], " ")) 218 c.config[key] = val 219 } 220 return nil 221 } 222 line = strings.TrimPrefix(line, "@") 223 ps = strings.SplitN(line, ":", 2) 224 if len(ps) < 2 || len(ps[1]) < 1 { 225 c.slog("%v", c.EnvReplace(line)) 226 return nil 227 } 228 ps[0] = strings.ToLower(strings.Trim(ps[0], " \t")) 229 ps[0] = c.EnvReplace(ps[0]) 230 if ps[0] == "l" { 231 ps[1] = strings.Trim(ps[1], " \t") 232 return c.load(base, ps[1], wait) 233 } 234 if cs := strings.SplitN(ps[0], "==", 2); len(cs) == 2 { 235 if cs[0] == cs[1] { 236 return c.exec(base, ps[1], wait) 237 } 238 return nil 239 } 240 if cs := strings.SplitN(ps[0], "!=", 2); len(cs) == 2 { 241 if cs[0] != cs[1] { 242 return c.exec(base, ps[1], wait) 243 } 244 return nil 245 } 246 //all other will print line. 247 c.slog("%v", c.EnvReplace(line)) 248 return nil 249 } 250 251 func (c *Config) load(base, line string, wait bool) error { 252 line = c.EnvReplaceEmpty(line, true) 253 line = strings.Trim(line, "\t ") 254 if len(line) < 1 { 255 return nil 256 } 257 if !(strings.HasPrefix(line, "http://") || strings.HasPrefix(line, "https://") || filepath.IsAbs(line)) { 258 line = path.Join(base, line) 259 } 260 config := NewConfig() 261 err := config.LoadWait(line, wait) 262 if err == nil { 263 c.Merge(config) 264 } 265 return err 266 } 267 268 //Load will load config by uri 269 func (c *Config) Load(uri string) error { 270 return c.LoadWait(uri, false) 271 } 272 273 //LoadWait will load config by uri and wait when uri is not found 274 func (c *Config) LoadWait(uri string, wait bool) error { 275 if strings.HasPrefix(uri, "http://") { 276 return c.LoadWebWait(uri, wait) 277 } else if strings.HasPrefix(uri, "https://") { 278 return c.LoadWebWait(uri, wait) 279 } else if strings.HasPrefix(uri, "data:text/conf,") { 280 return c.LoadConfString(strings.TrimPrefix(uri, "data:text/conf,")) 281 } else if strings.HasPrefix(uri, "data:text/prop,") { 282 return c.LoadPropStringWait(strings.TrimPrefix(uri, "data:text/prop,"), wait) 283 } else { 284 return c.LoadFileWait(uri, wait) 285 } 286 } 287 288 //LoadFile will load the configure by .properties file. 289 func (c *Config) LoadFile(filename string) error { 290 return c.LoadFileWait(filename, true) 291 } 292 293 //LoadFileWait will load the configure by .properties file and wait when file not exist 294 func (c *Config) LoadFileWait(filename string, wait bool) error { 295 c.slog("loading local configure->%v", filename) 296 var parts = strings.Split(filename, "?") 297 filename = parts[0] 298 if len(parts) > 1 { 299 furl, err := url.Parse("/?" + parts[1]) 300 if err == nil { 301 query := furl.Query() 302 for k := range query { 303 c.config[k] = query.Get(k) 304 } 305 } 306 } 307 var delay = 50 * time.Millisecond 308 for { 309 _, xerr := os.Stat(filename) 310 if xerr == nil { 311 break 312 } 313 if wait { 314 c.slog("file(%v) not found", filename) 315 if delay < 2*time.Second { 316 delay *= 2 317 } 318 time.Sleep(delay) 319 continue 320 } else { 321 return fmt.Errorf("file(%v) not found", filename) 322 } 323 } 324 file, err := os.Open(filename) 325 if err != nil { 326 return err 327 } 328 defer file.Close() 329 dir, _ := filepath.Split(filename) 330 if len(dir) < 1 { 331 dir = "." 332 } 333 dir, _ = filepath.Abs(dir) 334 if strings.HasSuffix(filename, ".conf") { 335 return c.LoadConfReader(dir, file) 336 } 337 return c.LoadPropReaderWait(dir, file, wait) 338 } 339 340 //LoadPropReader will load properties config by reader 341 func (c *Config) LoadPropReader(base string, reader io.Reader) error { 342 return c.LoadPropReaderWait(base, reader, true) 343 } 344 345 //LoadPropReaderWait will load properties config by reader 346 func (c *Config) LoadPropReaderWait(base string, reader io.Reader, wait bool) error { 347 if len(base) > 0 { 348 c.Base = base 349 } 350 buf := bufio.NewReaderSize(reader, 64*1024) 351 for { 352 //read one line 353 line, err := readLine(buf) 354 if err != nil { 355 break 356 } 357 c.Lines = append(c.Lines, line) 358 line = strings.TrimSpace(line) 359 if len(line) < 1 { 360 continue 361 } 362 err = c.exec(base, line, wait) 363 if err != nil { 364 return err 365 } 366 } 367 return nil 368 } 369 370 //LoadConfReader will load conf config by reader 371 func (c *Config) LoadConfReader(base string, reader io.Reader) error { 372 var key, val string 373 buf := bufio.NewReaderSize(reader, 64*1024) 374 for { 375 //read one line 376 line, err := readLine(buf) 377 if err != nil { 378 if len(key) > 0 { 379 c.config[key] = strings.Trim(val, "\n") 380 key, val = "", "" 381 } 382 break 383 } 384 if regexp.MustCompile("^\\[[^\\]]*\\][\t ]*$").MatchString(line) { 385 sec := strings.Trim(line, "\t []") 386 if len(key) > 0 { 387 c.config[key] = strings.Trim(val, "\n") 388 key, val = "", "" 389 } 390 key = sec 391 } else { 392 val += line + "\n" 393 } 394 } 395 return nil 396 } 397 398 func (c *Config) webGet(url string) (data string, err error) { 399 req, err := http.NewRequest("GET", url, nil) 400 if err != nil { 401 return 402 } 403 res, err := http.DefaultClient.Do(req) 404 if err != nil { 405 return 406 } 407 defer res.Body.Close() 408 if res.StatusCode != 200 { 409 err = fmt.Errorf("status code(%v)", res.StatusCode) 410 return 411 } 412 bys, err := ioutil.ReadAll(res.Body) 413 if err == nil { 414 data = string(bys) 415 } 416 return 417 } 418 419 //LoadWeb will load the configure by network .properties URL. 420 func (c *Config) LoadWeb(remote string) error { 421 return c.LoadWebWait(remote, true) 422 } 423 424 //LoadWebWait will load the configure by network .properties URL. 425 func (c *Config) LoadWebWait(remote string, wait bool) (err error) { 426 c.slog("loading remote configure->%v", remote) 427 var data string 428 var delay = 50 * time.Millisecond 429 for { 430 data, err = c.webGet(remote) 431 if err == nil { 432 c.slog("loading remote configure(%v) success", remote) 433 break 434 } 435 c.slog("loading remote configure(%v):%v", remote, err.Error()) 436 if wait { 437 if delay < 2*time.Second { 438 delay *= 2 439 } 440 time.Sleep(delay) 441 continue 442 } else { 443 break 444 } 445 } 446 if err != nil { 447 return 448 } 449 var filename string 450 rurl, _ := url.Parse(remote) 451 rurl.Path, filename = path.Split(rurl.Path) 452 if strings.HasSuffix(filename, ".conf") { 453 return c.LoadConfReader(rurl.RequestURI(), bytes.NewBufferString(data)) 454 } 455 return c.LoadPropReaderWait(rurl.RequestURI(), bytes.NewBufferString(data), wait) 456 } 457 458 //LoadPropString will load properties config by string config 459 func (c *Config) LoadPropString(data string) error { 460 return c.LoadPropStringWait(data, true) 461 } 462 463 //LoadPropStringWait will load properties config by string config 464 func (c *Config) LoadPropStringWait(data string, wait bool) error { 465 return c.LoadPropReaderWait("", bytes.NewBufferString(data), wait) 466 } 467 468 //LoadConfString will load conf config by string config 469 func (c *Config) LoadConfString(data string) error { 470 return c.LoadConfReader("", bytes.NewBufferString(data)) 471 } 472 473 //EnvReplace will replace tartget patter by ${key} with value in configure map or system environment value. 474 func (c *Config) EnvReplace(val string) string { 475 return c.EnvReplaceEmpty(val, false) 476 } 477 478 //EnvReplaceEmpty will replace tartget patter by ${key} with value in configure map or system environment value. 479 func (c *Config) EnvReplaceEmpty(val string, empty bool) string { 480 reg := regexp.MustCompile(`\$\{[^\}]*\}`) 481 val = reg.ReplaceAllStringFunc(val, func(m string) string { 482 keepEmpty := empty 483 rval := "" 484 keys := strings.Split(strings.Trim(m, "${}\t "), ",") 485 for _, key := range keys { 486 if strings.HasPrefix(key, "@/") { 487 keepEmpty = true 488 rval = strings.TrimPrefix(key, "@/") 489 break 490 } else if c.Exist(key) { 491 rval = c.Str(key) 492 } else if key == "CONF_DIR" { 493 rval = c.Base 494 } else { 495 rval = os.Getenv(key) 496 } 497 if len(rval) > 0 { 498 break 499 } 500 } 501 if len(rval) > 0 || keepEmpty { 502 return rval 503 } 504 return m 505 }) 506 return val 507 } 508 509 //Merge merge another configure. 510 func (c *Config) Merge(config *Config) { 511 if config == nil { 512 return 513 } 514 for k, v := range config.config { 515 c.config[k] = v 516 } 517 for _, s := range config.Seces { 518 if _, ok := c.SecLn[s]; ok { 519 continue 520 } 521 c.Seces = append(c.Seces, s) 522 } 523 } 524 525 //MergeSection merge section on another configure 526 func (c *Config) MergeSection(section string, config *Config) { 527 for k, v := range config.config { 528 if strings.HasPrefix(k, section) { 529 continue 530 } 531 c.config[k] = v 532 } 533 if _, ok := c.SecLn[section]; !ok { 534 c.Seces = append(c.Seces, section) 535 } 536 } 537 538 //Clone will clone the configure 539 func (c *Config) Clone() (conf *Config) { 540 conf = NewConfig() 541 for k, v := range c.config { 542 conf.config[k] = v 543 } 544 conf.ShowLog = c.ShowLog 545 conf.sec = c.sec 546 conf.Lines = append(conf.Lines, c.Lines...) 547 conf.Seces = append(conf.Seces, c.Seces...) 548 for k, v := range c.SecLn { 549 conf.SecLn[k] = v 550 } 551 conf.Base = c.Base 552 for k, v := range c.Masks { 553 conf.Masks[k] = v 554 } 555 return 556 } 557 558 // //Strip will strip one section to new Config 559 // func (c *Config) Strip(section string) (config *Config) { 560 // config = NewConfig() 561 // for k, v := range c.M { 562 // if !strings.HasPrefix(k, section) { 563 // continue 564 // } 565 // config.M["loc"+strings.TrimPrefix(k, section)] = v 566 // } 567 // return 568 // } 569 570 func (c *Config) String() string { 571 mask := map[*regexp.Regexp]*regexp.Regexp{} 572 for k, v := range c.Masks { 573 mask[regexp.MustCompile(k)] = regexp.MustCompile(v) 574 } 575 buf := bytes.NewBuffer(nil) 576 keys, locs := []string{}, []string{} 577 for k := range c.config { 578 if strings.HasPrefix(k, "loc/") { 579 locs = append(locs, k) 580 } else { 581 keys = append(keys, k) 582 } 583 } 584 sort.Strings(keys) 585 for _, k := range keys { 586 val := fmt.Sprintf("%v", c.config[k]) 587 for maskKey, maskVal := range mask { 588 if maskKey.MatchString(k) { 589 val = maskVal.ReplaceAllString(val, "***") 590 } 591 } 592 val = strings.Replace(val, "\n", "\\n", -1) 593 buf.WriteString(fmt.Sprintf("%v=%v\n", k, val)) 594 } 595 for _, k := range locs { 596 val := fmt.Sprintf("%v", c.config[k]) 597 for maskKey, maskVal := range mask { 598 if maskKey.MatchString(k) { 599 val = maskVal.ReplaceAllString(val, "***") 600 } 601 } 602 val = strings.Replace(val, "\n", "\\n", -1) 603 buf.WriteString(fmt.Sprintf("%v=%v\n", k, val)) 604 } 605 return buf.String() 606 } 607 608 // func (c *Config) Store(sec, fp, tsec string) error { 609 // var seci int = -1 610 // for idx, s := range c.Seces { 611 // if s == sec { 612 // seci = idx 613 // } 614 // } 615 // if seci < 0 { 616 // return fmt.Errorf("section not found by %v", sec) 617 // } 618 // var beg, end int = c.SecLn[sec], len(c.Lines) 619 // if seci < len(c.Seces)-1 { 620 // end = c.SecLn[c.Seces[seci+1]] - 1 621 // } 622 // tf, err := os.OpenFile(fp, os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModePerm) 623 // if err != nil { 624 // return err 625 // } 626 // defer tf.Close() 627 // buf := bufio.NewWriter(tf) 628 // buc.WriteString("[" + tsec + "]\n") 629 // for i := beg; i < end; i++ { 630 // buc.WriteString(c.Lines[i]) 631 // buc.WriteString("\n") 632 // } 633 // return buc.Flush() 634 // } 635 636 func readLine(buf *bufio.Reader) (line string, err error) { 637 var bys []byte 638 var prefix bool 639 for { 640 bys, prefix, err = buf.ReadLine() 641 if err != nil { 642 break 643 } 644 line += string(bys) 645 if !prefix { 646 break 647 } 648 } 649 return 650 }