Add support for python reducers

sleep-stdin-bug
Anton Medvedev 2 years ago
parent 73bf2d97cd
commit 1b09c93447

@ -0,0 +1,93 @@
package reducer
import (
_ "embed"
"fmt"
"os"
"os/exec"
"regexp"
"strings"
)
func CreateNodejs(args []string) *exec.Cmd {
cmd := exec.Command("node", "-e", GenerateCode(args))
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, "NODE_OPTIONS=--max-old-space-size=8192")
return cmd
}
//go:embed reduce.js
var templateJs string
func GenerateCodeNodejs(args []string) string {
rs := "\n"
for i, a := range args {
rs += " try {"
switch {
case a == ".":
rs += `
x = function ()
{ return this }
.call(x)
`
case flatMapRegex.MatchString(a):
code := fold(strings.Split(a, "[]"))
rs += fmt.Sprintf(
`
x = (
%v
)(x)
`, code)
case strings.HasPrefix(a, ".["):
rs += fmt.Sprintf(
`
x = function ()
{ return this%v }
.call(x)
`, a[1:])
case strings.HasPrefix(a, "."):
rs += fmt.Sprintf(
`
x = function ()
{ return this%v }
.call(x)
`, a)
default:
rs += fmt.Sprintf(
`
f = function ()
{ return %v }
.call(x)
x = typeof f === 'function' ? f(x) : fn
`, a)
}
// Generate a beautiful error message.
rs += " } catch (e) {\n"
pre, post, pointer := trace(args, i)
rs += fmt.Sprintf(
" throw `\\n ${%q} ${%q} ${%q}\\n %v\\n\\n${e.stack || e}`\n",
pre, a, post, pointer,
)
rs += " }\n"
}
return fmt.Sprintf(templateJs, rs)
}
var flatMapRegex = regexp.MustCompile("^(\\.\\w*)+\\[]")
func fold(s []string) string {
if len(s) == 1 {
return "x => x" + s[0]
}
obj := s[0]
if obj == "." {
obj = "x"
} else {
obj = "x" + obj
}
return fmt.Sprintf("x => Object.values(%v).flatMap(%v)", obj, fold(s[1:]))
}

@ -0,0 +1,46 @@
package reducer
import (
_ "embed"
"fmt"
"os/exec"
)
func CreatePython(bin string, args []string) *exec.Cmd {
cmd := exec.Command(bin, "-c", GenerateCode(args))
return cmd
}
//go:embed reduce.py
var templatePython string
func GenerateCodePython(args []string) string {
rs := "\n"
for i, a := range args {
rs += "try:"
switch {
case a == ".":
rs += `
x = x
`
default:
rs += fmt.Sprintf(
`
f = (lambda json: (%v))(x)
x = f(x) if callable(f) else f
`, a)
}
// Generate a beautiful error message.
rs += "except Exception as e:\n"
pre, post, pointer := trace(args, i)
rs += fmt.Sprintf(
` sys.stderr.write('\n {} {} {}\n %v\n\n{}\n'.format(%q, %q, %q, e))
sys.exit(1)`,
pointer,
pre, a, post,
)
rs += "\n"
}
return fmt.Sprintf(templatePython, rs)
}

