Refactor reducers

master
Anton Medvedev 2 years ago
parent a58152469f
commit d7b5ab7200

@ -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,

@ -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
}

@ -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)
}
}

@ -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)
}

@ -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 {

@ -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():]))
}

@ -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 {

@ -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
}

@ -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)
})
}

@ -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
}

Loading…
Cancel
Save