diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f4ba297 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +dist +bafi +site +*.exe +*.exe~ +*.csv +output.json + + diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..3ba3eb0 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "golang.go", + "casualjim.gotemplate" + ] +} \ No newline at end of file diff --git a/LICENSE b/LICENSE index f618a5c..0eac7be 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 Mario +Copyright (c) 2021 mmalcek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..e317883 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,145 @@ +[![Go](https://github.com/mmalcek/bafi/actions/workflows/go.yml/badge.svg)](https://github.com/mmalcek/bafi/actions/workflows/go.yml) +[![CodeQL](https://github.com/mmalcek/bafi/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/mmalcek/bafi/actions/workflows/) +# XML (or other formats) to TEXT using GO templates +Transform XML to text file using GO "text/template" + +## Key features +- Various input formats **(json, bson, yaml, xml)** +- Flexible output formatting using text templates +- Support for [LUA](https://www.lua.org/pil/contents.html) custom functions which allows very flexible data manipulation +- stdin/stdout support which allows get data from source -> translate -> delivery to destination. This allows easily translate data between different web services like REST to SOAP + + + + +## Command line arguments +- "-i input.xml" Input file name. If not defined app tries read stdin +- "-o output.txt" Output file name. If not defined result is send to stdout +- "-t template.tmpl" Template file. Alternatively you can use *inline* template + - inline template must start with **?** e.g. -t **"?{{.someValue}}"** +- "-f json" Alternative input format. + - Supported formats: **json, bson, yaml, xml (default)** +- "-v" - Show current verion +- "-?" - list available command line arguments + +#### Examples +- Basic +```sh +bafi.exe -i testdata.xml -o output.csv -t template.tmpl +``` +- JSON input +```sh +bafi.exe -i testdata.json -f json -o output.csv -t template.tmpl +``` +- STDIN +```sh +curl.exe -s "someURL" | bafi.exe -f json -t myTemplate.tmpl -o out.txt +``` +More info about curl [here](https://curl.se/) but you can of course use any tool with stdout + +- Inline template +```sh +bafi.exe -i testdata.xml -o output.json -t "?{{toJSON .}}" +``` +- Working example with inline template +``` +curl.exe -s "https://api.predic8.de/shop/products/" | bafi.exe -f json -t "?{{range .products}}\"{{.name}}\",{{.product_url}}{{print \"\n\"}}{{end}}" +``` + +## Template formating +- Basic iterate over lines +``` +{{range .TOP_LEVEL.DATA_LINE}}{{.val1}}{{end}} +``` +- Get XML tag (-VALUE = tag name) +``` +{{index .Linked_Text "-VALUE"}} +``` +- Use functions (count val1 + val2) +``` +{{add .val1 .val2}} +``` +- If statement +``` +{{if gt (int $val1) (int $val2)}}Value1{{else}}Value2{{end}} is greater +``` +Check template.tmpl and testdata.xml for more advanced example + +(more detailed info on https://golang.org/pkg/text/template/ ) + +## Lua custom functions +Aside of builtin functions you can write your own custom lua functions defined in ./lua/functions.lua file + +- Input is always passed as json array of strings +- Output must be passed as string +- lua table array starts with 1 +- Lua [documentation](http://www.lua.org/manual/5.1/) + +Minimal functions.lua example +```lua +json = require './lua/json' + +function sum(incomingData) + dataTable = json.decode(incomingData) + return tostring(tonumber(dataTable[1]) + tonumber(dataTable[2])) +end +``` + +### Call Lua function in template +``` +{{lua "sum" .val1 .val2}} +``` +- "sum" - Lua function name + +### Call built-in function +``` +{{add .val1 .val2}} +``` + +## Built-in functions +- add -> {{add .Value1 .Value2}} +- add1 +- sub +- div +- mod +- mul +- randInt +- add1f - "...f" functions parse float but provide **decimal** operation using https://github.com/shopspring/decimal +- addf +- subf +- divf +- mulf +- round +- max +- min +- maxf +- minf +- dateFormat -> {{dateFormat .Value "oldFormat" "newFormat"}} +- now - {{now "02.01.2006"}} - GO format date (see notes below) +- b64enc +- b64dec +- b32enc +- b32dec +- uuid +- regexMatch +- upper +- lower +- trim +- trimAll +- trimSuffix +- trimPrefix +- atoi +- int64 +- int +- float64 +- toJSON - convert input object to JSON +- toBSON - convert input object to BSON +- toYAML - convert input object to YAML +- toXML - convert input object to XML +- mapJSON - convert stringified JSON to map so it can be used as object or translated to other formats (e.g. "toXML"). Check template.tmpl for example + +### dateFormat +dateFormat can parse date and time using [GO time format](https://programming.guide/go/format-parse-string-time-date-example.html) + +## Relase builds (download) +- Releases (windows, linux, mac) can be found [here](https://github.com/mmalcek/bafi/releases) \ No newline at end of file diff --git a/docs/examples.md b/docs/examples.md new file mode 100644 index 0000000..a0788ef --- /dev/null +++ b/docs/examples.md @@ -0,0 +1,4 @@ +# Examples + +## Basic usage +- TODO - Add examples \ No newline at end of file diff --git a/docs/img/bafi.svg b/docs/img/bafi.svg new file mode 100644 index 0000000..9043b71 --- /dev/null +++ b/docs/img/bafi.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + diff --git a/docs/img/bafiIcon.svg b/docs/img/bafiIcon.svg new file mode 100644 index 0000000..0c8aa87 --- /dev/null +++ b/docs/img/bafiIcon.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + diff --git a/docs/img/favicon.ico b/docs/img/favicon.ico new file mode 100644 index 0000000..337e41d Binary files /dev/null and b/docs/img/favicon.ico differ diff --git a/functions.go b/functions.go new file mode 100644 index 0000000..58537a6 --- /dev/null +++ b/functions.go @@ -0,0 +1,283 @@ +// Inspired by https://github.com/Masterminds/sprig +package main + +import ( + "encoding/base32" + "encoding/base64" + "encoding/json" + "fmt" + "log" + "math" + "math/rand" + "regexp" + "strconv" + "strings" + "text/template" + "time" + + "github.com/clbanning/mxj/v2" + "github.com/go-yaml/yaml" + "github.com/google/uuid" + "github.com/shopspring/decimal" + "github.com/spf13/cast" + lua "github.com/yuin/gopher-lua" + "go.mongodb.org/mongo-driver/bson" +) + +func templateFunctions() template.FuncMap { + return template.FuncMap{ + "lua": luaF, + "add": add, + "add1": func(i interface{}) int64 { return toInt64(i) + 1 }, + "sub": func(a, b interface{}) int64 { return toInt64(a) - toInt64(b) }, + "div": func(a, b interface{}) int64 { return toInt64(a) / toInt64(b) }, + "mod": func(a, b interface{}) int64 { return toInt64(a) % toInt64(b) }, + "mul": mul, + "randInt": func(min, max int) int { return rand.Intn(max-min) + min }, + "add1f": func(i interface{}) float64 { + return execDecimalOp(i, []interface{}{1}, func(d1, d2 decimal.Decimal) decimal.Decimal { return d1.Add(d2) }) + }, + "addf": func(i ...interface{}) float64 { + a := interface{}(float64(0)) + return execDecimalOp(a, i, func(d1, d2 decimal.Decimal) decimal.Decimal { return d1.Add(d2) }) + }, + "subf": func(a interface{}, v ...interface{}) float64 { + return execDecimalOp(a, v, func(d1, d2 decimal.Decimal) decimal.Decimal { return d1.Sub(d2) }) + }, + "divf": func(a interface{}, v ...interface{}) float64 { + return execDecimalOp(a, v, func(d1, d2 decimal.Decimal) decimal.Decimal { return d1.Div(d2) }) + }, + "mulf": func(a interface{}, v ...interface{}) float64 { + return execDecimalOp(a, v, func(d1, d2 decimal.Decimal) decimal.Decimal { return d1.Mul(d2) }) + }, + "round": round, + "max": max, + "min": min, + "maxf": maxf, + "minf": minf, + "dateFormat": dateFormat, + "now": now, + "b64enc": base64encode, + "b64dec": base64decode, + "b32enc": base32encode, + "b32dec": base32decode, + "uuid": func() string { return uuid.New().String() }, + "regexMatch": regexMatch, + "upper": strings.ToUpper, + "lower": strings.ToLower, + "trim": strings.TrimSpace, + "trimAll": func(a, b string) string { return strings.Trim(b, a) }, + "trimSuffix": func(a, b string) string { return strings.TrimSuffix(b, a) }, + "trimPrefix": func(a, b string) string { return strings.TrimPrefix(b, a) }, + "atoi": func(a string) int { i, _ := strconv.Atoi(a); return i }, + "int64": toInt64, + "int": toInt, + "float64": toFloat64, + "toJSON": toJSON, + "toBSON": toBSON, + "toYAML": toYAML, + "toXML": toXML, + "mapJSON": mapJSON, + } +} + +func mapJSON(input string) map[string]interface{} { + var mapData map[string]interface{} + if err := json.Unmarshal([]byte(input), &mapData); err != nil { + log.Fatalf("errorMapJSON: %s\n", err.Error()) + } + return mapData +} + +func toJSON(data map[string]interface{}) string { + if out, err := json.Marshal(data); err != nil { + return fmt.Sprintf("err: %s", err.Error()) + } else { + return string(out) + } +} + +func toBSON(data map[string]interface{}) string { + if out, err := bson.Marshal(data); err != nil { + return fmt.Sprintf("err: %s", err.Error()) + } else { + return string(out) + } +} + +func toYAML(data map[string]interface{}) string { + if out, err := yaml.Marshal(data); err != nil { + return fmt.Sprintf("err: %s", err.Error()) + } else { + return string(out) + } +} + +func toXML(data map[string]interface{}) string { + if out, err := mxj.AnyXml(data); err != nil { + return fmt.Sprintf("err: %s", err.Error()) + } else { + return string(out) + } +} + +func luaF(i ...interface{}) string { + if !luaReady { + return "error: ./lua/functions.lua file missing)" + } + strData, err := json.Marshal(i[1:]) + if err != nil { + return fmt.Sprintf("luaInputError: %s\r\n", err.Error()) + } + if err := luaData.CallByParam( + lua.P{Fn: luaData.GetGlobal(i[0].(string)), NRet: 1, Protect: true}, lua.LString(string(strData))); err != nil { + return fmt.Sprintf("luaError: %s\r\n", err.Error()) + } + if str, ok := luaData.Get(-1).(lua.LString); ok { + luaData.Pop(1) + return str.String() + } else { + return "luaError: getResult" + } +} + +func dateFormat(date string, inputFormat string, outputFormat string) string { + timeParsed, err := time.Parse(inputFormat, date) + if err != nil { + return date + } + return timeParsed.Format(outputFormat) +} + +func now(format string) string { + return time.Now().Format(format) +} + +func add(i ...interface{}) int64 { + var a int64 = 0 + for _, b := range i { + a += toInt64(b) + } + return a +} + +func mul(a interface{}, v ...interface{}) int64 { + val := toInt64(a) + for _, b := range v { + val = val * toInt64(b) + } + return val +} + +func max(a interface{}, i ...interface{}) int64 { + aa := toInt64(a) + for _, b := range i { + bb := toInt64(b) + if bb > aa { + aa = bb + } + } + return aa +} + +func maxf(a interface{}, i ...interface{}) float64 { + aa := toFloat64(a) + for _, b := range i { + bb := toFloat64(b) + aa = math.Max(aa, bb) + } + return aa +} + +func min(a interface{}, i ...interface{}) int64 { + aa := toInt64(a) + for _, b := range i { + bb := toInt64(b) + if bb < aa { + aa = bb + } + } + return aa +} + +func minf(a interface{}, i ...interface{}) float64 { + aa := toFloat64(a) + for _, b := range i { + bb := toFloat64(b) + aa = math.Min(aa, bb) + } + return aa +} + +func round(a interface{}, p int, rOpt ...float64) float64 { + roundOn := .5 + if len(rOpt) > 0 { + roundOn = rOpt[0] + } + val := toFloat64(a) + places := toFloat64(p) + + var round float64 + pow := math.Pow(10, places) + digit := pow * val + _, div := math.Modf(digit) + if div >= roundOn { + round = math.Ceil(digit) + } else { + round = math.Floor(digit) + } + return round / pow +} + +func base64encode(v string) string { + return base64.StdEncoding.EncodeToString([]byte(v)) +} + +func base64decode(v string) string { + data, err := base64.StdEncoding.DecodeString(v) + if err != nil { + return err.Error() + } + return string(data) +} + +func base32encode(v string) string { + return base32.StdEncoding.EncodeToString([]byte(v)) +} + +func base32decode(v string) string { + data, err := base32.StdEncoding.DecodeString(v) + if err != nil { + return err.Error() + } + return string(data) +} + +func regexMatch(regex string, s string) bool { + match, _ := regexp.MatchString(regex, s) + return match +} + +// toFloat64 converts 64-bit floats +func toFloat64(v interface{}) float64 { + return cast.ToFloat64(v) +} + +func toInt(v interface{}) int { + return cast.ToInt(v) +} + +// toInt64 converts integer types to 64-bit integers +func toInt64(v interface{}) int64 { + return cast.ToInt64(v) +} + +func execDecimalOp(a interface{}, b []interface{}, f func(d1, d2 decimal.Decimal) decimal.Decimal) float64 { + prt := decimal.NewFromFloat(toFloat64(a)) + for _, x := range b { + dx := decimal.NewFromFloat(toFloat64(x)) + prt = f(prt, dx) + } + rslt, _ := prt.Float64() + return rslt +} diff --git a/functions_test.go b/functions_test.go new file mode 100644 index 0000000..99d274e --- /dev/null +++ b/functions_test.go @@ -0,0 +1,124 @@ +package main + +import ( + "testing" + + "github.com/shopspring/decimal" +) + +func TestLuaF(t *testing.T) { + if luaF("sum", "5", "5") != "10" { + t.Errorf("luaF unexpected result: %s", luaF("sum", "5", "5")) + } +} + +func TestDateFormat(t *testing.T) { + if dateFormat("15.03.2021", "02.01.2006", "01022006") != "03152021" { + t.Errorf("dateFormat unexpected result: %s", dateFormat("15.03.2021", "02.01.2006", "01022006")) + } +} + +func TestAdd(t *testing.T) { + if add("6", 4) != 10 { + t.Errorf("add unexpected result: %v", add("6", 4)) + } +} + +func TestMul(t *testing.T) { + if mul("6", 4) != 24 { + t.Errorf("mul unexpected result: %v", mul("6", 4)) + } +} + +func TestMax(t *testing.T) { + if max("6", 4, "12", "5") != 12 { + t.Errorf("max unexpected result: %v", max("6", 4, "12", "5")) + } +} + +func TestMaxf(t *testing.T) { + if maxf("6.32", 4.15, "12.3128", "5") != 12.3128 { + t.Errorf("maxf unexpected result: %v", maxf("6.32", 4.15, "12.3128", "5")) + } +} + +func TestMin(t *testing.T) { + if min("6", 4, "12", "5") != 4 { + t.Errorf("min unexpected result: %v", min("6", 4, "12", "5")) + } +} + +func TestMinf(t *testing.T) { + if minf("6.32", 4.15, "12.3128", "5") != 4.15 { + t.Errorf("minf unexpected result: %v", minf("6.32", 4.15, "12.3128", "5")) + } +} + +func TestRound(t *testing.T) { + if round("6.32487", 2) != 6.32 { + t.Errorf("round unexpected result: %v", round("6.32487", 2)) + } +} + +func TestBase64encode(t *testing.T) { + if base64encode("Hello World!") != "SGVsbG8gV29ybGQh" { + t.Errorf("base64encode unexpected result: %v", base64encode("Hello World!")) + } +} + +func TestBase64decode(t *testing.T) { + if base64decode("SGVsbG8gV29ybGQh") != "Hello World!" { + t.Errorf("base64decode unexpected result: %v", base64decode("SGVsbG8gV29ybGQh")) + } +} + +func TestBase32encode(t *testing.T) { + if base32encode("Hello World!") != "JBSWY3DPEBLW64TMMQQQ====" { + t.Errorf("base32encode unexpected result: %v", base32encode("Hello World!")) + } +} + +func TestBase32decode(t *testing.T) { + if base32decode("JBSWY3DPEBLW64TMMQQQ====") != "Hello World!" { + t.Errorf("base32decode unexpected result: %v", base32decode("JBSWY3DPEBLW64TMMQQQ====")) + } +} + +func TestToFloat64(t *testing.T) { + if toFloat64("3.14159265") != 3.14159265 { + t.Errorf("toFloat64 unexpected result: %v", toFloat64("3.14159265")) + } +} + +func TestToInt(t *testing.T) { + if toInt("42") != 42 { + t.Errorf("toInt unexpected result: %d", toInt("42")) + } +} + +func TestToInt64(t *testing.T) { + if toInt64("42") != 42 { + t.Errorf("toInt unexpected result: %d", toInt64("42")) + } +} + +func TestRegexMatch(t *testing.T) { + if !regexMatch(`a.b`, "aaxbb") { + t.Errorf("regexMatch unexpected result: %v", regexMatch(`^a.b$`, "aaxbb")) + } +} + +func TestExecDecimalOp(t *testing.T) { + testMulf := func(a interface{}, v ...interface{}) float64 { + return execDecimalOp(a, v, func(d1, d2 decimal.Decimal) decimal.Decimal { return d1.Mul(d2) }) + } + if testMulf(6.2154, "4.35") != 27.03699 { + t.Errorf("testMulf unexpected result: %v", testMulf(6.2154, "4.35")) + } + testDivf := func(a interface{}, v ...interface{}) float64 { + return execDecimalOp(a, v, func(d1, d2 decimal.Decimal) decimal.Decimal { return d1.Div(d2) }) + } + if testDivf(6.2154, "4.35") != 1.4288275862068966 { + t.Errorf("testDivf unexpected result: %v", testDivf(6.2154, "4.35")) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f03e7e8 --- /dev/null +++ b/go.mod @@ -0,0 +1,14 @@ +module bafi + +go 1.16 + +require ( + github.com/clbanning/mxj/v2 v2.5.5 + github.com/go-yaml/yaml v2.1.0+incompatible + github.com/google/uuid v1.1.1 + github.com/shopspring/decimal v1.2.0 + github.com/spf13/cast v1.3.1 + github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 + go.mongodb.org/mongo-driver v1.5.4 + gopkg.in/yaml.v2 v2.3.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..53583cd --- /dev/null +++ b/go.sum @@ -0,0 +1,131 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E= +github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= +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/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o= +github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= +github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= +github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= +github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= +github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= +github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= +github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= +github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= +github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= +github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= +github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= +github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= +github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= +github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= +github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= +github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= +github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLCur2yWnJzwQEKRcAHXo6seXGuSwWw= +github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= +go.mongodb.org/mongo-driver v1.5.4 h1:NPIBF/lxEcKNfWwoCJRX8+dMVwecWf9q3qUJkuh75oM= +go.mongodb.org/mongo-driver v1.5.4/go.mod h1:gRXCHX4Jo7J0IJ1oDQyUxF7jfy19UfxniMS4xxMmUqw= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/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-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/lua/functions.lua b/lua/functions.lua new file mode 100644 index 0000000..78667d3 --- /dev/null +++ b/lua/functions.lua @@ -0,0 +1,11 @@ +json = require './lua/json' + +function sum(incomingData) + dataTable = json.decode(incomingData) + return tostring(tonumber(dataTable[1]) + tonumber(dataTable[2])) +end + +function mul(incomingData) + dataTable = json.decode(incomingData) + return tostring(tonumber(dataTable[1]) * tonumber(dataTable[2])) +end \ No newline at end of file diff --git a/lua/json.lua b/lua/json.lua new file mode 100644 index 0000000..00ddb38 --- /dev/null +++ b/lua/json.lua @@ -0,0 +1,389 @@ +-- (https://github.com/rxi/json.lua) +-- +-- json.lua +-- +-- Copyright (c) 2020 rxi +-- +-- 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 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. +-- + +local json = { _version = "0.1.2" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\", + [ "\"" ] = "\"", + [ "\b" ] = "b", + [ "\f" ] = "f", + [ "\n" ] = "n", + [ "\r" ] = "r", + [ "\t" ] = "t", +} + +local escape_char_map_inv = { [ "/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if rawget(val, 1) ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(1, 4), 16 ) + local n2 = tonumber( s:sub(7, 10), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local res = "" + local j = i + 1 + local k = j + + while j <= #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + + elseif x == 92 then -- `\`: Escape + res = res .. str:sub(k, j - 1) + j = j + 1 + local c = str:sub(j, j) + if c == "u" then + local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) + or str:match("^%x%x%x%x", j + 1) + or decode_error(str, j - 1, "invalid unicode escape in string") + res = res .. parse_unicode_escape(hex) + j = j + #hex + else + if not escape_chars[c] then + decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") + end + res = res .. escape_char_map_inv[c] + end + k = j + 1 + + elseif x == 34 then -- `"`: End of string + res = res .. str:sub(k, j - 1) + return res, j + 1 + end + + j = j + 1 + end + + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end + + +return json diff --git a/main.go b/main.go new file mode 100644 index 0000000..befebce --- /dev/null +++ b/main.go @@ -0,0 +1,130 @@ +package main + +import ( + "bytes" + "encoding/json" + "flag" + "fmt" + "io/ioutil" + "log" + "os" + "strings" + "text/template" + + "github.com/clbanning/mxj/v2" + lua "github.com/yuin/gopher-lua" + "go.mongodb.org/mongo-driver/bson" + "gopkg.in/yaml.v2" +) + +const version = "1.0.4" + +var ( + luaData *lua.LState + luaReady = false +) + +func init() { + if _, err := os.Stat("./lua/functions.lua"); !os.IsNotExist(err) { + luaData = lua.NewState() + err := luaData.DoFile("./lua/functions.lua") + if err != nil { + log.Fatal("loadLuaFunctions", err.Error()) + } + luaReady = true + } +} + +func main() { + inputFile := flag.String("i", "", "input file, if not defined read from stdin (pipe mode)") + outputFile := flag.String("o", "", "output file, if not defined stdout is used") + textTemplate := flag.String("t", "", "template, file or inline. Inline should start with ? e.g. -t \"?{{.MyValue}}\" ") + inputFormat := flag.String("f", "", "input format (json,xml), default xml") + getVersion := flag.Bool("v", false, "template") + flag.Parse() + + if *getVersion { + fmt.Printf("Version: %s", version) + os.Exit(0) + } + + if *textTemplate == "" { + log.Fatal("template file must be defined: -t template.tmpl") + } + + var data []byte + var err error + if *inputFile == "" { + if fi, err := os.Stdin.Stat(); err != nil { + log.Fatal("getStdin: ", err.Error()) + } else { + if fi.Mode()&os.ModeNamedPipe == 0 { + log.Fatal("stdin: Error-noPipe") + } + } + data, err = ioutil.ReadAll(os.Stdin) + if err != nil { + log.Fatal("readStdin: ", err.Error()) + } + } else { + data, err = ioutil.ReadFile(*inputFile) + if err != nil { + log.Fatal("readFile: ", err.Error()) + } + } + var mapData map[string]interface{} + switch strings.ToLower(*inputFormat) { + case "json": + if err := json.Unmarshal(data, &mapData); err != nil { + log.Fatal("mapJSON: ", err.Error()) + } + case "bson": + if err := bson.Unmarshal(data, &mapData); err != nil { + log.Fatal("mapBSON: ", err.Error()) + } + case "yaml": + if err := yaml.Unmarshal(data, &mapData); err != nil { + log.Fatal("mapYAML: ", err.Error()) + } + default: + mapData, err = mxj.NewMapXml(data) + if err != nil { + log.Fatal("mapXML: ", err.Error()) + } + } + textTemplateVar := *textTemplate + var templateFile []byte + if textTemplateVar[:1] == "?" { + templateFile = []byte(textTemplateVar[1:]) + } else { + templateFile, err = ioutil.ReadFile(*textTemplate) + if err != nil { + log.Fatal("readFile: ", err.Error()) + } + } + template, err := template.New("new").Funcs(templateFunctions()).Parse(string(templateFile)) + if err != nil { + log.Fatal("parseTemplate: ", err.Error()) + } + if *outputFile == "" { + output := new(bytes.Buffer) + err = template.Execute(output, mapData) + if err != nil { + log.Fatal("writeStdout: ", err.Error()) + } + fmt.Print(output) + } else { + output, err := os.Create(*outputFile) + if err != nil { + log.Fatal("createOutputFile: ", err.Error()) + } + defer output.Close() + err = template.Execute(output, mapData) + if err != nil { + log.Fatal("writeOutputFile: ", err.Error()) + } + } + if luaReady { + luaData.Close() + } +} diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..8b99b82 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,10 @@ +site_name: bafi +site_url: https://mmalcek.github.io/bafi +nav: + - Home: README.md + - Examples: examples.md +theme: + name: mkdocs + highlightjs: true + hljs_languages: + - lua diff --git a/template.tmpl b/template.tmpl new file mode 100644 index 0000000..4aa1c56 --- /dev/null +++ b/template.tmpl @@ -0,0 +1,31 @@ +------------------------------------------------------------------------------ +Employee,Date,val1,val2,val3,SUM,LuaMultiply,linkedText +{{- /* {{- ...}} - minus trim whitespace */}} +{{- range .TOP_LEVEL.DATA_LINE}} +{{index .Employee "-ID"}},{{dateFormat .Trans_Date "2006-01-02" "02.01.2006"}},{{.val1}},{{.val2}},{{.val3}},{{add .val1 .val2}},{{lua "mul" .val1 .val2}},"{{index .Linked_Text "-VALUE"}}" +{{- end}} +------------------------------------------------------------------------------ +{{- /* Use variable and iterate over lines to get SUM */}} +{{- $TotalV1 := 0 }} +{{- range .TOP_LEVEL.DATA_LINE}}{{$TotalV1 = lua "sum" $TotalV1 .val1}}{{end}} +{{- $TotalV3 := 0 }} +{{- /* addf - will provide decimal count */}} +{{- range .TOP_LEVEL.DATA_LINE}}{{$TotalV3 = addf $TotalV3 .val3}}{{end}} +Total: +{{- /* if functions and,or,not,eq(equal),lt(lessThen),gt(greatherThen) */}} +Val1: {{$TotalV1}} - {{if gt (int $TotalV1) 50}}Over Budget{{else}}Under Buget{{end}} +Val3: {{$TotalV3}} +Created at: {{now "02.01.2006 - 15:04:05"}} +------------------------------------------------------------------------------ +{{- $new := "{\"employees\": [" }} +{{- range .TOP_LEVEL.DATA_LINE}} +{{- $new = print $new "{\"employeeID\":\"" (index .Employee "-ID") "\", \"val1\":" .val1 "}," }} +{{- end}} +{{- /* "slice $new 0 (sub (len $new) 1" - remove trailing comma */}} +{{- $new = print (slice $new 0 (sub (len $new) 1)) "]}" }} +JSON formated data: +{{ $new}} +------------------------------------------------------------------------------ +JSON Converted to map and marshal to YAML: +{{toYAML (mapJSON $new)}} + diff --git a/testdata.xml b/testdata.xml new file mode 100644 index 0000000..aad6c27 --- /dev/null +++ b/testdata.xml @@ -0,0 +1,33 @@ + + + + + + 2021-05-21 + 5 + 12 + 18.3285 + + + + + + + 2021-05-23 + 43 + 9 + 5.67343 + + + + + + + 2021-05-23 + 14 + 4 + 2.97984 + + + + \ No newline at end of file