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  }