@ -5,112 +5,45 @@ import (
_ "embed" _ "embed"
"encoding/json" "encoding/json"
"fmt" "fmt"
. "github.com/antonmedv/fx/pkg/json"
. "github.com/antonmedv/fx/pkg/theme"
"os" "os"
"os/exec" "os/exec"
"regexp"
"strings" "strings"
)
//go:embed reduce.js . "github.com/antonmedv/fx/pkg/json"
var template string . "github.com/antonmedv/fx/pkg/theme"
)
var flatMapRegex = regexp.MustCompile("^(\\.\\w*)+\\[]")
func GenerateCode(args []string) string { func GenerateCode(args []string) string {
rs := "\n" lang, ok := os.LookupEnv("FX_LANG")
for i, a := range args { if !ok {
rs += " try {" lang = "node"
switch { }
case a == ".": switch {
rs += ` case lang == "node":
json = function () return GenerateCodeNodejs(args)
{ return this } case strings.HasPrefix(lang, "python"):
.call(json) return GenerateCodePython(args)
` default:
panic("unknown lang")
case flatMapRegex.MatchString(a):
code := fold(strings.Split(a, "[]"))
rs += fmt.Sprintf(
`
json = (
%v
)(json)
`, code)
case strings.HasPrefix(a, ".["):
rs += fmt.Sprintf(
`
json = function ()
{ return this%v }
.call(json)
`, a[1:])
case strings.HasPrefix(a, "."):
rs += fmt.Sprintf(
`
json = function ()
{ return this%v }
.call(json)
`, a)
default:
rs += fmt.Sprintf(
`
fn = function ()
{ return %v }
.call(json)
json = typeof fn === 'function' ? json = fn(json) : fn
`, a)
}
// Generate a beautiful error message.
rs += " } catch (e) {\n"
pre := strings.Join(args[:i], " ")
if len(pre) > 20 {
pre = "..." + pre[len(pre)-20:]
}
post := strings.Join(args[i+1:], " ")
if len(post) > 20 {
post = post[:20] + "..."
}
pointer := fmt.Sprintf(
"%v %v %v",
strings.Repeat(" ", len(pre)),
strings.Repeat("^", len(a)),
strings.Repeat(" ", len(post)),
)
rs += fmt.Sprintf(
" throw `\\n"+
" ${%q} ${%q} ${%q}\\n"+
" %v\\n"+
"\\n${e.stack || e}`\n",
pre, a, post,
pointer,
)
rs += " }\n"
} }
return fmt.Sprintf(template, rs)
} }
func fold(s []string) string { func Reduce(object interface{}, args []string, theme Theme) {
if len(s) == 1 { var cmd *exec.Cmd
return "x => x" + s[0] lang, ok := os.LookupEnv("FX_LANG")
if !ok {
lang = "node"
} }
obj := s[0] switch {
if obj == "." { case lang == "node":
obj = "x" cmd = CreateNodejs(args)
} else { case strings.HasPrefix(lang, "python"):
obj = "x" + obj cmd = CreatePython(lang, args)
default:
panic("unknown lang")
} }
return fmt.Sprintf("x => Object.values(%v).flatMap(%v)", obj, fold(s[1:]))
}
func Reduce(object interface{}, args []string, theme Theme) {
var stdout, stderr bytes.Buffer var stdout, stderr bytes.Buffer
cmd := exec.Command("node", "-e", GenerateCode(args))
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, "NODE_OPTIONS=--max-old-space-size=8192")
cmd.Stdin = strings.NewReader(Stringify(object)) cmd.Stdin = strings.NewReader(Stringify(object))
cmd.Stdout = &stdout cmd.Stdout = &stdout
cmd.Stderr = &stderr cmd.Stderr = &stderr
@ -138,3 +71,21 @@ func Reduce(object interface{}, args []string, theme Theme) {
os.Exit(exitCode) os.Exit(exitCode)
} }
} }
func trace(args []string, i int) (pre, post, pointer string) {
pre = strings.Join(args[:i], " ")
if len(pre) > 20 {
pre = "..." + pre[len(pre)-20:]
}
post = strings.Join(args[i+1:], " ")
if len(post) > 20 {
post = post[:20] + "..."
}
pointer = fmt.Sprintf(
"%v %v %v",
strings.Repeat(" ", len(pre)),
strings.Repeat("^", len(args[i])),
strings.Repeat(" ", len(post)),
)
return
}

@ -14,14 +14,14 @@ void async function () {
for await (let chunk of process.stdin) { for await (let chunk of process.stdin) {
buffer += chunk buffer += chunk
} }
let json = JSON.parse(buffer) let x = JSON.parse(buffer)
// Reducers %v // Reducers %v
if (typeof json === 'undefined') { if (typeof x === 'undefined') {
process.stderr.write('undefined') process.stderr.write('undefined')
} else { } else {
process.stdout.write(JSON.stringify(json)) process.stdout.write(JSON.stringify(x))
} }
}().catch(err => { }().catch(err => {
console.error(err) console.error(err)

@ -0,0 +1,9 @@
import json, sys, os
x = json.load(sys.stdin)
# Reducers %v
try:
print(json.dumps(x))
except:
print(json.dumps(list(x)))
Loading…
Cancel
Save