commit ddbfcc1093835a73a659d31cf4d893f7c256d7ce Author: Michael Wesemann Date: Wed May 20 16:21:52 2026 +0200 initial commit [141.14.140.180,mike] 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