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 @@
+[](https://github.com/mmalcek/bafi/actions/workflows/go.yml)
+[](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