From 481ebc30e03379729b9639f8b7d679436f4177c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Sima=CC=83o?= Date: Wed, 29 Sep 2021 08:55:11 -0300 Subject: [PATCH] 1st ver --- .gitignore | 4 + LICENSE | 20 ++- README.md | 110 ++++++++++++- go.mod | 13 ++ go.sum | 29 ++++ lib/cli.go | 19 +++ lib/cmdmgr.go | 95 ++++++++++++ lib/init.go | 20 +++ lib/res/.mk.yaml | 22 +++ lib/res/mk.json | 89 +++++++++++ lib/run.go | 396 +++++++++++++++++++++++++++++++++++++++++++++++ lib/types.go | 18 +++ lib/ver.go | 15 ++ lib/ver.txt | 1 + main.go | 10 ++ 15 files changed, 856 insertions(+), 5 deletions(-) create mode 100644 .gitignore create mode 100644 go.mod create mode 100644 go.sum create mode 100644 lib/cli.go create mode 100644 lib/cmdmgr.go create mode 100644 lib/init.go create mode 100644 lib/res/.mk.yaml create mode 100644 lib/res/mk.json create mode 100644 lib/run.go create mode 100644 lib/types.go create mode 100644 lib/ver.go create mode 100644 lib/ver.txt create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..92b809a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/tmp/ +/deploy/ +.idea +.vscode diff --git a/LICENSE b/LICENSE index 2071b23..c88afcb 100644 --- a/LICENSE +++ b/LICENSE @@ -1,9 +1,21 @@ MIT License -Copyright (c) +Copyright (c) 2021 digitalcircle-com-br -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index ab0d92a..eef88fb 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,111 @@ # mk -mk - Make like tool for building, testing, etc. \ No newline at end of file +_Multiplatform make-like tool_ + +Ok, ok - you say - why another make tool clone? Oh lord, there we go again.... + +Indeed its a valid question... reasons are: + +1 - Cuz I could not find a make tool that fit my needs as simple as this one + +2 - Cuz windows is always a pain when it comes to compiling software + +3 - Why not? + +4 - Did I mention I hate verbose stuff? + +So these are the drivers for writting mk + +## Install + +```shell +go install github.com/digitalcircle-com-br/mk@latest +``` + +## How it works: + +mk will look for mk files (which may be named: mk, mk.yaml .mk or .mk.yaml) + +you may create a new mk file by using ```mk -i``` bingo, thats all... + +The file will look like this one: + +```yaml +# SAMPLE mk file - Feel free to add your own header +default: a #This is the default task, in case you call command w/o parameters +env: # in case you want to add var to env, you may add it here + a: 1 + b: 2 + +tasks: #now lets define the tasks + a_darwin_arm64: #this is the task name + cmd: |- # and this is the command - which may be multiline, no issues. + echo \"${TASK} / ${BASETASK}\" + ls -larth + pwd + deploy: + pre: [ build,test ] # pre is an array of predecessors + help: Deploys the project # help prints the help message + cmd: echo deploying + test: + pre: [ c ] + help: Tests project + cmd: echo testing + build: + help: Build binaries + cmd: echo building + main: + help: Main task + pre: [ build ] + cmd: |- + echo main + echo done% +``` + +And thats it. + +## Some gotchas you should notice + +### Name resolution + +Tasks are resolved considering this rule: task_os_arch: In case you have a task with the os name and arch name, it will +have higher precedence at resolving it. Suppose you add 2 tasks in your mk file: a_windows_amd64 and a_darwin_arm64. In +case youre on a Mac with Apple silicon, and call make a, a_darwin_arm64 will be called. In case you have a task a_darwin +and a_windows, and call mk a from a Mac with Intel processor, it will call a_darwing. Lastly, in case you also define a +task a, it will be called in case none of these more restrictive rules find math. + +> By adopting this approach same mk file will allow multiple platform compilation. + +### Variables + +You may place ${VAR} anywhere in your command, and it will be replaced by mk. It provides you some var, and also env +vars + +## Help reference + +```shell +Usage: mk [ ...] + +Arguments: + [ ...] Tasks to be run - Default is main. + +Flags: + -h, --help Show context-sensitive help. + -f, --file=STRING File to be used - Defaults are: .mk.yaml, .mk, mk, mk.yaml + -i, --init Creates a new empty file (default is .mk.yaml in case no filename is provided) + -v, --ver Prints version and exit + -l, --list Check file and print tasks + -d, --dbg Debugs execution + --dump-validator Dumps Validator JSON File + -e, --env Dumps env and vars + +``` + +# TODO + +- Output is pretty ugly, but the best I could think of so far... +- Integrate zip and git into "internal tasks" +- allow file to have tasks as strings in case no other props are required +- allow file include +- allow it to run as server +- Accepting recommendations on how to improve it \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1b13aae --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module go.digitalcircle.com.br/open/mk + +go 1.17 + +require ( + github.com/alecthomas/kong v0.2.17 + github.com/fatih/color v1.13.0 + github.com/mattn/go-colorable v0.1.9 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/pkg/errors v0.9.1 // indirect + golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..4ba176b --- /dev/null +++ b/go.sum @@ -0,0 +1,29 @@ +github.com/alecthomas/kong v0.2.17 h1:URDISCI96MIgcIlQyoCAlhOmrSw6pZScBNkctg8r0W0= +github.com/alecthomas/kong v0.2.17/go.mod h1:ka3VZ8GZNPXv9Ov+j4YNLkI8mTuhXyr/0ktSlqIydQQ= +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/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/lib/cli.go b/lib/cli.go new file mode 100644 index 0000000..181e3ae --- /dev/null +++ b/lib/cli.go @@ -0,0 +1,19 @@ +package lib + +import "github.com/alecthomas/kong" + +var CLI = struct { + File string `help:"File to be used - Defaults are: .mk.yaml, .mk, mk, mk.yaml" short:"f"` + Init bool `help:"Creates a new empty file (default is .mk.yaml in case no filename is provided)" short:"i"` + Tasks []string `arg:"" help:"Tasks to be run - Default is main." default:"."` + Ver bool `help:"Prints version and exit" short:"v"` + List bool `help:"Check file and print tasks" short:"l"` + Dbg bool `help:"Debugs execution" short:"d"` + DumpValidator bool `help:"Dumps Validator JSON File" default:"false"` + Env bool `help:"Dumps env and vars" default:"false" short:"e"` +}{} + +func InitCli() { + kong.Parse(&CLI) + Ver(CLI.Ver) +} diff --git a/lib/cmdmgr.go b/lib/cmdmgr.go new file mode 100644 index 0000000..5e316dd --- /dev/null +++ b/lib/cmdmgr.go @@ -0,0 +1,95 @@ +package lib + +import ( + "archive/zip" + "errors" + "fmt" + "io" + "os" + "path" + "path/filepath" + "strings" + "time" +) + +const ( + CMD_ZIP = "zip" + CMD_PACK = "pack" +) + +func RecursiveZip(i int, pathToZip, destinationPath string) error { + Log(i, "mk:zip", "O", "Starting Zip") + destinationFile, err := os.Create(destinationPath) + if err != nil { + return err + } + myZip := zip.NewWriter(destinationFile) + err = filepath.Walk(pathToZip, func(filePath string, info os.FileInfo, err error) error { + Log(i, "mk:zip", "O", fmt.Sprintf("Adding file: %s", filePath)) + if info.IsDir() { + return nil + } + if err != nil { + return err + } + relPath := strings.TrimPrefix(filePath, filepath.Dir(pathToZip)) + zipFile, err := myZip.Create(relPath) + if err != nil { + return err + } + fsFile, err := os.Open(filePath) + if err != nil { + return err + } + _, err = io.Copy(zipFile, fsFile) + if err != nil { + return err + } + return nil + }) + if err != nil { + return err + } + err = myZip.Close() + if err != nil { + return err + } + Log(i, "mk:zip", "O", fmt.Sprintf("Zip finished: %s", destinationPath)) + return nil +} +func RunMkCmd(i int, line string) error { + parts := strings.Split(line, " ") + switch parts[0] { + case CMD_ZIP: + if len(parts) < 3 { + return errors.New(fmt.Sprintf("Not enought params: must be zip . Got: %s", strings.Join(parts, " "))) + } + return RecursiveZip(i, parts[1], parts[2]) + case CMD_PACK: + var ptz string + if len(parts) > 1 { + ptz = parts[1] + } else { + for _, f := range []string{"stage", "deploy", "pack", "release"} { + st, err := os.Stat(f) + if err == nil && st.IsDir() { + ptz = f + break + } + + } + + } + dir, err := os.Getwd() + if err != nil { + return err + } + dname := path.Base(dir) + ts := time.Now().Format("060102150405") + fname := dname + "-" + ts + ".zip" + return RecursiveZip(i, ptz, fname) + default: + Log(i, parts[0], "E", fmt.Sprintf("mk:cmd %s is not known", parts[0])) + return errors.New(fmt.Sprintf("mk:cmd %s is not known", parts[0])) + } +} diff --git a/lib/init.go b/lib/init.go new file mode 100644 index 0000000..e01b6e3 --- /dev/null +++ b/lib/init.go @@ -0,0 +1,20 @@ +package lib + +import ( + _ "embed" + "os" +) + +//go:embed res/.mk.yaml +var sample []byte + +//go:embed res/mk.json +var validator []byte + +func InitFile() error { + return os.WriteFile(CLI.File, sample, 0600) +} + +func DumpValidator() error { + return os.WriteFile(CLI.File, validator, 0600) +} diff --git a/lib/res/.mk.yaml b/lib/res/.mk.yaml new file mode 100644 index 0000000..d5ff191 --- /dev/null +++ b/lib/res/.mk.yaml @@ -0,0 +1,22 @@ +# SAMPLE mk file +default: main +env: + +tasks: + deploy: + pre: [ build,test ] + help: Deploys the project + cmd: echo deploying + test: + pre: [ c ] + help: Tests project + cmd: echo testing + build: + help: Build binaries + cmd: echo building + main: + help: Main task + pre: [ build ] + cmd: |- + echo main + echo done \ No newline at end of file diff --git a/lib/res/mk.json b/lib/res/mk.json new file mode 100644 index 0000000..38fa14d --- /dev/null +++ b/lib/res/mk.json @@ -0,0 +1,89 @@ +{ + "$id": "https://www.digitalticircle.com.br/_schemas/mk.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Digital Circle - Make Instructions", + "type": "object", + "additionalProperties": false, + "$d": { + "task": { + "type": "object", + "properties": { + "pre": { + "description": "Predecessor tasks", + "type": "array", + "items": { + "type": "string" + } + }, + "cmd": { + "description": "Command this task should run", + "type": "string" + }, + "help": { + "description": "Help for this task", + "type": "string" + }, + "onerror": { + "description": "What to do in case of error" + }, + "env": { + "type": "object", + "description": "Global Env Var", + "patternProperties": { + ".{1.}": { + "type": "string" + } + } + }, + "vars": { + "type": "object", + "description": "Global Text Var", + "patternProperties": { + ".{1.}": { + "type": "string" + } + } + } + }, + "additionalProperties": "false" + } + }, + "properties": { + "default": { + "type": "string", + "default": "main", + "description": "Default task to run" + }, + "tasks": { + "type": "object", + "patternProperties": { + ".{1,}": { + "allOf": [ + { + "$ref": "#/$d/task" + } + ] + } + }, + "description": "Tasks configured for execution" + }, + "env": { + "type": "object", + "description": "Global Env Var", + "patternProperties": { + ".{1.}": { + "type": "string" + } + } + }, + "vars": { + "type": "object", + "description": "Global Text Var", + "patternProperties": { + ".{1.}": { + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/lib/run.go b/lib/run.go new file mode 100644 index 0000000..7dee832 --- /dev/null +++ b/lib/run.go @@ -0,0 +1,396 @@ +package lib + +import ( + "bufio" + "errors" + "fmt" + "github.com/fatih/color" + "gopkg.in/yaml.v3" + "log" + "os" + "os/exec" + "os/user" + "runtime" + "sort" + "strings" + "sync" + "time" +) + +var model *MkModel + +func FileExists(filename string) bool { + info, err := os.Stat(filename) + if os.IsNotExist(err) { + return false + } + return !info.IsDir() +} + +func ResolveConfig() (err error) { + if CLI.File != "" { + if CLI.Init && FileExists(CLI.File) { + println(fmt.Sprintf("Using file: %s", CLI.File)) + return + } else { + err = errors.New(fmt.Sprintf("Config File %s not found", CLI.File)) + } + } + deffiles := []string{".mk.yaml", ".mk", "mk", "mk.yaml"} + for _, v := range deffiles { + if CLI.Init || FileExists(v) { + println(fmt.Sprintf("Using file: %s", v)) + CLI.File = v + return + } + } + err = errors.New(fmt.Sprintf("No Config File found")) + return +} + +func ResolveTask(n string) *MkTask { + osname := runtime.GOOS + arch := runtime.GOARCH + vars["BASETASK"] = n + t, ok := model.Tasks[n+"_"+osname+"_"+arch] + if ok { + vars["TASK"] = n + "_" + osname + "_" + arch + return t + } + t, ok = model.Tasks[n+"_"+osname] + if ok { + vars["TASK"] = n + "_" + osname + return t + } + vars["TASK"] = n + + return model.Tasks[n] +} + +func Log(i int, task string, stream string, b string) { + timeLb := time.Now().Format("15:04:05") + tab := "" + for x := 0; x < i; x++ { + tab = tab + "\t" + } + if stream == "O" { + println(color.GreenString(fmt.Sprintf("%s %s [%s - %s]: %s", timeLb, tab, task, stream, b))) + } else if stream == "E" { + println(color.RedString(fmt.Sprintf("%s %s [%s - %s]: %s", timeLb, tab, task, stream, b))) + } else { + println(fmt.Sprintf("%s %s [%s - %s]: %s", timeLb, tab, task, stream, b)) + } + +} + +func RunTask(name string, t *MkTask, l int) error { + + for k, v := range t.Env { + env[k] = v + } + + for k, v := range t.Vars { + vars[k] = v + } + + Log(l, name, "**", fmt.Sprintf("Starting %s", name)) + if len(t.Pre) > 0 { + Log(l, name, "**", fmt.Sprintf("Will run Pre tasks: [%s]", strings.Join(t.Pre, ","))) + for _, v := range t.Pre { + caller, repeat := model.Stack[v] + if repeat { + Log(l, name, "**", fmt.Sprintf("Task %s already called by %s - skipping.", v, caller)) + continue + } else { + model.Stack[v] = name + } + + pr, ok := model.Tasks[v] + if ok { + err := RunTask(v, pr, l+1) + if err != nil { + return err + } + } else { + return errors.New(fmt.Sprintf("Task %s, prereq of: %s not found in model", v, name)) + } + } + } + var c *exec.Cmd + if runtime.GOOS == "windows" { + c = exec.Command("cmd.exe") + } else { + c = exec.Command("sh") + } + for k, v := range env { + c.Env = append(c.Env, fmt.Sprintf("%s=%s", k, v)) + } + + pi, _ := c.StdinPipe() + + po, err := c.StdoutPipe() + + if err != nil { + log.Printf(err.Error()) + return err + } + scannero := bufio.NewScanner(po) + + pe, err := c.StderrPipe() + if err != nil { + log.Printf(err.Error()) + return err + } + scannere := bufio.NewScanner(pe) + + wg := sync.WaitGroup{} + + wg.Add(2) + + ch := make(chan string) + + go func() { + time.Sleep(time.Millisecond * 100) + defer wg.Done() + for scannero.Scan() { + line := scannero.Text() + if strings.Contains(line, "__CMD_ENDED__:") { + ret := strings.Split(line, "__CMD_ENDED__:")[1] + if ret != "%errorlevel%" { + ch <- ret + } + } else { + Log(l+1, name, "O", line) + } + } + + }() + + go func() { + time.Sleep(time.Millisecond * 100) + defer wg.Done() + for scannere.Scan() { + Log(l+1, name, "E", scannere.Text()) + } + }() + + err = c.Start() + + if err != nil { + return err + } + + lines := bufio.NewScanner(strings.NewReader(t.Cmd)) + + //if runtime.GOOS == "windows" { + // pi.Write([]byte("@echo off\n")) + //} + + for lines.Scan() { + + txt := lines.Text() + + for k, v := range vars { + txt = strings.Replace(txt, fmt.Sprintf("${%s}", k), v, -1) + } + if strings.HasPrefix(txt, "@") { + txt = strings.TrimLeft(txt, "@") + } else { + Log(l+1, name, "I", txt) + } + + if strings.HasPrefix(txt, "mk:") { + txt = strings.Replace(txt, "mk:", "", 1) + txt = strings.TrimSpace(txt) + err = RunMkCmd(l+1, txt) + if err != nil { + log.Printf("Error running: %s", txt) + } + } else { + + _, err = pi.Write([]byte(txt + "\n")) + + if runtime.GOOS == "windows" { + _, err = pi.Write([]byte("echo __CMD_ENDED__:%errorlevel%" + "\n")) + } else { + _, err = pi.Write([]byte("echo __CMD_ENDED__:$?" + "\n")) + } + + ret := <-ch + if ret != "0" && t.Onerror == "skip" { + Log(l+1, name, "E", "Error Code: "+ret+" will continue, but watch out") + + } else if ret != "0" && t.Onerror != "skip" { + Log(l+1, name, "E", "Error Code is: "+ret+" ABORTING") + break + } + } + + } + _, err = pi.Write([]byte("exit\n")) + if err != nil { + return err + } + + wg.Wait() + err = c.Wait() + Log(l, name, "**", "End") + return err + +} + +func DumpEnv() { + if CLI.Env { + println("Vars:") + + varnames := make([]string, 0) + for k := range vars { + varnames = append(varnames, k) + } + sort.Strings(varnames) + for _, k := range varnames { + v := vars[k] + println(fmt.Sprintf("%s => %s", k, v)) + } + + println("========") + println("Env:") + + envnames := make([]string, 0) + for k := range env { + envnames = append(envnames, k) + } + sort.Strings(envnames) + for _, k := range envnames { + v := env[k] + println(fmt.Sprintf("%s => %s", k, v)) + } + + os.Exit(0) + } +} + +func Prepare() error { + + InitCli() + err := ResolveConfig() + if err != nil { + return err + } + + if CLI.Init { + err = InitFile() + if err != nil { + return err + } + os.Exit(0) + } + + if CLI.DumpValidator { + err = DumpValidator() + if err != nil { + log.Printf("Error DumpValidator: %s", err.Error()) + } + os.Exit(0) + } + + bs, err := os.ReadFile(CLI.File) + if err != nil { + panic(err) + } + model = &MkModel{} + err = yaml.Unmarshal(bs, model) + if err != nil { + return err + } + if model.Default == "" { + model.Default = "main" + } + + for k, v := range model.Tasks { + v.Name = k + } + + if CLI.List { + println("Tasks:") + tasknames := make([]string, 0) + for k := range model.Tasks { + tasknames = append(tasknames, k) + } + sort.Strings(tasknames) + for _, k := range tasknames { + v := model.Tasks[k] + def := "" + if v.Name == model.Default { + def = "DEF>" + } else { + def = "" + } + println(fmt.Sprintf("%s %s [%s]: %s", def, k, strings.Join(v.Pre, ","), v.Help)) + } + os.Exit(0) + } + + if len(CLI.Tasks) < 1 || CLI.Tasks[0] == "" || CLI.Tasks[0] == "." { + CLI.Tasks = []string{model.Default} + } + + return err +} + +var env map[string]string +var vars map[string]string + +func Run() error { + env = make(map[string]string) + vars = make(map[string]string) + envstrs := os.Environ() + for _, k := range envstrs { + parts := strings.Split(k, "=") + ek := strings.TrimSpace(parts[0]) + ev := os.Getenv(ek) + env[ek] = ev + } + + err := Prepare() + if err != nil { + log.Printf("Could not execute: %s", err.Error()) + os.Exit(0) + } + + for k, v := range model.Env { + env[k] = v + } + + for k, v := range model.Vars { + vars[k] = v + } + now := time.Now() + vars["DT_YYMMDDHHmmss"] = now.Format("060102150405") + vars["DT_YYMMDD"] = now.Format("060102") + vars["DS_TS"] = fmt.Sprintf("%d", now.Unix()) + + u, err := user.Current() + if err != nil { + panic(err) + } + vars["USERNAME"] = u.Username + vars["HOMEDIR"] = u.HomeDir + + if err != nil { + return err + } + + DumpEnv() + model.Stack = make(map[string]string) + for _, v := range CLI.Tasks { + tasko := ResolveTask(v) + if tasko != nil { + err = RunTask(v, tasko, 0) + } else { + return errors.New(fmt.Sprintf("No task named %s found", v)) + } + } + + return err +} diff --git a/lib/types.go b/lib/types.go new file mode 100644 index 0000000..55a5b8a --- /dev/null +++ b/lib/types.go @@ -0,0 +1,18 @@ +package lib + +type MkTask struct { + Name string + Help string + Cmd string `yaml:"cmd"` + Pre []string `yaml:"pre"` + Onerror string `yaml:"onerror"` + Env map[string]string `yaml:"env"` + Vars map[string]string `yaml:"vars"` +} +type MkModel struct { + Env map[string]string `yaml:"env"` + Vars map[string]string `yaml:"vars"` + Tasks map[string]*MkTask `yaml:"tasks"` + Default string `yaml:"default"` + Stack map[string]string `yaml:"-"` +} diff --git a/lib/ver.go b/lib/ver.go new file mode 100644 index 0000000..3e44348 --- /dev/null +++ b/lib/ver.go @@ -0,0 +1,15 @@ +package lib + +import "os" + +import _ "embed" + +//go:embed ver.txt +var ver string + +func Ver(end bool) { + println("DC MK Tool - ver: " + ver) + if end { + os.Exit(0) + } +} diff --git a/lib/ver.txt b/lib/ver.txt new file mode 100644 index 0000000..3a925a4 --- /dev/null +++ b/lib/ver.txt @@ -0,0 +1 @@ +Sat Sep 18 20:38:27 -03 2021 diff --git a/main.go b/main.go new file mode 100644 index 0000000..f905097 --- /dev/null +++ b/main.go @@ -0,0 +1,10 @@ +package main + +import "go.digitalcircle.com.br/open/mk/lib" + +func main() { + err := lib.Run() + if err != nil { + panic(err) + } +}