From d7b5ab72004fa55da90f45142c1f595ae852343e Mon Sep 17 00:00:00 2001 From: Anton Medvedev Date: Sun, 8 May 2022 15:13:09 +0200 Subject: [PATCH] Refactor reducers --- main.go | 41 +++++++--- pkg/reducer/js.go | 50 ++++++++--- pkg/reducer/js.js | 12 +++ pkg/reducer/{nodejs.go => node.go} | 20 ++--- pkg/reducer/{reduce.js => node.js} | 0 pkg/reducer/python.go | 2 +- pkg/reducer/{reduce.py => python.py} | 0 pkg/reducer/reduce.go | 82 +++---------------- pkg/reducer/ruby.go | 2 +- pkg/reducer/{reduce.rb => ruby.rb} | 0 pkg/reducer/{split.go => simple.go} | 39 ++++++++- pkg/reducer/{split_test.go => simple_test.go} | 8 +- stream.go | 22 ++++- 13 files changed, 160 insertions(+), 118 deletions(-) create mode 100644 pkg/reducer/js.js rename pkg/reducer/{nodejs.go => node.go} (80%) rename pkg/reducer/{reduce.js => node.js} (100%) rename pkg/reducer/{reduce.py => python.py} (100%) rename pkg/reducer/{reduce.rb => ruby.rb} (100%) rename pkg/reducer/{split.go => simple.go} (78%) rename pkg/reducer/{split_test.go => simple_test.go} (93%) diff --git a/main.go b/main.go index 8a9ed2a..7ed1f24 100644 --- a/main.go +++ b/main.go @@ -39,7 +39,7 @@ func main() { } } - if flagVersion && len(args) == 0 { + if flagVersion { fmt.Println(version) return } @@ -96,7 +96,7 @@ func main() { os.Exit(1) } dec.UseNumber() - jsonObject, err := Parse(dec) + object, err := Parse(dec) if err != nil { fmt.Println("JSON Parse Error:", err.Error()) os.Exit(1) @@ -105,22 +105,43 @@ func main() { if !ok { lang = "js" } + var fxrc string + if lang == "js" || lang == "node" { + home, err := os.UserHomeDir() + if err == nil { + b, err := os.ReadFile(path.Join(home, ".fxrc.js")) + if err == nil { + fxrc = "\n" + string(b) + if lang == "js" { + parts := strings.SplitN(fxrc, "// nodejs:", 2) + fxrc = parts[0] + } + } + } + } if dec.More() { - exitCode := stream(dec, jsonObject, lang, args, theme) - os.Exit(exitCode) + os.Exit(stream(dec, object, lang, args, theme, fxrc)) } if len(args) > 0 || !stdoutIsTty { if len(args) > 0 && flagPrintCode { - fmt.Print(GenerateCode(lang, args)) + fmt.Print(GenerateCode(lang, args, fxrc)) return } - exitCode := Reduce(jsonObject, lang, args, theme) - os.Exit(exitCode) + if lang == "js" { + vm, fn, err := CreateJS(args, fxrc) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + os.Exit(ReduceJS(vm, fn, object, theme)) + } else { + os.Exit(Reduce(object, lang, args, theme, fxrc)) + } } // Start interactive mode. expand := map[string]bool{"": true} - if array, ok := jsonObject.(Array); ok { + if array, ok := object.(Array); ok { for i := range array { expand[accessor("", i)] = true } @@ -128,7 +149,7 @@ func main() { parents := map[string]string{} children := map[string][]string{} canBeExpanded := map[string]bool{} - Dfs(jsonObject, func(it Iterator) { + Dfs(object, func(it Iterator) { parents[it.Path] = it.Parent children[it.Parent] = append(children[it.Parent], it.Path) switch it.Object.(type) { @@ -143,7 +164,7 @@ func main() { m := &model{ fileName: fileName, theme: theme, - json: jsonObject, + json: object, width: 80, height: 60, mouseWheelDelta: 3, diff --git a/pkg/reducer/js.go b/pkg/reducer/js.go index 1b6dcf5..169ddd6 100644 --- a/pkg/reducer/js.go +++ b/pkg/reducer/js.go @@ -2,11 +2,19 @@ package reducer import ( _ "embed" + "encoding/json" "fmt" "strings" + + . "github.com/antonmedv/fx/pkg/json" + . "github.com/antonmedv/fx/pkg/theme" + "github.com/dop251/goja" ) -func js(args []string) string { +//go:embed js.js +var templateJs string + +func js(args []string, fxrc string) string { rs := "\n" for i, a := range args { rs += " try {" @@ -62,16 +70,36 @@ func js(args []string) string { rs += " }\n" } - fn := `function reduce(input) { - let x = JSON.parse(input) + return fmt.Sprintf(templateJs, fxrc, rs) +} - // Reducers %v - if (typeof x === 'undefined') { - return 'null' - } else { - return JSON.stringify(x) - } +func CreateJS(args []string, fxrc string) (*goja.Runtime, goja.Callable, error) { + vm := goja.New() + _, err := vm.RunString(js(args, fxrc)) + if err != nil { + return nil, nil, err + } + fn, ok := goja.AssertFunction(vm.Get("reduce")) + if !ok { + panic("Not a function") + } + return vm, fn, nil } -` - return fmt.Sprintf(fn, rs) + +func ReduceJS(vm *goja.Runtime, reduce goja.Callable, input interface{}, theme Theme) int { + value, err := reduce(goja.Undefined(), vm.ToValue(Stringify(input))) + if err != nil { + fmt.Println(err) + return 1 + } + output := value.String() + dec := json.NewDecoder(strings.NewReader(output)) + dec.UseNumber() + object, err := Parse(dec) + if err != nil { + fmt.Print(output) + return 0 + } + echo(object, theme) + return 0 } diff --git a/pkg/reducer/js.js b/pkg/reducer/js.js new file mode 100644 index 0000000..60470a8 --- /dev/null +++ b/pkg/reducer/js.js @@ -0,0 +1,12 @@ +// .fxrc.js %v + +function reduce(input) { + let x = JSON.parse(input) + + // Reducers %v + if (typeof x === 'undefined') { + return 'null' + } else { + return JSON.stringify(x) + } +} diff --git a/pkg/reducer/nodejs.go b/pkg/reducer/node.go similarity index 80% rename from pkg/reducer/nodejs.go rename to pkg/reducer/node.go index aed7eab..3773e4c 100644 --- a/pkg/reducer/nodejs.go +++ b/pkg/reducer/node.go @@ -9,8 +9,8 @@ import ( "strings" ) -func CreateNodejs(args []string) *exec.Cmd { - cmd := exec.Command("node", "--input-type=module", "-e", nodejs(args)) +func CreateNodejs(args []string, fxrc string) *exec.Cmd { + cmd := exec.Command("node", "--input-type=module", "-e", nodejs(args, fxrc)) nodePath, exist := os.LookupEnv("NODE_PATH") if exist { cmd.Dir = path.Dir(nodePath) @@ -24,10 +24,10 @@ func CreateNodejs(args []string) *exec.Cmd { return cmd } -//go:embed reduce.js -var templateJs string +//go:embed node.js +var templateNode string -func nodejs(args []string) string { +func nodejs(args []string, fxrc string) string { rs := "\n" for i, a := range args { rs += " try {" @@ -86,13 +86,5 @@ func nodejs(args []string) string { rs += " }\n" } - fxrc := "" - home, err := os.UserHomeDir() - if err == nil { - b, err := os.ReadFile(path.Join(home, ".fxrc.js")) - if err == nil { - fxrc = "\n" + string(b) - } - } - return fmt.Sprintf(templateJs, fxrc, rs) + return fmt.Sprintf(templateNode, fxrc, rs) } diff --git a/pkg/reducer/reduce.js b/pkg/reducer/node.js similarity index 100% rename from pkg/reducer/reduce.js rename to pkg/reducer/node.js diff --git a/pkg/reducer/python.go b/pkg/reducer/python.go index d42f968..888ca61 100644 --- a/pkg/reducer/python.go +++ b/pkg/reducer/python.go @@ -11,7 +11,7 @@ func CreatePython(bin string, args []string) *exec.Cmd { return cmd } -//go:embed reduce.py +//go:embed python.py var templatePython string func python(args []string) string { diff --git a/pkg/reducer/reduce.py b/pkg/reducer/python.py similarity index 100% rename from pkg/reducer/reduce.py rename to pkg/reducer/python.py diff --git a/pkg/reducer/reduce.go b/pkg/reducer/reduce.go index 9552691..5fd142c 100644 --- a/pkg/reducer/reduce.go +++ b/pkg/reducer/reduce.go @@ -6,21 +6,18 @@ import ( "encoding/json" "fmt" "os/exec" - "strconv" "strings" - . "github.com/antonmedv/fx/pkg/dict" . "github.com/antonmedv/fx/pkg/json" . "github.com/antonmedv/fx/pkg/theme" - "github.com/dop251/goja" ) -func GenerateCode(lang string, args []string) string { +func GenerateCode(lang string, args []string, fxrc string) string { switch lang { case "js": - return js(args) + return js(args, fxrc) case "node": - return nodejs(args) + return nodejs(args, fxrc) case "python", "python3": return python(args) case "ruby": @@ -30,76 +27,17 @@ func GenerateCode(lang string, args []string) string { } } -func Reduce(input interface{}, lang string, args []string, theme Theme) int { - // TODO: Move to separate function. - path, ok := split(args) +func Reduce(input interface{}, lang string, args []string, theme Theme, fxrc string) int { + path, ok := splitPath(args) if ok { - for _, get := range path { - switch get := get.(type) { - case string: - switch o := input.(type) { - case *Dict: - input = o.Values[get] - case string: - if get == "length" { - input = Number(strconv.Itoa(len([]rune(o)))) - } else { - input = nil - } - case Array: - if get == "length" { - input = Number(strconv.Itoa(len(o))) - } else { - input = nil - } - default: - input = nil - } - case int: - switch o := input.(type) { - case Array: - input = o[get] - default: - input = nil - } - } - } - echo(input, theme) + output := getByPath(input, path) + echo(output, theme) return 0 } - - // TODO: Remove switch and this Reduce function. var cmd *exec.Cmd switch lang { - case "js": - vm := goja.New() - _, err := vm.RunString(js(args)) - if err != nil { - fmt.Println(err) - return 1 - } - // TODO: Do not evaluate reduce function on every message in stream. - sum, ok := goja.AssertFunction(vm.Get("reduce")) - if !ok { - panic("Not a function") - } - res, err := sum(goja.Undefined(), vm.ToValue(Stringify(input))) - if err != nil { - fmt.Println(err) - return 1 - } - output := res.String() - dec := json.NewDecoder(strings.NewReader(output)) - dec.UseNumber() - jsonObject, err := Parse(dec) - if err != nil { - fmt.Print(output) - return 0 - } - echo(jsonObject, theme) - return 0 case "node": - cmd = CreateNodejs(args) + cmd = CreateNodejs(args, fxrc) case "python", "python3": cmd = CreatePython(lang, args) case "ruby": @@ -125,12 +63,12 @@ func Reduce(input interface{}, lang string, args []string, theme Theme) int { dec := json.NewDecoder(bytes.NewReader(output)) dec.UseNumber() - jsonObject, err := Parse(dec) + object, err := Parse(dec) if err != nil { fmt.Print(string(output)) return 0 } - echo(jsonObject, theme) + echo(object, theme) if dec.InputOffset() < int64(len(output)) { fmt.Print(string(output[dec.InputOffset():])) } diff --git a/pkg/reducer/ruby.go b/pkg/reducer/ruby.go index 88c91e2..82684be 100644 --- a/pkg/reducer/ruby.go +++ b/pkg/reducer/ruby.go @@ -11,7 +11,7 @@ func CreateRuby(args []string) *exec.Cmd { return cmd } -//go:embed reduce.rb +//go:embed ruby.rb var templateRuby string func ruby(args []string) string { diff --git a/pkg/reducer/reduce.rb b/pkg/reducer/ruby.rb similarity index 100% rename from pkg/reducer/reduce.rb rename to pkg/reducer/ruby.rb diff --git a/pkg/reducer/split.go b/pkg/reducer/simple.go similarity index 78% rename from pkg/reducer/split.go rename to pkg/reducer/simple.go index c1436b5..2d575d1 100644 --- a/pkg/reducer/split.go +++ b/pkg/reducer/simple.go @@ -3,6 +3,9 @@ package reducer import ( "strconv" "unicode" + + . "github.com/antonmedv/fx/pkg/dict" + . "github.com/antonmedv/fx/pkg/json" ) type state int @@ -21,7 +24,7 @@ const ( singleQuoteEscape ) -func split(args []string) ([]interface{}, bool) { +func splitPath(args []string) ([]interface{}, bool) { path := make([]interface{}, 0) for _, arg := range args { s := "" @@ -176,3 +179,37 @@ func split(args []string) ([]interface{}, bool) { func isProp(ch rune) bool { return unicode.IsLetter(ch) || unicode.IsDigit(ch) || ch == '_' || ch == '$' } + +func getByPath(object interface{}, path []interface{}) interface{} { + for _, get := range path { + switch get := get.(type) { + case string: + switch o := object.(type) { + case *Dict: + object = o.Values[get] + case string: + if get == "length" { + object = Number(strconv.Itoa(len([]rune(o)))) + } else { + object = nil + } + case Array: + if get == "length" { + object = Number(strconv.Itoa(len(o))) + } else { + object = nil + } + default: + object = nil + } + case int: + switch o := object.(type) { + case Array: + object = o[get] + default: + object = nil + } + } + } + return object +} diff --git a/pkg/reducer/split_test.go b/pkg/reducer/simple_test.go similarity index 93% rename from pkg/reducer/split_test.go rename to pkg/reducer/simple_test.go index 1b9efb7..e9828d0 100644 --- a/pkg/reducer/split_test.go +++ b/pkg/reducer/simple_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/require" ) -func Test_split(t *testing.T) { +func Test_splitPath(t *testing.T) { tests := []struct { args []string want []interface{} @@ -83,14 +83,14 @@ func Test_split(t *testing.T) { } for _, tt := range tests { t.Run(strings.Join(tt.args, " "), func(t *testing.T) { - path, ok := split(tt.args) + path, ok := splitPath(tt.args) require.Equal(t, tt.want, path) require.True(t, ok) }) } } -func Test_split_negative(t *testing.T) { +func Test_splitPath_negative(t *testing.T) { tests := []struct { args []string }{ @@ -130,7 +130,7 @@ func Test_split_negative(t *testing.T) { } for _, tt := range tests { t.Run(strings.Join(tt.args, " "), func(t *testing.T) { - path, ok := split(tt.args) + path, ok := splitPath(tt.args) require.False(t, ok, path) }) } diff --git a/stream.go b/stream.go index 00e425c..53a3114 100644 --- a/stream.go +++ b/stream.go @@ -8,15 +8,29 @@ import ( . "github.com/antonmedv/fx/pkg/json" . "github.com/antonmedv/fx/pkg/reducer" . "github.com/antonmedv/fx/pkg/theme" + "github.com/dop251/goja" ) -func stream(dec *json.Decoder, jsonObject interface{}, lang string, args []string, theme Theme) int { +func stream(dec *json.Decoder, object interface{}, lang string, args []string, theme Theme, fxrc string) int { + var vm *goja.Runtime + var fn goja.Callable var err error + if lang == "js" { + vm, fn, err = CreateJS(args, fxrc) + if err != nil { + fmt.Println(err) + return 1 + } + } for { - if jsonObject != nil { - Reduce(jsonObject, lang, args, theme) + if object != nil { + if lang == "js" { + ReduceJS(vm, fn, object, theme) + } else { + Reduce(object, lang, args, theme, fxrc) + } } - jsonObject, err = Parse(dec) + object, err = Parse(dec) if err == io.EOF { return 0 }