From ddbfcc1093835a73a659d31cf4d893f7c256d7ce Mon Sep 17 00:00:00 2001 From: Michael Wesemann Date: Wed, 20 May 2026 16:21:52 +0200 Subject: [PATCH] initial commit [141.14.140.180,mike] --- .gitignore | 19 +++ build.go | 3 + gbld.go | 453 +++++++++++++++++++++++++++++++++++++++++++++++++ gbld.menu.json | 3 + go.mod | 37 ++++ go.sum | 122 +++++++++++++ linux.go | 14 ++ mac.go | 14 ++ tools.go | 341 +++++++++++++++++++++++++++++++++++++ 9 files changed, 1006 insertions(+) create mode 100644 .gitignore create mode 100644 build.go create mode 100644 gbld.go create mode 100644 gbld.menu.json create mode 100644 go.mod create mode 100644 go.sum create mode 100644 linux.go create mode 100644 mac.go create mode 100644 tools.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5057663 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +.DS_Store +.AppleDouble +.LSOverride +._* +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +*.swp +*.lock +._* +*.o +*.a +*/bin/* +*/tmp/* +bin/* +tmp/* +gbld diff --git a/build.go b/build.go new file mode 100644 index 0000000..24c2879 --- /dev/null +++ b/build.go @@ -0,0 +1,3 @@ +package main + +var build = "305" diff --git a/gbld.go b/gbld.go new file mode 100644 index 0000000..085f2cd --- /dev/null +++ b/gbld.go @@ -0,0 +1,453 @@ +// ============================================================================ simple golang builder (mwx'2026) +package main + +import ( + "bufio" + "bytes" + "encoding/json" + "flag" + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" + "time" + + "github.com/AlecAivazis/survey/v2" +) + +var version = "1.18.4" + +var VERSIONURL = "http://gozilla.fhi.mpg.de/gbld/version.txt" +var UPDATEBASEURL = "http://gozilla.fhi.mpg.de/gbld" + +var oses = []string{"darwin", "linux", "windows"} +var arches = []string{"arm64", "amd64"} + +var GO string +var GOIMPORTS string + +var done chan bool + + +func main() { // ========================================================================================== main + + checkforupdate() + + if len(os.Args) == 1 { // read predefined builds + c, err := os.ReadFile("gbld.menu.json") + if err == nil { + var sel []string + var buildjson map[string][]string + + err := json.Unmarshal([]byte(c), &buildjson) + if err == nil { + for k := range buildjson { + sel = append(sel, k) + } + tmp := "" + err = survey.AskOne(&survey.Select{Message: "Select build", Options: sel}, &tmp) + os.Args = []string{"gbld"} + os.Args = append(os.Args, buildjson[tmp]...) + } else { + PE(err.Error()) + os.Exit(0); + } + } else { + os.Args = []string{"gbld","-b","-i","-1",dirName()} + } + } + + opt_b := flag.Bool("b", false, "") + opt_1 := flag.Bool("1", false, "") + opt_i := flag.Bool("i", false, "") + opt_x := flag.Bool("x", false, "") + opt_c := flag.String("c", "", "") + opt_o := flag.String("o", "", "") + opt_a := flag.Bool("a", false, "") + opt_v := flag.Bool("v", false, "") + opt_h := flag.Bool("h", false, "") + opt_u := flag.Bool("u", false, "") + + flag.Usage = func() { + P() + info() + P() + P(Cw("Usage:")) + P(Cy(" gbld [-b] [-1] [-i] [-a] [-c ] [-o ] [build name]")) + P(Cy(" gbld -u")) + P(Cw(" -b build only")) + P(Cw(" -1 run only once")) + P(Cw(" -i run gpimports before build")) + P(Cw(" -a build for all platforms")) + P(Cw(" -o exec options")) + P(Cw(" -c run command after build")) + P(Cw(" -v show version")) + P(Cw(" -h show help")) + P(Cw(" -u upload builds to gozilla")) + P() + P(Cw(" · if build is supplied the current directory name will be used")) + P() + P(Cw(" · if no option supplied try to read '") + Cy("gbld.menu.json") + Cw("'")) + P(Cm(` {`)) + P(Cm(` "build": ["-b","gbld"],`)) + P(Cm(` "help": ["-h"]`)) + P(Cm(` }`)) + P() + P(Cw(" · if no option is supplied and no '") + Cy("gbld.menu.json") + Cw("' is available")) + P(Cw(" the current directory name with ") + Cy("-b -i -1") + Cw(" as options will be used")) + P() + } + + flag.Parse() + + path, err := exec.LookPath("go") // ··························································· check for 'go' + if err != nil { + fmt.Println(Crb("Error: ") + Cwb("go") + " not found") + os.Exit(0) + + } + GO = path + + + path, err = exec.LookPath("goimports") // ·············································· check for 'goimports' + if err != nil { + fmt.Println(Crb("Error: ") + Cwb("goimports") + " not found") + os.Exit(0) + } + GOIMPORTS = path + + + if *opt_v { // ·················································································· show version + info() + os.Exit(0) + } + + if *opt_h { // ····················································································· show help + flag.Usage() + os.Exit(0) + } + + name := flag.Arg(0) // ································································ command line arguments + if len(name) == 0 { + name=dirName() + if len(name) == 0 { + flag.Usage() + os.Exit(0) + } + } + + file := name + ".go" + if !fileExists(file) { + P(Crb("file not found: " + file)) + os.Exit(0) + } + + var ot int64 = 0 + var st int = 1 + var xmd *exec.Cmd = nil + + if (!*opt_1) { + PN("\033[2J\033[1;1H") + info() + } + + + if *opt_u { // ······································································ upload builds to gozilla + targetversion:=targetversion(file) + + for _, osys := range oses { + for _, arch := range arches { + ofile:="bin/"+name+"_"+targetversion+"_"+osys+"_"+arch + _, err := os.Stat(ofile) + if err == nil { + + cmd := exec.Command("ssh","root@gozilla","rm", "/db/www/"+prgname()+"/*_"+osys+"_"+arch ) + P(Cy(cmd)) + cmd.Run() + + + cmd = exec.Command("scp",ofile, "root@gozilla:/db/www/"+prgname()) + P(Cy(cmd)) + cmd.Run() + } + } + } + _, err := os.Stat("bin/version.txt") + if err == nil { + cmd := exec.Command("scp","bin/version.txt", "root@gozilla:/db/www/"+prgname()) + P(Cy(cmd)) + cmd.Run() + } + + os.Exit(0) + } + + + for { // ------------------------------------------------------------------------------------------- main loop + lmf := lmf("./") + + var dt int64 = 0 + + if fileExists(name) { + dt = lmf - filemodtime(name) + } else { + dt = 1 + } + + if (dt > 0 && ot != lmf) || st == 1 { // ·············································· new version detected + + if st == 0 && !*opt_1{ + PN("\033[2J\033[1;1H") + } + P(Cc("--- start compiling " + file + " ---")) + + stopRunning(xmd) + + o := "" // ········································································· increase build number + + readFile, err := os.Open("build.go") + if err == nil { + backup("build.go") + fileScanner := bufio.NewScanner(readFile) + fileScanner.Split(bufio.ScanLines) + r := regexp.MustCompile("var\\s+build\\s*=\\s*\"(.*)\"$") + for fileScanner.Scan() { + line := fileScanner.Text() + s := r.FindStringSubmatch(line) + if len(s) > 0 { + b := Atoi(s[1]) + 1 + o = o + "var build = \"" + Itoa(b) + "\"\n" + P("build:", s[1], "->", b) + } else { + o = o + line + "\n" + } + } + readFile.Close() + os.WriteFile("build.go", []byte(o), 0644) + } + + targetversion:="" + readFile, err = os.Open(file) + if err == nil { + fileScanner := bufio.NewScanner(readFile) + fileScanner.Split(bufio.ScanLines) + r := regexp.MustCompile("var\\s+version\\s*=\\s*\"(.*)\"$") + for fileScanner.Scan() { + line := fileScanner.Text() + s := r.FindStringSubmatch(line) + if len(s) > 0 { + targetversion=s[1] + } + } + readFile.Close() + } + + if (targetversion=="") { + PE("target version not found") + os.Exit(0) + } + + if *opt_i { // ············································································· run goimports + re := regexp.MustCompile(`(?s)import\s*\((.*?)\)`) + files, err := os.ReadDir(".") + if err != nil { fmt.Printf("Error: %v\n", err); return } + + for _, f := range files { + if !f.IsDir() && strings.HasSuffix(f.Name(), ".go") { + fileName := f.Name() + cmd := exec.Command("goimports", fileName) + output, err := cmd.Output() + if err != nil { continue } + origContent, _ := os.ReadFile(fileName) + tempMatch := re.FindStringSubmatch(string(output)) + if len(tempMatch) < 2 { continue } + newImportBlock := tempMatch[1] + updatedContent := ReplaceFirst(re,string(origContent),fmt.Sprintf("import (%s)", newImportBlock)) + if (string(origContent) != updatedContent) { + backup(fileName) + PF("%s %s\n",Cw("imports updated:"),Cmb(fileName)) + os.WriteFile(fileName, []byte(updatedContent), 0644) + } + } + } + + } + + cmd := exec.Command("go", "build", "-o", "bin/"+name, name) + + PF("%s %s\n", Cw("running:"), Cyb("go build "+name)) + + out, err := cmd.CombinedOutput() + + if err != nil { // ·········································································· build failed + + PN(Cy(string(out))) + P(Cr("--- build failed: " + name + " ---")) + + } else { // ·············································································· build succeeded + + if *opt_a { // ······························································· build for other platforms + for _, osys := range oses { + for _, arch := range arches { + + ofile:="bin/"+name+"_"+targetversion+"_"+osys+"_"+arch + P(Cy(ofile)) + + cmd := exec.Command(GO, "build", "-o", ofile, name) + cmd.Env = append(os.Environ(), "GOOS="+osys, "GOARCH="+arch) + cmd.Run() + } + } + + os.WriteFile("bin/version.txt", []byte(targetversion), 0644) + + } + + P(Cb("--- compiling done: " + name + " ---")) + + if len(*opt_c) > 0 { // ··························································· execute post command + params := strings.Fields(*opt_c) + app := params[0] + args := params[1:] + + P(Cw(*opt_c)) + xmd = exec.Command(app, args...) + xmd.Stdout = os.Stdout + xmd.Stdin = os.Stdin + xmd.Stderr = os.Stderr + xmd.Run() + } + + if !*opt_b && !*opt_a { + stopRunning(xmd) + + args := []string{} + if len(*opt_o) > 0 { + args = strings.Fields(*opt_o) + } + + xmd = exec.Command("./bin/"+name, args...) // ······································· start new build + var stdoutBuf, stderrBuf bytes.Buffer + xmd.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf) + xmd.Stderr = io.MultiWriter(os.Stderr, &stderrBuf) + xmd.Start() + + PF("%s %d\n", Cw("process started:"), xmd.Process.Pid) + } + + P(Cg("--- end building: " + name + " ---")) + if *opt_x { + os.Exit(0) + } + } + ot = lmf + } + + if *opt_1 { + os.Exit(0) + } + + st = 0 + time.Sleep(500 * time.Millisecond) + } +} + + +func backup(src string) { // ---------------------------------------------------------------------- backup files + sourceFile,_ := os.Open(src) + defer sourceFile.Close() + dst:=SF("./tmp/%s.%s",src,time.Now().Format("20060102150405")) + destFile,_ := os.Create(dst) + defer destFile.Close() + io.Copy(destFile, sourceFile) + destFile.Sync() + destFile.Close() +} + +// ===================================================================================================== SUPPORT + +func targetversion(file string) string { // ---------------------------------------------- detect target version + targetversion:="" + readFile, err := os.Open(file) + if err == nil { + fileScanner := bufio.NewScanner(readFile) + fileScanner.Split(bufio.ScanLines) + r := regexp.MustCompile("var\\s+version\\s*=\\s*\"(.*)\"$") + for fileScanner.Scan() { + line := fileScanner.Text() + s := r.FindStringSubmatch(line) + if len(s) > 0 { + targetversion=s[1] + } + } + readFile.Close() + } + + if (targetversion=="") { + PE("target version not found") + os.Exit(0) + } + + return targetversion +} + + +func lmf(dir string) int64 { // -------------------------------------- get last modification date from .go files + var tmax int64 = 0 + files, err := ioutil.ReadDir("./") + if err == nil { + r1, _ := regexp.Compile("^\\.") + r2, _ := regexp.Compile("build\\.go") + r3, _ := regexp.Compile("\\.go$") + for _, file := range files { + if r3.MatchString(file.Name()) { + if !r1.MatchString(file.Name()) && !r2.MatchString(file.Name()) { + tf := filemodtime(file.Name()) + if tf > tmax { + tmax = tf + } + } + } + } + } + return tmax +} + +func stopRunning(cmd *exec.Cmd) { // -------------------------------------------------- stop running old process + if cmd == nil || cmd.ProcessState != nil && cmd.ProcessState.Exited() || cmd.Process == nil { + return + } + + err := cmd.Process.Kill() + if err == nil { + cmd.Wait() + P("process stopped:", cmd.Process.Pid) + } +} + +func fileExists(filename string) bool { // ------------------------------------------------ check if file exists + info, err := os.Stat(filename) + if os.IsNotExist(err) { + return false + } + return !info.IsDir() +} + +func dirName() string { // ------------------------------------------------------- get name of current directory + pwd,err := os.Getwd() + if err == nil { + return filepath.Base(pwd) + } + return "" +} + +func info() { // ------------------------------------------------------------------------------------- show info + PF("%s (%s (%s), toolbox %s, %s)\n", Cwb("gbld - simple golang builder"), + Cgb("v"+version), Cgb(build), Cgb("v"+tbversion), Ccb("mwx'2026")) +} + +// ========================================================================================================= END diff --git a/gbld.menu.json b/gbld.menu.json new file mode 100644 index 0000000..0d93362 --- /dev/null +++ b/gbld.menu.json @@ -0,0 +1,3 @@ +{ + "build (-b -i -1 gbld)": ["-b","-i","-1","gbld"] +} \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1f250cf --- /dev/null +++ b/go.mod @@ -0,0 +1,37 @@ +module gbld + +go 1.26.1 + +require ( + github.com/AlecAivazis/survey/v2 v2.3.7 + github.com/eknkc/basex v1.0.1 + github.com/fatih/color v1.19.0 + github.com/minio/selfupdate v0.6.0 + github.com/spf13/viper v1.21.0 + github.com/tidwall/gjson v1.19.0 +) + +require ( + aead.dev/minisign v0.2.0 // indirect + github.com/Masterminds/semver/v3 v3.5.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/sagikazarmark/locafero v0.11.0 // indirect + github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect + github.com/spf13/afero v1.15.0 // indirect + github.com/spf13/cast v1.10.0 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect + golang.org/x/text v0.28.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..fec9c1a --- /dev/null +++ b/go.sum @@ -0,0 +1,122 @@ +aead.dev/minisign v0.2.0 h1:kAWrq/hBRu4AARY6AlciO83xhNnW9UaC8YipS2uhLPk= +aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ= +github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= +github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= +github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= +github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= +github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= +github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= +github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/eknkc/basex v1.0.1 h1:TcyAkqh4oJXgV3WYyL4KEfCMk9W8oJCpmx1bo+jVgKY= +github.com/eknkc/basex v1.0.1/go.mod h1:k/F/exNEHFdbs3ZHuasoP2E7zeWwZblG84Y7Z59vQRo= +github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= +github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= +github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/minio/selfupdate v0.6.0 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU= +github.com/minio/selfupdate v0.6.0/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= +github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= +github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b h1:QAqMVf3pSa6eeTsuklijukjXBlj7Es2QQplab+/RbQ4= +golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/linux.go b/linux.go new file mode 100644 index 0000000..16e82ab --- /dev/null +++ b/linux.go @@ -0,0 +1,14 @@ +//go:build linux + +package main + +import ( + "os" + "syscall" +) + +func filemodtime(build string) int64 { + fibuild, _ := os.Stat(build) + tb := fibuild.Sys().(*syscall.Stat_t).Mtim + return tb.Sec +} diff --git a/mac.go b/mac.go new file mode 100644 index 0000000..90eda99 --- /dev/null +++ b/mac.go @@ -0,0 +1,14 @@ +//go:build darwin + +package main + +import ( + "os" + "syscall" +) + +func filemodtime(build string) int64 { + fibuild, _ := os.Stat(build) + tb := fibuild.Sys().(*syscall.Stat_t).Mtimespec + return tb.Sec +} diff --git a/tools.go b/tools.go new file mode 100644 index 0000000..9841c92 --- /dev/null +++ b/tools.go @@ -0,0 +1,341 @@ +// ======================================================================================= go toolbox (mwx'2026) +package main + +import ( + "bufio" + "crypto/rand" + "database/sql" + "encoding/base64" + "flag" + "fmt" + "io" + "math/big" + "net" + "net/http" + "os" + "path/filepath" + "regexp" + "runtime" + "strconv" + "strings" + + "github.com/AlecAivazis/survey/v2" + "github.com/AlecAivazis/survey/v2/terminal" + "github.com/Masterminds/semver/v3" + "github.com/eknkc/basex" + "github.com/fatih/color" + "github.com/minio/selfupdate" + "github.com/spf13/viper" + "github.com/tidwall/gjson" +) + +var tbversion = "0.4.1" + +var CHRS = "VW9IdGJ6eXh1T25DRHdrc2M5MlhOQVNQcEJFWnJhWVY2ZEowaFJLdmoxNUdxVDRJZkZpTTdRZW0zTFc4Z2w=" +var LR = []rune("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + + + +func checkforupdate() { // --------------------------------------------------------------- check for new version + prg := prgname() + resp, err := http.Get(VERSIONURL) + if err == nil { + scanner := bufio.NewScanner(resp.Body) + if scanner.Scan() { + lversion := strings.TrimSpace(scanner.Text()) + sv_version, err := semver.NewVersion(version) + if err == nil { + sv_lversion, err := semver.NewVersion(lversion) + if err == nil { + if (sv_lversion.GreaterThan(sv_version)) { + ans:=Yesno(SF("new '%s' version found (%s -> %s), update now?",prg,sv_version,sv_lversion),true); + if (ans) { + updateurl:=SF("%s/%s_%s_%s_%s",UPDATEBASEURL,prg,lversion,runtime.GOOS,runtime.GOARCH) + + if err := doupdate(updateurl); err != nil { + PO(SF("Update failed: %v\n", err)) + os.Exit(1) + } + PO("Update successful!","please run your last command again") + os.Exit(0) + + } + } + } + } + } + resp.Body.Close() + } +} + +func doupdate(url string) error { // ----------------------------------------------------------------- do update + resp, err := http.Get(url) + if err != nil { return err } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { return fmt.Errorf("server returned status: %v", resp.Status) } + err = selfupdate.Apply(resp.Body, selfupdate.Options{}) + if err != nil { return err } + return nil +} + +func Enc(str string) string { // ----------------------------------------------------------------- encode string + enc, _ := basex.NewEncoding(Db64(CHRS)) + return enc.Encode([]byte(Rndstr(2) + str)) +} + +func Dec(str string) string { // ----------------------------------------------------------------- decode string + enc, _ := basex.NewEncoding(Db64(CHRS)) + b, _ := enc.Decode(str) + return string(b[2:]) +} + +func Db64(txt string) string { // ------------------------------------------------------------- string to base64 + d, _ := base64.StdEncoding.DecodeString(txt) + return string(d) +} + +func Rndstr(n int) string { // ------------------------------------------------------- random string with length + b := make([]rune, n) + for i := range b { + n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(LR)))) + b[i] = LR[n.Int64()] + } + return string(b) +} + +func Input(msg string, def string) string { // -------------------------------- AlecAivazis/survey: input string + tmp := "" + err := survey.AskOne(&survey.Input{Message: msg, Default: def}, &tmp) + if err != nil { + if err == terminal.InterruptErr { + P(Crb("Interrupted.")) + os.Exit(0) + } + } + return tmp +} + +func Inputpw(msg string) string { // ---------------------------------------- AlecAivazis/survey: input password + tmp := "" + err := survey.AskOne(&survey.Password{Message: msg}, &tmp) + if err != nil { + if err == terminal.InterruptErr { + P(Crb("Interrupted.")) + os.Exit(0) + } + } + return tmp +} + +func Yesno(msg string, def bool) bool { // ------------------------------------------ AlecAivazis/survey: yes/no + var err error + tmp := "" + if def { + err = survey.AskOne(&survey.Select{Message: msg, Options: []string{"Yes", "No"}}, &tmp) + } else { + err = survey.AskOne(&survey.Select{Message: msg, Options: []string{"No", "Yes"}}, &tmp) + } + + if err != nil { + if err == terminal.InterruptErr { + P(Crb("Interrupted.")) + os.Exit(0) + } + } + + if tmp == "Yes" { + return true + } else { + return false + } +} + +func Getid(n int) string { // --------------------------------------------------------- get base62 random string + const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + ret := make([]byte, n) + for i := 0; i < n; i++ { + num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(letters)))) + ret[i] = letters[num.Int64()] + } + return string(ret) +} + +func GETid(n int) string { // --------------------------------------------------------- get base36 random string + const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + ret := make([]byte, n) + for i := 0; i < n; i++ { + num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(letters)))) + ret[i] = letters[num.Int64()] + } + return string(ret) +} + +func Mytable(rows *sql.Rows) []map[string]interface{} { // ----------------------------- load mysql result table + defer rows.Close() + columns, _ := rows.Columns() + count := len(columns) + tableData := make([]map[string]interface{}, 0) + values := make([]interface{}, count) + valuePtrs := make([]interface{}, count) + for rows.Next() { + for i := 0; i < count; i++ { + valuePtrs[i] = &values[i] + } + rows.Scan(valuePtrs...) + entry := make(map[string]interface{}) + for i, col := range columns { + var v interface{} + val := values[i] + b, ok := val.([]byte) + if ok { + v = string(b) + } else { + v = val + } + entry[col] = v + } + tableData = append(tableData, entry) + } + return (tableData) +} + +func Checkip(network string, ip string) bool { // ---------- check if ip is in range (cidr address or single ip) + if net.ParseIP(ip) == nil { + return false + } + _, subnet, err := net.ParseCIDR(network) + if err == nil { + if subnet.Contains(net.ParseIP(ip)) { + return true + } + } else { + if network == ip { + return true + } + } + return false +} + +func Isflagpassed(name string) bool { // -------------------------------------------------- check if flag is set + found := false + flag.Visit(func(f *flag.Flag) { + if f.Name == name { + found = true + } + }) + return found +} + +func Body(r *http.Response) string { // ------------------------------------------------------------ http body + body, err := io.ReadAll(r.Body) + if err == nil { + return string(body) + } + return "" +} + +func Atoi(s string) int { // ------------------------------------------------------------------------------ atoi + i, err := strconv.Atoi(s) + if err != nil { + return 0 + } + return i +} + +func Itoa(i int) string { // ------------------------------------------------------------------------------ itoa + return strconv.Itoa(i) +} + +func GJA(j string, k string) []string { // -------------------------------------------------- convert gjson array + var ret []string + for _, c := range gjson.Get(j, k).Array() { + ret = append(ret, c.String()) + } + return ret +} + +// ------------------------------------------------------------------------------------------------ print simple + +func P(a ...any) (n int, err error) { return fmt.Fprintln(os.Stdout, a...) } +func PN(a ...any) (n int, err error) { return fmt.Fprint(os.Stdout, a...) } +func PF(format string, a ...any) (n int, err error) { return fmt.Fprintf(os.Stdout, format, a...) } +func SF(format string, a ...any) string { return fmt.Sprintf(format, a...) } + +func PE(msg ...string) (n int, err error) { + if (len(msg)==2) { return fmt.Fprintf(os.Stdout, "%s: %s (%s)\n", Crb("ERROR"), Cwb(msg[0]),msg[1]) } + return fmt.Fprintf(os.Stdout, "%s: %s\n", Crb("ERROR"), Cwb(msg)) +} +func PO(msg ...string) (n int, err error) { + if (len(msg)==2) { return fmt.Fprintf(os.Stdout, "%s: %s (%s)\n", Cgb("OK"), Cwb(msg[0]),msg[1]) } + return fmt.Fprintf(os.Stdout, "%s: %s\n", Cgb("OK"), Cwb(msg)) +} + +// ------------------------------------------------------------------------------------------ regular expression + +func ReplaceFirst(re *regexp.Regexp, str, replace string) string { + loc := re.FindStringIndex(str) + if loc == nil { return str } + return str[:loc[0]] + replace + str[loc[1]:] +} + +// -------------------------------------------------------------------------------------------- string functions + +func Shortstr(s string, length int) string { + runes := []rune(s) + if len(runes) <= length { + return s + } + return string(runes[:length-2]) + ".." +} + +func SR(str string, n int) string { return strings.Repeat(str, n) } + +func RemoveAllMatches(slice []string, target string) []string { // remove matching string from array + result := slice[:0] + for _, v := range slice { + if v != target { + result = append(result, v) + } + } + return result +} + +// -------------------------------------------------------------------------------------------- system functions + +func prgname() string { // program name + exepath, err := os.Executable() + if err != nil { + PE(SF("Error getting executable path: %s", err)) + return "" + } + exename := filepath.Base(exepath) + return exename +} + +// ------------------------------------------------------------------------------------------ viper abbrevations + +func VX(key string) bool { return viper.IsSet(key) } +func VS(key string) string { return viper.GetString(key) } +func VSS(key string) []string { return viper.GetStringSlice(key) } +func VB(key string) bool { return viper.GetBool(key) } +func VI(key string) int { return viper.GetInt(key) } + +// ------------------------------------------------------------------------------------------------- text colors + +var Cr func(...interface{}) string = color.New(color.FgRed).SprintFunc() +var Cg func(...interface{}) string = color.New(color.FgGreen).SprintFunc() +var Cy func(...interface{}) string = color.New(color.FgYellow).SprintFunc() +var Cb func(...interface{}) string = color.New(color.FgBlue).SprintFunc() +var Cm func(...interface{}) string = color.New(color.FgMagenta).SprintFunc() +var Cc func(...interface{}) string = color.New(color.FgCyan).SprintFunc() +var Cw func(...interface{}) string = color.New(color.FgWhite).SprintFunc() + +var Crb func(...interface{}) string = color.New(color.Bold, color.FgRed).SprintFunc() +var Cgb func(...interface{}) string = color.New(color.Bold, color.FgGreen).SprintFunc() +var Cyb func(...interface{}) string = color.New(color.Bold, color.FgYellow).SprintFunc() +var Cbb func(...interface{}) string = color.New(color.Bold, color.FgBlue).SprintFunc() +var Cmb func(...interface{}) string = color.New(color.Bold, color.FgMagenta).SprintFunc() +var Ccb func(...interface{}) string = color.New(color.Bold, color.FgCyan).SprintFunc() +var Cwb func(...interface{}) string = color.New(color.Bold, color.FgWhite).SprintFunc() + +// ========================================================================================================= END \ No newline at end of file