mirror of
https://github.com/antonmedv/fx.git
synced 2024-10-03 21:27:12 +03:00
Add fallback engine (#300)
This commit is contained in:
parent
b7c4bab9f1
commit
04f79a71d8
2
.github/workflows/snap.yml
vendored
2
.github/workflows/snap.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18
|
||||
go-version: 1.21
|
||||
|
||||
- uses: snapcore/action-build@v1
|
||||
id: build
|
||||
|
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -15,7 +15,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18
|
||||
go-version: 1.21
|
||||
|
||||
- name: Test
|
||||
run: go test ./...
|
||||
|
6
go.mod
6
go.mod
@ -1,6 +1,6 @@
|
||||
module github.com/antonmedv/fx
|
||||
|
||||
go 1.20
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/antonmedv/clipboard v1.0.1
|
||||
@ -23,10 +23,10 @@ require (
|
||||
github.com/aymanbagabas/go-udiff v0.1.3 // indirect
|
||||
github.com/containerd/console v1.0.4 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dlclark/regexp2 v1.7.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.10.0 // indirect
|
||||
github.com/fatih/color v1.16.0 // indirect
|
||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
|
||||
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect
|
||||
github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
|
13
go.sum
13
go.sum
@ -23,8 +23,9 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
||||
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/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
|
||||
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
|
||||
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
|
||||
github.com/dop251/goja v0.0.0-20240220182346-e401ed450204 h1:O7I1iuzEA7SG+dK8ocOBSlYAA9jBUmCYl/Qa7ey7JAM=
|
||||
github.com/dop251/goja v0.0.0-20240220182346-e401ed450204/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4=
|
||||
@ -33,15 +34,20 @@ github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
|
||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
|
||||
github.com/goccy/go-yaml v1.11.3 h1:B3W9IdWbvrUu2OYQGwvU1nZtvMQJPBKgBUuweJjLj6I=
|
||||
github.com/goccy/go-yaml v1.11.3/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
|
||||
github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 h1:pUa4ghanp6q4IJHwE9RwLgmVFfReJN+KbQ8ExNEUUoQ=
|
||||
github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
@ -52,7 +58,9 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
@ -89,6 +97,7 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
|
@ -1,6 +1,7 @@
|
||||
package complete
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@ -10,6 +11,7 @@ import (
|
||||
"github.com/dop251/goja"
|
||||
"github.com/goccy/go-yaml"
|
||||
|
||||
"github.com/antonmedv/fx/internal/engine"
|
||||
"github.com/antonmedv/fx/internal/shlex"
|
||||
)
|
||||
|
||||
@ -53,6 +55,9 @@ var globals = []string{
|
||||
"skip",
|
||||
}
|
||||
|
||||
//go:embed prelude.js
|
||||
var prelude string
|
||||
|
||||
func Complete() bool {
|
||||
compLine, ok := os.LookupEnv("COMP_LINE")
|
||||
|
||||
@ -145,7 +150,7 @@ func doComplete(compLine string, compWord string) {
|
||||
}
|
||||
}
|
||||
|
||||
codeComplete(string(input), args, compWord)
|
||||
codeComplete(input, args, compWord)
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,7 +168,7 @@ func globalsComplete(compWord string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func codeComplete(input string, args []string, compWord string) {
|
||||
func codeComplete(input []byte, args []string, compWord string) {
|
||||
args = args[2:] // Drop binary & file from the args.
|
||||
|
||||
if compWord == "" {
|
||||
@ -180,21 +185,24 @@ func codeComplete(input string, args []string, compWord string) {
|
||||
|
||||
var code strings.Builder
|
||||
code.WriteString(prelude)
|
||||
code.WriteString(fmt.Sprintf("let json = %s\n", input))
|
||||
code.WriteString(engine.Stdlib)
|
||||
code.WriteString("let json = ")
|
||||
code.Write(input)
|
||||
for _, arg := range args {
|
||||
if arg == "" {
|
||||
if arg == "" { // After dropTail, we can have empty strings.
|
||||
continue
|
||||
}
|
||||
code.WriteString(Transform(arg))
|
||||
code.WriteString(engine.Transform(arg))
|
||||
}
|
||||
code.WriteString("\n__keys\n")
|
||||
|
||||
out, err := goja.New().RunString(code.String())
|
||||
vm := goja.New()
|
||||
value, err := vm.RunString(code.String())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if array, ok := out.Export().([]interface{}); ok {
|
||||
if array, ok := value.Export().([]interface{}); ok {
|
||||
prefix := dropTail(compWord)
|
||||
var reply []string
|
||||
for _, key := range array {
|
||||
|
10
internal/complete/prelude.js
Normal file
10
internal/complete/prelude.js
Normal file
@ -0,0 +1,10 @@
|
||||
const __keys = new Set()
|
||||
|
||||
Object.prototype.__keys = function () {
|
||||
if (Array.isArray(this)) return
|
||||
if (typeof this === 'string') return
|
||||
if (this instanceof String) return
|
||||
if (typeof this === 'object' && this !== null)
|
||||
Object.keys(this).forEach(x => __keys.add(x))
|
||||
}
|
||||
|
126
internal/engine/engine.go
Normal file
126
internal/engine/engine.go
Normal file
@ -0,0 +1,126 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"github.com/goccy/go-yaml"
|
||||
|
||||
"github.com/antonmedv/fx/internal/jsonx"
|
||||
)
|
||||
|
||||
//go:embed stdlib.js
|
||||
var Stdlib string
|
||||
|
||||
//go:embed prelude.js
|
||||
var prelude string
|
||||
|
||||
func Reduce(args []string) {
|
||||
if len(args) < 1 {
|
||||
panic("args must have at least one element")
|
||||
}
|
||||
|
||||
var (
|
||||
flagYaml bool
|
||||
flagRaw bool
|
||||
flagSlurp bool
|
||||
)
|
||||
|
||||
var src io.Reader = os.Stdin
|
||||
if isFile(args[0]) {
|
||||
src = open(args[0], &flagYaml)
|
||||
args = args[1:]
|
||||
} else if isFile(args[len(args)-1]) {
|
||||
src = open(args[len(args)-1], &flagYaml)
|
||||
args = args[:len(args)-1]
|
||||
}
|
||||
|
||||
var fns []string
|
||||
for _, arg := range args {
|
||||
switch arg {
|
||||
case "--yaml":
|
||||
flagYaml = true
|
||||
case "--raw", "-r":
|
||||
flagRaw = true
|
||||
case "--slurp", "-s":
|
||||
flagSlurp = true
|
||||
case "-rs", "-sr":
|
||||
flagRaw = true
|
||||
flagSlurp = true
|
||||
default:
|
||||
fns = append(fns, arg)
|
||||
}
|
||||
}
|
||||
|
||||
if flagSlurp {
|
||||
println("Error: Built-in JS engine does not support \"--slurp\" flag. Install Node.js or Deno to use this flag.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(src)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if flagRaw {
|
||||
data = []byte(strconv.Quote(string(data)))
|
||||
} else if flagYaml {
|
||||
data, err = yaml.YAMLToJSON(data)
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
node, err := jsonx.Parse(data)
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
data = []byte(node.String())
|
||||
}
|
||||
|
||||
var code strings.Builder
|
||||
code.WriteString(prelude)
|
||||
code.WriteString(Stdlib)
|
||||
code.WriteString(fmt.Sprintf("let json = JSON.parse(%q)\n", data))
|
||||
for _, fn := range fns {
|
||||
code.WriteString(Transform(fn))
|
||||
}
|
||||
code.WriteString("JSON.stringify(json)")
|
||||
|
||||
vm := goja.New()
|
||||
vm.Set("println", func(s string) any {
|
||||
fmt.Println(s)
|
||||
return nil
|
||||
})
|
||||
|
||||
value, err := vm.RunString(code.String())
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
output, ok := value.Export().(string)
|
||||
if !ok {
|
||||
println("undefined")
|
||||
return
|
||||
}
|
||||
|
||||
node, err := jsonx.Parse([]byte(output))
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(node.Value) > 0 && node.Value[0] == '"' {
|
||||
s, _ := strconv.Unquote(string(node.Value))
|
||||
fmt.Println(s)
|
||||
return
|
||||
}
|
||||
fmt.Print(node.PrettyPrint())
|
||||
}
|
9
internal/engine/prelude.js
Normal file
9
internal/engine/prelude.js
Normal file
@ -0,0 +1,9 @@
|
||||
const console = {
|
||||
log: function (...args) {
|
||||
const parts = []
|
||||
for (const arg of args) {
|
||||
parts.push(typeof arg === 'string' ? arg : JSON.stringify(arg, null, 2))
|
||||
}
|
||||
println(parts.join(' '))
|
||||
},
|
||||
}
|
@ -1,16 +1,3 @@
|
||||
package complete
|
||||
|
||||
const prelude = `
|
||||
const __keys = new Set()
|
||||
|
||||
Object.prototype.__keys = function () {
|
||||
if (Array.isArray(this)) return
|
||||
if (typeof this === 'string') return
|
||||
if (this instanceof String) return
|
||||
if (typeof this === 'object' && this !== null)
|
||||
Object.keys(this).forEach(x => __keys.add(x))
|
||||
}
|
||||
|
||||
function apply(fn, ...args) {
|
||||
if (typeof fn === 'function') return fn(...args)
|
||||
return fn
|
||||
@ -20,23 +7,23 @@ function len(x) {
|
||||
if (Array.isArray(x)) return x.length
|
||||
if (typeof x === 'string') return x.length
|
||||
if (typeof x === 'object' && x !== null) return Object.keys(x).length
|
||||
throw new Error()
|
||||
throw new Error(`Cannot get length of ${typeof x}`)
|
||||
}
|
||||
|
||||
function uniq(x) {
|
||||
if (Array.isArray(x)) return [...new Set(x)]
|
||||
throw new Error()
|
||||
throw new Error(`Cannot get unique values of ${typeof x}`)
|
||||
}
|
||||
|
||||
function sort(x) {
|
||||
if (Array.isArray(x)) return x.sort()
|
||||
throw new Error()
|
||||
throw new Error(`Cannot sort ${typeof x}`)
|
||||
}
|
||||
|
||||
function map(fn) {
|
||||
return function (x) {
|
||||
if (Array.isArray(x)) return x.map((v, i) => fn(v, i))
|
||||
throw new Error()
|
||||
throw new Error(`Cannot map ${typeof x}`)
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,7 +34,7 @@ function sortBy(fn) {
|
||||
const fb = fn(b)
|
||||
return fa < fb ? -1 : fa > fb ? 1 : 0
|
||||
})
|
||||
throw new Error()
|
||||
throw new Error(`Cannot sort ${typeof x}`)
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,21 +72,20 @@ function zip(...x) {
|
||||
|
||||
function flatten(x) {
|
||||
if (Array.isArray(x)) return x.flat()
|
||||
throw new Error()
|
||||
throw new Error(`Cannot flatten ${typeof x}`)
|
||||
}
|
||||
|
||||
function reverse(x) {
|
||||
if (Array.isArray(x)) return x.reverse()
|
||||
throw new Error()
|
||||
throw new Error(`Cannot reverse ${typeof x}`)
|
||||
}
|
||||
|
||||
function keys(x) {
|
||||
if (typeof x === 'object' && x !== null) return Object.keys(x)
|
||||
throw new Error()
|
||||
throw new Error(`Cannot get keys of ${typeof x}`)
|
||||
}
|
||||
|
||||
function values(x) {
|
||||
if (typeof x === 'object' && x !== null) return Object.values(x)
|
||||
throw new Error()
|
||||
throw new Error(`Cannot get values of ${typeof x}`)
|
||||
}
|
||||
`
|
@ -1,4 +1,4 @@
|
||||
package complete
|
||||
package engine
|
||||
|
||||
import (
|
||||
"fmt"
|
36
internal/engine/utils.go
Normal file
36
internal/engine/utils.go
Normal file
@ -0,0 +1,36 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
func isFile(name string) bool {
|
||||
stat, err := os.Stat(name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return !stat.IsDir()
|
||||
}
|
||||
|
||||
func open(filePath string, flagYaml *bool) *os.File {
|
||||
f, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
var pathError *fs.PathError
|
||||
if errors.As(err, &pathError) {
|
||||
println(err.Error())
|
||||
os.Exit(1)
|
||||
} else {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
fileName := path.Base(filePath)
|
||||
hasYamlExt, _ := regexp.MatchString(`(?i)\.ya?ml$`, fileName)
|
||||
if !*flagYaml && hasYamlExt {
|
||||
*flagYaml = true
|
||||
}
|
||||
return f
|
||||
}
|
@ -1,10 +1,12 @@
|
||||
package main
|
||||
package jsonx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/antonmedv/fx/internal/utils"
|
||||
)
|
||||
|
||||
type jsonParser struct {
|
||||
@ -17,7 +19,7 @@ type jsonParser struct {
|
||||
skipFirstIdent bool
|
||||
}
|
||||
|
||||
func parse(data []byte) (head *node, err error) {
|
||||
func Parse(data []byte) (head *Node, err error) {
|
||||
p := &jsonParser{
|
||||
data: data,
|
||||
lineNumber: 1,
|
||||
@ -29,14 +31,14 @@ func parse(data []byte) (head *node, err error) {
|
||||
}
|
||||
}()
|
||||
p.next()
|
||||
var next *node
|
||||
var next *Node
|
||||
for p.lastChar != 0 {
|
||||
value := p.parseValue()
|
||||
if head == nil {
|
||||
head = value
|
||||
next = head
|
||||
} else {
|
||||
value.index = -1
|
||||
value.Index = -1
|
||||
next.adjacent(value)
|
||||
next = value
|
||||
}
|
||||
@ -57,10 +59,10 @@ func (p *jsonParser) next() {
|
||||
p.sourceTail.writeByte(p.lastChar)
|
||||
}
|
||||
|
||||
func (p *jsonParser) parseValue() *node {
|
||||
func (p *jsonParser) parseValue() *Node {
|
||||
p.skipWhitespace()
|
||||
|
||||
var l *node
|
||||
var l *Node
|
||||
switch p.lastChar {
|
||||
case '"':
|
||||
l = p.parseString()
|
||||
@ -84,8 +86,8 @@ func (p *jsonParser) parseValue() *node {
|
||||
return l
|
||||
}
|
||||
|
||||
func (p *jsonParser) parseString() *node {
|
||||
str := &node{depth: p.depth}
|
||||
func (p *jsonParser) parseString() *Node {
|
||||
str := &Node{Depth: p.depth}
|
||||
start := p.end - 1
|
||||
p.next()
|
||||
escaped := false
|
||||
@ -96,7 +98,7 @@ func (p *jsonParser) parseString() *node {
|
||||
var unicode string
|
||||
for i := 0; i < 4; i++ {
|
||||
p.next()
|
||||
if !isHexDigit(p.lastChar) {
|
||||
if !utils.IsHexDigit(p.lastChar) {
|
||||
panic(fmt.Sprintf("Invalid Unicode escape sequence '\\u%s%c'", unicode, p.lastChar))
|
||||
}
|
||||
unicode += string(p.lastChar)
|
||||
@ -122,19 +124,19 @@ func (p *jsonParser) parseString() *node {
|
||||
p.next()
|
||||
}
|
||||
|
||||
str.value = p.data[start:p.end]
|
||||
str.Value = p.data[start:p.end]
|
||||
p.next()
|
||||
return str
|
||||
}
|
||||
|
||||
func (p *jsonParser) parseNumber() *node {
|
||||
num := &node{depth: p.depth}
|
||||
func (p *jsonParser) parseNumber() *Node {
|
||||
num := &Node{Depth: p.depth}
|
||||
start := p.end - 1
|
||||
|
||||
// Handle negative numbers
|
||||
if p.lastChar == '-' {
|
||||
p.next()
|
||||
if !isDigit(p.lastChar) {
|
||||
if !utils.IsDigit(p.lastChar) {
|
||||
panic(fmt.Sprintf("Invalid character %q in number", p.lastChar))
|
||||
}
|
||||
}
|
||||
@ -143,7 +145,7 @@ func (p *jsonParser) parseNumber() *node {
|
||||
if p.lastChar == '0' {
|
||||
p.next()
|
||||
} else {
|
||||
for isDigit(p.lastChar) {
|
||||
for utils.IsDigit(p.lastChar) {
|
||||
p.next()
|
||||
}
|
||||
}
|
||||
@ -151,10 +153,10 @@ func (p *jsonParser) parseNumber() *node {
|
||||
// Decimal portion
|
||||
if p.lastChar == '.' {
|
||||
p.next()
|
||||
if !isDigit(p.lastChar) {
|
||||
if !utils.IsDigit(p.lastChar) {
|
||||
panic(fmt.Sprintf("Invalid character %q in number", p.lastChar))
|
||||
}
|
||||
for isDigit(p.lastChar) {
|
||||
for utils.IsDigit(p.lastChar) {
|
||||
p.next()
|
||||
}
|
||||
}
|
||||
@ -165,28 +167,28 @@ func (p *jsonParser) parseNumber() *node {
|
||||
if p.lastChar == '+' || p.lastChar == '-' {
|
||||
p.next()
|
||||
}
|
||||
if !isDigit(p.lastChar) {
|
||||
if !utils.IsDigit(p.lastChar) {
|
||||
panic(fmt.Sprintf("Invalid character %q in number", p.lastChar))
|
||||
}
|
||||
for isDigit(p.lastChar) {
|
||||
for utils.IsDigit(p.lastChar) {
|
||||
p.next()
|
||||
}
|
||||
}
|
||||
|
||||
num.value = p.data[start : p.end-1]
|
||||
num.Value = p.data[start : p.end-1]
|
||||
return num
|
||||
}
|
||||
|
||||
func (p *jsonParser) parseObject() *node {
|
||||
object := &node{depth: p.depth}
|
||||
object.value = []byte{'{'}
|
||||
func (p *jsonParser) parseObject() *Node {
|
||||
object := &Node{Depth: p.depth}
|
||||
object.Value = []byte{'{'}
|
||||
|
||||
p.next()
|
||||
p.skipWhitespace()
|
||||
|
||||
// Empty object
|
||||
if p.lastChar == '}' {
|
||||
object.value = append(object.value, '}')
|
||||
object.Value = append(object.Value, '}')
|
||||
p.next()
|
||||
return object
|
||||
}
|
||||
@ -199,8 +201,8 @@ func (p *jsonParser) parseObject() *node {
|
||||
|
||||
p.depth++
|
||||
key := p.parseString()
|
||||
key.key, key.value = key.value, nil
|
||||
object.size += 1
|
||||
key.Key, key.Value = key.Value, nil
|
||||
object.Size += 1
|
||||
key.directParent = object
|
||||
|
||||
p.skipWhitespace()
|
||||
@ -216,34 +218,34 @@ func (p *jsonParser) parseObject() *node {
|
||||
value := p.parseValue()
|
||||
p.depth--
|
||||
|
||||
key.value = value.value
|
||||
key.size = value.size
|
||||
key.next = value.next
|
||||
if key.next != nil {
|
||||
key.next.prev = key
|
||||
key.Value = value.Value
|
||||
key.Size = value.Size
|
||||
key.Next = value.Next
|
||||
if key.Next != nil {
|
||||
key.Next.Prev = key
|
||||
}
|
||||
key.end = value.end
|
||||
key.End = value.End
|
||||
value.indirectParent = key
|
||||
object.append(key)
|
||||
|
||||
p.skipWhitespace()
|
||||
|
||||
if p.lastChar == ',' {
|
||||
object.end.comma = true
|
||||
object.End.Comma = true
|
||||
p.next()
|
||||
p.skipWhitespace()
|
||||
if p.lastChar == '}' {
|
||||
object.end.comma = false
|
||||
object.End.Comma = false
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if p.lastChar == '}' {
|
||||
closeBracket := &node{depth: p.depth}
|
||||
closeBracket.value = []byte{'}'}
|
||||
closeBracket := &Node{Depth: p.depth}
|
||||
closeBracket.Value = []byte{'}'}
|
||||
closeBracket.directParent = object
|
||||
closeBracket.index = -1
|
||||
closeBracket.Index = -1
|
||||
object.append(closeBracket)
|
||||
p.next()
|
||||
return object
|
||||
@ -253,15 +255,15 @@ func (p *jsonParser) parseObject() *node {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *jsonParser) parseArray() *node {
|
||||
arr := &node{depth: p.depth}
|
||||
arr.value = []byte{'['}
|
||||
func (p *jsonParser) parseArray() *Node {
|
||||
arr := &Node{Depth: p.depth}
|
||||
arr.Value = []byte{'['}
|
||||
|
||||
p.next()
|
||||
p.skipWhitespace()
|
||||
|
||||
if p.lastChar == ']' {
|
||||
arr.value = append(arr.value, ']')
|
||||
arr.Value = append(arr.Value, ']')
|
||||
p.next()
|
||||
return arr
|
||||
}
|
||||
@ -270,29 +272,29 @@ func (p *jsonParser) parseArray() *node {
|
||||
p.depth++
|
||||
value := p.parseValue()
|
||||
value.directParent = arr
|
||||
arr.size += 1
|
||||
value.index = i
|
||||
arr.Size += 1
|
||||
value.Index = i
|
||||
p.depth--
|
||||
|
||||
arr.append(value)
|
||||
p.skipWhitespace()
|
||||
|
||||
if p.lastChar == ',' {
|
||||
arr.end.comma = true
|
||||
arr.End.Comma = true
|
||||
p.next()
|
||||
p.skipWhitespace()
|
||||
if p.lastChar == ']' {
|
||||
arr.end.comma = false
|
||||
arr.End.Comma = false
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if p.lastChar == ']' {
|
||||
closeBracket := &node{depth: p.depth}
|
||||
closeBracket.value = []byte{']'}
|
||||
closeBracket := &Node{Depth: p.depth}
|
||||
closeBracket.Value = []byte{']'}
|
||||
closeBracket.directParent = arr
|
||||
closeBracket.index = -1
|
||||
closeBracket.Index = -1
|
||||
arr.append(closeBracket)
|
||||
p.next()
|
||||
return arr
|
||||
@ -302,7 +304,7 @@ func (p *jsonParser) parseArray() *node {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *jsonParser) parseKeyword(name string) *node {
|
||||
func (p *jsonParser) parseKeyword(name string) *Node {
|
||||
for i := 1; i < len(name); i++ {
|
||||
p.next()
|
||||
if p.lastChar != name[i] {
|
||||
@ -313,8 +315,8 @@ func (p *jsonParser) parseKeyword(name string) *node {
|
||||
|
||||
nextCharIsSpecial := isWhitespace(p.lastChar) || p.lastChar == ',' || p.lastChar == '}' || p.lastChar == ']' || p.lastChar == 0
|
||||
if nextCharIsSpecial {
|
||||
keyword := &node{depth: p.depth}
|
||||
keyword.value = []byte(name)
|
||||
keyword := &Node{Depth: p.depth}
|
||||
keyword.Value = []byte(name)
|
||||
return keyword
|
||||
}
|
||||
|
259
internal/jsonx/node.go
Normal file
259
internal/jsonx/node.go
Normal file
@ -0,0 +1,259 @@
|
||||
package jsonx
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
jsonpath "github.com/antonmedv/fx/path"
|
||||
)
|
||||
|
||||
type Node struct {
|
||||
Prev, Next, End *Node
|
||||
directParent *Node
|
||||
indirectParent *Node
|
||||
Collapsed *Node
|
||||
Depth uint8
|
||||
Key []byte
|
||||
Value []byte
|
||||
Size int
|
||||
Chunk []byte
|
||||
ChunkEnd *Node
|
||||
Comma bool
|
||||
Index int
|
||||
}
|
||||
|
||||
// append ands a node as a child to the current node (body of {...} or [...]).
|
||||
func (n *Node) append(child *Node) {
|
||||
if n.End == nil {
|
||||
n.End = n
|
||||
}
|
||||
n.End.Next = child
|
||||
child.Prev = n.End
|
||||
if child.End == nil {
|
||||
n.End = child
|
||||
} else {
|
||||
n.End = child.End
|
||||
}
|
||||
}
|
||||
|
||||
// adjacent adds a node as a sibling to the current node ({}{}{} or [][][]).
|
||||
func (n *Node) adjacent(child *Node) {
|
||||
end := n.End
|
||||
if end == nil {
|
||||
end = n
|
||||
}
|
||||
end.Next = child
|
||||
child.Prev = end
|
||||
}
|
||||
|
||||
func (n *Node) insertChunk(chunk *Node) {
|
||||
if n.ChunkEnd == nil {
|
||||
n.insertAfter(chunk)
|
||||
} else {
|
||||
n.ChunkEnd.insertAfter(chunk)
|
||||
}
|
||||
n.ChunkEnd = chunk
|
||||
}
|
||||
|
||||
func (n *Node) insertAfter(child *Node) {
|
||||
if n.Next == nil {
|
||||
n.Next = child
|
||||
child.Prev = n
|
||||
} else {
|
||||
old := n.Next
|
||||
n.Next = child
|
||||
child.Prev = n
|
||||
child.Next = old
|
||||
old.Prev = child
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Node) dropChunks() {
|
||||
if n.ChunkEnd == nil {
|
||||
return
|
||||
}
|
||||
|
||||
n.Chunk = nil
|
||||
|
||||
n.Next = n.ChunkEnd.Next
|
||||
if n.Next != nil {
|
||||
n.Next.Prev = n
|
||||
}
|
||||
|
||||
n.ChunkEnd = nil
|
||||
}
|
||||
|
||||
func (n *Node) HasChildren() bool {
|
||||
return n.End != nil
|
||||
}
|
||||
|
||||
func (n *Node) Parent() *Node {
|
||||
if n.directParent == nil {
|
||||
return nil
|
||||
}
|
||||
parent := n.directParent
|
||||
if parent.indirectParent != nil {
|
||||
parent = parent.indirectParent
|
||||
}
|
||||
return parent
|
||||
}
|
||||
|
||||
func (n *Node) IsCollapsed() bool {
|
||||
return n.Collapsed != nil
|
||||
}
|
||||
|
||||
func (n *Node) Collapse() *Node {
|
||||
if n.End != nil && !n.IsCollapsed() {
|
||||
n.Collapsed = n.Next
|
||||
n.Next = n.End.Next
|
||||
if n.Next != nil {
|
||||
n.Next.Prev = n
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (n *Node) CollapseRecursively() {
|
||||
var at *Node
|
||||
if n.IsCollapsed() {
|
||||
at = n.Collapsed
|
||||
} else {
|
||||
at = n.Next
|
||||
}
|
||||
for at != nil && at != n.End {
|
||||
if at.HasChildren() {
|
||||
at.CollapseRecursively()
|
||||
at.Collapse()
|
||||
}
|
||||
at = at.Next
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Node) Expand() {
|
||||
if n.IsCollapsed() {
|
||||
if n.Next != nil {
|
||||
n.Next.Prev = n.End
|
||||
}
|
||||
n.Next = n.Collapsed
|
||||
n.Collapsed = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Node) ExpandRecursively(level, maxLevel int) {
|
||||
if level >= maxLevel {
|
||||
return
|
||||
}
|
||||
if n.IsCollapsed() {
|
||||
n.Expand()
|
||||
}
|
||||
it := n.Next
|
||||
for it != nil && it != n.End {
|
||||
if it.HasChildren() {
|
||||
it.ExpandRecursively(level+1, maxLevel)
|
||||
it = it.End.Next
|
||||
} else {
|
||||
it = it.Next
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Node) FindChildByKey(key string) *Node {
|
||||
it := n.Next
|
||||
for it != nil && it != n.End {
|
||||
if it.Key != nil {
|
||||
k, err := strconv.Unquote(string(it.Key))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if k == key {
|
||||
return it
|
||||
}
|
||||
}
|
||||
if it.ChunkEnd != nil {
|
||||
it = it.ChunkEnd.Next
|
||||
} else if it.End != nil {
|
||||
it = it.End.Next
|
||||
} else {
|
||||
it = it.Next
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Node) FindChildByIndex(index int) *Node {
|
||||
for at := n.Next; at != nil && at != n.End; {
|
||||
if at.Index == index {
|
||||
return at
|
||||
}
|
||||
if at.End != nil {
|
||||
at = at.End.Next
|
||||
} else {
|
||||
at = at.Next
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Node) paths(prefix string, paths *[]string, nodes *[]*Node) {
|
||||
it := n.Next
|
||||
for it != nil && it != n.End {
|
||||
var path string
|
||||
|
||||
if it.Key != nil {
|
||||
quoted := string(it.Key)
|
||||
unquoted, err := strconv.Unquote(quoted)
|
||||
if err == nil && jsonpath.Identifier.MatchString(unquoted) {
|
||||
path = prefix + "." + unquoted
|
||||
} else {
|
||||
path = prefix + "[" + quoted + "]"
|
||||
}
|
||||
} else if it.Index >= 0 {
|
||||
path = prefix + "[" + strconv.Itoa(it.Index) + "]"
|
||||
}
|
||||
|
||||
*paths = append(*paths, path)
|
||||
*nodes = append(*nodes, it)
|
||||
|
||||
if it.HasChildren() {
|
||||
it.paths(path, paths, nodes)
|
||||
it = it.End.Next
|
||||
} else {
|
||||
it = it.Next
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Node) Children() ([]string, []*Node) {
|
||||
if !n.HasChildren() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var paths []string
|
||||
var nodes []*Node
|
||||
|
||||
var it *Node
|
||||
if n.IsCollapsed() {
|
||||
it = n.Collapsed
|
||||
} else {
|
||||
it = n.Next
|
||||
}
|
||||
|
||||
for it != nil && it != n.End {
|
||||
if it.Key != nil {
|
||||
key := string(it.Key)
|
||||
unquoted, err := strconv.Unquote(key)
|
||||
if err == nil {
|
||||
key = unquoted
|
||||
}
|
||||
paths = append(paths, key)
|
||||
nodes = append(nodes, it)
|
||||
}
|
||||
|
||||
if it.HasChildren() {
|
||||
it = it.End.Next
|
||||
} else {
|
||||
it = it.Next
|
||||
}
|
||||
}
|
||||
|
||||
return paths, nodes
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package jsonx
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@ -8,28 +8,28 @@ import (
|
||||
)
|
||||
|
||||
func TestNode_paths(t *testing.T) {
|
||||
n, err := parse([]byte(`{"a": 1, "b": {"f": 2}, "c": [3, 4]}`))
|
||||
n, err := Parse([]byte(`{"a": 1, "b": {"f": 2}, "c": [3, 4]}`))
|
||||
require.NoError(t, err)
|
||||
|
||||
var paths []string
|
||||
var nodes []*node
|
||||
var nodes []*Node
|
||||
n.paths("", &paths, &nodes)
|
||||
assert.Equal(t, []string{".a", ".b", ".b.f", ".c", ".c[0]", ".c[1]"}, paths)
|
||||
}
|
||||
|
||||
func TestNode_children(t *testing.T) {
|
||||
n, err := parse([]byte(`{"a": 1, "b": {"f": 2}, "c": [3, 4]}`))
|
||||
n, err := Parse([]byte(`{"a": 1, "b": {"f": 2}, "c": [3, 4]}`))
|
||||
require.NoError(t, err)
|
||||
|
||||
paths, _ := n.children()
|
||||
paths, _ := n.Children()
|
||||
assert.Equal(t, []string{"a", "b", "c"}, paths)
|
||||
}
|
||||
|
||||
func TestNode_expandRecursively(t *testing.T) {
|
||||
n, err := parse([]byte(`{"a": {"b": {"c": 1}}}`))
|
||||
n, err := Parse([]byte(`{"a": {"b": {"c": 1}}}`))
|
||||
require.NoError(t, err)
|
||||
|
||||
n.collapseRecursively()
|
||||
n.expandRecursively(0, 3)
|
||||
assert.Equal(t, `"c"`, string(n.next.next.next.key))
|
||||
n.CollapseRecursively()
|
||||
n.ExpandRecursively(0, 3)
|
||||
assert.Equal(t, `"c"`, string(n.Next.Next.Next.Key))
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package jsonx
|
||||
|
||||
import (
|
||||
"strings"
|
61
internal/jsonx/string.go
Normal file
61
internal/jsonx/string.go
Normal file
@ -0,0 +1,61 @@
|
||||
package jsonx
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/antonmedv/fx/internal/theme"
|
||||
)
|
||||
|
||||
func (n *Node) String() string {
|
||||
var out strings.Builder
|
||||
|
||||
it := n
|
||||
for it != nil {
|
||||
if it.Key != nil {
|
||||
out.Write(it.Key)
|
||||
out.WriteByte(':')
|
||||
}
|
||||
if it.Value != nil {
|
||||
out.Write(it.Value)
|
||||
}
|
||||
if it.Comma {
|
||||
out.WriteByte(',')
|
||||
}
|
||||
if it.IsCollapsed() {
|
||||
it = it.Collapsed
|
||||
} else {
|
||||
it = it.Next
|
||||
}
|
||||
}
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
func (n *Node) PrettyPrint() string {
|
||||
var out strings.Builder
|
||||
|
||||
it := n
|
||||
for it != nil {
|
||||
for ident := 0; ident < int(it.Depth); ident++ {
|
||||
out.WriteString(" ")
|
||||
}
|
||||
if it.Key != nil {
|
||||
out.Write(theme.CurrentTheme.Key(it.Key))
|
||||
out.Write(theme.Colon)
|
||||
}
|
||||
if it.Value != nil {
|
||||
out.Write(theme.Value(it.Value, false, false)(it.Value))
|
||||
}
|
||||
if it.Comma {
|
||||
out.Write(theme.Comma)
|
||||
}
|
||||
out.WriteByte('\n')
|
||||
if it.IsCollapsed() {
|
||||
it = it.Collapsed
|
||||
} else {
|
||||
it = it.Next
|
||||
}
|
||||
}
|
||||
|
||||
return out.String()
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package jsonx
|
||||
|
||||
import (
|
||||
"unicode/utf8"
|
||||
@ -6,56 +6,56 @@ import (
|
||||
"github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
func dropWrapAll(n *node) {
|
||||
func DropWrapAll(n *Node) {
|
||||
for n != nil {
|
||||
if n.value != nil && n.value[0] == '"' {
|
||||
if n.Value != nil && n.Value[0] == '"' {
|
||||
n.dropChunks()
|
||||
}
|
||||
if n.isCollapsed() {
|
||||
n = n.collapsed
|
||||
if n.IsCollapsed() {
|
||||
n = n.Collapsed
|
||||
} else {
|
||||
n = n.next
|
||||
n = n.Next
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func wrapAll(n *node, termWidth int) {
|
||||
func WrapAll(n *Node, termWidth int) {
|
||||
if termWidth <= 0 {
|
||||
return
|
||||
}
|
||||
for n != nil {
|
||||
if n.value != nil && n.value[0] == '"' {
|
||||
if n.Value != nil && n.Value[0] == '"' {
|
||||
n.dropChunks()
|
||||
lines, count := doWrap(n, termWidth)
|
||||
if count > 1 {
|
||||
n.chunk = lines[0]
|
||||
n.Chunk = lines[0]
|
||||
for i := 1; i < count; i++ {
|
||||
child := &node{
|
||||
child := &Node{
|
||||
directParent: n,
|
||||
depth: n.depth,
|
||||
chunk: lines[i],
|
||||
Depth: n.Depth,
|
||||
Chunk: lines[i],
|
||||
}
|
||||
if n.comma && i == count-1 {
|
||||
child.comma = true
|
||||
if n.Comma && i == count-1 {
|
||||
child.Comma = true
|
||||
}
|
||||
n.insertChunk(child)
|
||||
}
|
||||
}
|
||||
}
|
||||
if n.isCollapsed() {
|
||||
n = n.collapsed
|
||||
if n.IsCollapsed() {
|
||||
n = n.Collapsed
|
||||
} else {
|
||||
n = n.next
|
||||
n = n.Next
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func doWrap(n *node, termWidth int) ([][]byte, int) {
|
||||
func doWrap(n *Node, termWidth int) ([][]byte, int) {
|
||||
lines := make([][]byte, 0, 1)
|
||||
width := int(n.depth) * 2
|
||||
width := int(n.Depth) * 2
|
||||
|
||||
if n.key != nil {
|
||||
for _, ch := range string(n.key) {
|
||||
if n.Key != nil {
|
||||
for _, ch := range string(n.Key) {
|
||||
width += runewidth.RuneWidth(ch)
|
||||
}
|
||||
width += 2 // for ": "
|
||||
@ -63,15 +63,15 @@ func doWrap(n *node, termWidth int) ([][]byte, int) {
|
||||
|
||||
linesCount := 0
|
||||
start, end := 0, 0
|
||||
b := n.value
|
||||
b := n.Value
|
||||
|
||||
for len(b) > 0 {
|
||||
r, size := utf8.DecodeRune(b)
|
||||
w := runewidth.RuneWidth(r)
|
||||
if width+w > termWidth {
|
||||
lines = append(lines, n.value[start:end])
|
||||
lines = append(lines, n.Value[start:end])
|
||||
start = end
|
||||
width = int(n.depth) * 2
|
||||
width = int(n.Depth) * 2
|
||||
linesCount++
|
||||
}
|
||||
width += w
|
||||
@ -80,7 +80,7 @@ func doWrap(n *node, termWidth int) ([][]byte, int) {
|
||||
}
|
||||
|
||||
if start < end {
|
||||
lines = append(lines, n.value[start:])
|
||||
lines = append(lines, n.Value[start:])
|
||||
linesCount++
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package theme
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@ -10,42 +10,44 @@ import (
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/muesli/termenv"
|
||||
|
||||
"github.com/antonmedv/fx/internal/utils"
|
||||
)
|
||||
|
||||
type theme struct {
|
||||
Cursor color
|
||||
Syntax color
|
||||
Preview color
|
||||
StatusBar color
|
||||
Search color
|
||||
Key color
|
||||
String color
|
||||
Null color
|
||||
Boolean color
|
||||
Number color
|
||||
Size color
|
||||
type Theme struct {
|
||||
Cursor Color
|
||||
Syntax Color
|
||||
Preview Color
|
||||
StatusBar Color
|
||||
Search Color
|
||||
Key Color
|
||||
String Color
|
||||
Null Color
|
||||
Boolean Color
|
||||
Number Color
|
||||
Size Color
|
||||
}
|
||||
|
||||
type color func(s []byte) []byte
|
||||
type Color func(s []byte) []byte
|
||||
|
||||
func valueStyle(b []byte, selected, chunk bool) color {
|
||||
func Value(b []byte, selected, chunk bool) Color {
|
||||
if selected {
|
||||
return currentTheme.Cursor
|
||||
return CurrentTheme.Cursor
|
||||
} else if chunk {
|
||||
return currentTheme.String
|
||||
return CurrentTheme.String
|
||||
} else {
|
||||
switch b[0] {
|
||||
case '"':
|
||||
return currentTheme.String
|
||||
return CurrentTheme.String
|
||||
case 't', 'f':
|
||||
return currentTheme.Boolean
|
||||
return CurrentTheme.Boolean
|
||||
case 'n':
|
||||
return currentTheme.Null
|
||||
return CurrentTheme.Null
|
||||
case '{', '[', '}', ']':
|
||||
return currentTheme.Syntax
|
||||
return CurrentTheme.Syntax
|
||||
default:
|
||||
if isDigit(b[0]) || b[0] == '-' {
|
||||
return currentTheme.Number
|
||||
if utils.IsDigit(b[0]) || b[0] == '-' {
|
||||
return CurrentTheme.Number
|
||||
}
|
||||
return noColor
|
||||
}
|
||||
@ -53,7 +55,7 @@ func valueStyle(b []byte, selected, chunk bool) color {
|
||||
}
|
||||
|
||||
var (
|
||||
termOutput = termenv.NewOutput(os.Stderr)
|
||||
TermOutput = termenv.NewOutput(os.Stderr)
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -71,51 +73,51 @@ func init() {
|
||||
showSizesValue, ok := os.LookupEnv("FX_SHOW_SIZE")
|
||||
if ok {
|
||||
showSizesValue := strings.ToLower(showSizesValue)
|
||||
showSizes = showSizesValue == "true" || showSizesValue == "yes" || showSizesValue == "on" || showSizesValue == "1"
|
||||
ShowSizes = showSizesValue == "true" || showSizesValue == "yes" || showSizesValue == "on" || showSizesValue == "1"
|
||||
}
|
||||
|
||||
currentTheme, ok = themes[themeId]
|
||||
CurrentTheme, ok = themes[themeId]
|
||||
if !ok {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "fx: unknown theme %q, available themes: %v\n", themeId, themeNames)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if termOutput.ColorProfile() == termenv.Ascii {
|
||||
currentTheme = themes["0"]
|
||||
if TermOutput.ColorProfile() == termenv.Ascii {
|
||||
CurrentTheme = themes["0"]
|
||||
}
|
||||
|
||||
colon = currentTheme.Syntax([]byte{':', ' '})
|
||||
colonPreview = currentTheme.Preview([]byte{':'})
|
||||
comma = currentTheme.Syntax([]byte{','})
|
||||
empty = currentTheme.Preview([]byte{'~'})
|
||||
dot3 = currentTheme.Preview([]byte("…"))
|
||||
closeCurlyBracket = currentTheme.Syntax([]byte{'}'})
|
||||
closeSquareBracket = currentTheme.Syntax([]byte{']'})
|
||||
Colon = CurrentTheme.Syntax([]byte{':', ' '})
|
||||
ColonPreview = CurrentTheme.Preview([]byte{':'})
|
||||
Comma = CurrentTheme.Syntax([]byte{','})
|
||||
Empty = CurrentTheme.Preview([]byte{'~'})
|
||||
Dot3 = CurrentTheme.Preview([]byte("…"))
|
||||
CloseCurlyBracket = CurrentTheme.Syntax([]byte{'}'})
|
||||
CloseSquareBracket = CurrentTheme.Syntax([]byte{']'})
|
||||
}
|
||||
|
||||
var (
|
||||
themeNames []string
|
||||
currentTheme theme
|
||||
CurrentTheme Theme
|
||||
defaultCursor = toColor(lipgloss.NewStyle().Reverse(true).Render)
|
||||
defaultPreview = toColor(lipgloss.NewStyle().Foreground(lipgloss.Color("8")).Render)
|
||||
defaultStatusBar = toColor(lipgloss.NewStyle().Background(lipgloss.Color("7")).Foreground(lipgloss.Color("0")).Render)
|
||||
defaultSearch = toColor(lipgloss.NewStyle().Background(lipgloss.Color("11")).Foreground(lipgloss.Color("16")).Render)
|
||||
defaultNull = fg("243")
|
||||
defaultSize = toColor(lipgloss.NewStyle().Foreground(lipgloss.Color("8")).Render)
|
||||
showSizes = false
|
||||
ShowSizes = false
|
||||
)
|
||||
|
||||
var (
|
||||
colon []byte
|
||||
colonPreview []byte
|
||||
comma []byte
|
||||
empty []byte
|
||||
dot3 []byte
|
||||
closeCurlyBracket []byte
|
||||
closeSquareBracket []byte
|
||||
Colon []byte
|
||||
ColonPreview []byte
|
||||
Comma []byte
|
||||
Empty []byte
|
||||
Dot3 []byte
|
||||
CloseCurlyBracket []byte
|
||||
CloseSquareBracket []byte
|
||||
)
|
||||
|
||||
var themes = map[string]theme{
|
||||
var themes = map[string]Theme{
|
||||
"0": {
|
||||
Cursor: defaultCursor,
|
||||
Syntax: noColor,
|
||||
@ -268,21 +270,21 @@ func noColor(s []byte) []byte {
|
||||
return s
|
||||
}
|
||||
|
||||
func toColor(f func(s ...string) string) color {
|
||||
func toColor(f func(s ...string) string) Color {
|
||||
return func(s []byte) []byte {
|
||||
return []byte(f(string(s)))
|
||||
}
|
||||
}
|
||||
|
||||
func fg(color string) color {
|
||||
func fg(color string) Color {
|
||||
return toColor(lipgloss.NewStyle().Foreground(lipgloss.Color(color)).Render)
|
||||
}
|
||||
|
||||
func boldFg(color string) color {
|
||||
func boldFg(color string) Color {
|
||||
return toColor(lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color(color)).Render)
|
||||
}
|
||||
|
||||
func themeTester() {
|
||||
func ThemeTester() {
|
||||
title := lipgloss.NewStyle().Bold(true)
|
||||
for _, name := range themeNames {
|
||||
t := themes[name]
|
||||
@ -319,7 +321,7 @@ func themeTester() {
|
||||
}
|
||||
}
|
||||
|
||||
func exportThemes() {
|
||||
func ExportThemes() {
|
||||
lipgloss.SetColorProfile(termenv.ANSI256) // Export in Terminal.app compatible colors
|
||||
placeholder := []byte{'_'}
|
||||
extract := func(b []byte) string {
|
9
internal/utils/utils.go
Normal file
9
internal/utils/utils.go
Normal file
@ -0,0 +1,9 @@
|
||||
package utils
|
||||
|
||||
func IsHexDigit(ch byte) bool {
|
||||
return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')
|
||||
}
|
||||
|
||||
func IsDigit(ch byte) bool {
|
||||
return ch >= '0' && ch <= '9'
|
||||
}
|
322
main.go
322
main.go
@ -26,6 +26,8 @@ import (
|
||||
"github.com/sahilm/fuzzy"
|
||||
|
||||
"github.com/antonmedv/fx/internal/complete"
|
||||
. "github.com/antonmedv/fx/internal/jsonx"
|
||||
"github.com/antonmedv/fx/internal/theme"
|
||||
jsonpath "github.com/antonmedv/fx/path"
|
||||
)
|
||||
|
||||
@ -71,10 +73,10 @@ func main() {
|
||||
fmt.Println(version)
|
||||
return
|
||||
case "--themes":
|
||||
themeTester()
|
||||
theme.ThemeTester()
|
||||
return
|
||||
case "--export-themes":
|
||||
exportThemes()
|
||||
theme.ExportThemes()
|
||||
return
|
||||
default:
|
||||
args = append(args, arg)
|
||||
@ -143,7 +145,7 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
head, err := parse(data)
|
||||
head, err := Parse(data)
|
||||
if err != nil {
|
||||
fmt.Print(err.Error())
|
||||
os.Exit(1)
|
||||
@ -176,7 +178,7 @@ func main() {
|
||||
search: newSearch(),
|
||||
}
|
||||
|
||||
lipgloss.SetColorProfile(termOutput.ColorProfile())
|
||||
lipgloss.SetColorProfile(theme.TermOutput.ColorProfile())
|
||||
|
||||
withMouse := tea.WithMouseCellMotion()
|
||||
if _, ok := os.LookupEnv("FX_NO_MOUSE"); ok {
|
||||
@ -200,7 +202,7 @@ func main() {
|
||||
|
||||
type model struct {
|
||||
termWidth, termHeight int
|
||||
head, top *node
|
||||
head, top *Node
|
||||
cursor int // cursor position [0, termHeight)
|
||||
showCursor bool
|
||||
wrap bool
|
||||
@ -229,7 +231,7 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.help.Height = m.termHeight - 1
|
||||
m.preview.Width = m.termWidth
|
||||
m.preview.Height = m.termHeight - 1
|
||||
wrapAll(m.top, m.termWidth)
|
||||
WrapAll(m.top, m.termWidth)
|
||||
m.redoSearch()
|
||||
}
|
||||
|
||||
@ -257,18 +259,18 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if m.cursor == msg.Y {
|
||||
to := m.cursorPointsTo()
|
||||
if to != nil {
|
||||
if to.isCollapsed() {
|
||||
to.expand()
|
||||
if to.IsCollapsed() {
|
||||
to.Expand()
|
||||
} else {
|
||||
to.collapse()
|
||||
to.Collapse()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
to := m.at(msg.Y)
|
||||
if to != nil {
|
||||
m.cursor = msg.Y
|
||||
if to.isCollapsed() {
|
||||
to.expand()
|
||||
if to.IsCollapsed() {
|
||||
to.Expand()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -490,11 +492,11 @@ func (m *model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
|
||||
case key.Matches(msg, keyMap.NextSibling):
|
||||
pointsTo := m.cursorPointsTo()
|
||||
var nextSibling *node
|
||||
if pointsTo.end != nil && pointsTo.end.next != nil {
|
||||
nextSibling = pointsTo.end.next
|
||||
var nextSibling *Node
|
||||
if pointsTo.End != nil && pointsTo.End.Next != nil {
|
||||
nextSibling = pointsTo.End.Next
|
||||
} else {
|
||||
nextSibling = pointsTo.next
|
||||
nextSibling = pointsTo.Next
|
||||
}
|
||||
if nextSibling != nil {
|
||||
m.selectNode(nextSibling)
|
||||
@ -502,13 +504,13 @@ func (m *model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
|
||||
case key.Matches(msg, keyMap.PrevSibling):
|
||||
pointsTo := m.cursorPointsTo()
|
||||
var prevSibling *node
|
||||
if pointsTo.parent() != nil && pointsTo.parent().end == pointsTo {
|
||||
prevSibling = pointsTo.parent()
|
||||
} else if pointsTo.prev != nil {
|
||||
prevSibling = pointsTo.prev
|
||||
parent := prevSibling.parent()
|
||||
if parent != nil && parent.end == prevSibling {
|
||||
var prevSibling *Node
|
||||
if pointsTo.Parent() != nil && pointsTo.Parent().End == pointsTo {
|
||||
prevSibling = pointsTo.Parent()
|
||||
} else if pointsTo.Prev != nil {
|
||||
prevSibling = pointsTo.Prev
|
||||
parent := prevSibling.Parent()
|
||||
if parent != nil && parent.End == prevSibling {
|
||||
prevSibling = parent
|
||||
}
|
||||
}
|
||||
@ -518,41 +520,41 @@ func (m *model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
|
||||
case key.Matches(msg, keyMap.Collapse):
|
||||
n := m.cursorPointsTo()
|
||||
if n.hasChildren() && !n.isCollapsed() {
|
||||
n.collapse()
|
||||
if n.HasChildren() && !n.IsCollapsed() {
|
||||
n.Collapse()
|
||||
} else {
|
||||
if n.parent() != nil {
|
||||
n = n.parent()
|
||||
if n.Parent() != nil {
|
||||
n = n.Parent()
|
||||
}
|
||||
}
|
||||
m.selectNode(n)
|
||||
|
||||
case key.Matches(msg, keyMap.Expand):
|
||||
m.cursorPointsTo().expand()
|
||||
m.cursorPointsTo().Expand()
|
||||
m.showCursor = true
|
||||
|
||||
case key.Matches(msg, keyMap.CollapseRecursively):
|
||||
n := m.cursorPointsTo()
|
||||
if n.hasChildren() {
|
||||
n.collapseRecursively()
|
||||
if n.HasChildren() {
|
||||
n.CollapseRecursively()
|
||||
}
|
||||
m.showCursor = true
|
||||
|
||||
case key.Matches(msg, keyMap.ExpandRecursively):
|
||||
n := m.cursorPointsTo()
|
||||
if n.hasChildren() {
|
||||
n.expandRecursively(0, math.MaxInt)
|
||||
if n.HasChildren() {
|
||||
n.ExpandRecursively(0, math.MaxInt)
|
||||
}
|
||||
m.showCursor = true
|
||||
|
||||
case key.Matches(msg, keyMap.CollapseAll):
|
||||
n := m.top
|
||||
for n != nil {
|
||||
n.collapseRecursively()
|
||||
if n.end == nil {
|
||||
n.CollapseRecursively()
|
||||
if n.End == nil {
|
||||
n = nil
|
||||
} else {
|
||||
n = n.end.next
|
||||
n = n.End.Next
|
||||
}
|
||||
}
|
||||
m.cursor = 0
|
||||
@ -563,21 +565,21 @@ func (m *model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
at := m.cursorPointsTo()
|
||||
n := m.top
|
||||
for n != nil {
|
||||
n.expandRecursively(0, math.MaxInt)
|
||||
if n.end == nil {
|
||||
n.ExpandRecursively(0, math.MaxInt)
|
||||
if n.End == nil {
|
||||
n = nil
|
||||
} else {
|
||||
n = n.end.next
|
||||
n = n.End.Next
|
||||
}
|
||||
}
|
||||
m.selectNode(at)
|
||||
|
||||
case key.Matches(msg, keyMap.CollapseLevel):
|
||||
at := m.cursorPointsTo()
|
||||
if at != nil && at.hasChildren() {
|
||||
if at != nil && at.HasChildren() {
|
||||
toLevel, _ := strconv.Atoi(msg.String())
|
||||
at.collapseRecursively()
|
||||
at.expandRecursively(0, toLevel)
|
||||
at.CollapseRecursively()
|
||||
at.ExpandRecursively(0, toLevel)
|
||||
m.showCursor = true
|
||||
}
|
||||
|
||||
@ -585,12 +587,12 @@ func (m *model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
at := m.cursorPointsTo()
|
||||
m.wrap = !m.wrap
|
||||
if m.wrap {
|
||||
wrapAll(m.top, m.termWidth)
|
||||
WrapAll(m.top, m.termWidth)
|
||||
} else {
|
||||
dropWrapAll(m.top)
|
||||
DropWrapAll(m.top)
|
||||
}
|
||||
if at.chunk != nil && at.value == nil {
|
||||
at = at.parent()
|
||||
if at.Chunk != nil && at.Value == nil {
|
||||
at = at.Parent()
|
||||
}
|
||||
m.redoSearch()
|
||||
m.selectNode(at)
|
||||
@ -632,8 +634,8 @@ func (m *model) up() {
|
||||
m.cursor--
|
||||
if m.cursor < 0 {
|
||||
m.cursor = 0
|
||||
if m.head.prev != nil {
|
||||
m.head = m.head.prev
|
||||
if m.head.Prev != nil {
|
||||
m.head = m.head.Prev
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -648,8 +650,8 @@ func (m *model) down() {
|
||||
}
|
||||
if m.cursor >= m.viewHeight() {
|
||||
m.cursor = m.viewHeight() - 1
|
||||
if m.head.next != nil {
|
||||
m.head = m.head.next
|
||||
if m.head.Next != nil {
|
||||
m.head = m.head.Next
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -659,7 +661,7 @@ func (m *model) visibleLines() int {
|
||||
n := m.head
|
||||
for n != nil && visibleLines < m.viewHeight() {
|
||||
visibleLines++
|
||||
n = n.next
|
||||
n = n.Next
|
||||
}
|
||||
return visibleLines
|
||||
}
|
||||
@ -669,22 +671,22 @@ func (m *model) scrollIntoView() {
|
||||
if m.cursor >= visibleLines {
|
||||
m.cursor = visibleLines - 1
|
||||
}
|
||||
for visibleLines < m.viewHeight() && m.head.prev != nil {
|
||||
for visibleLines < m.viewHeight() && m.head.Prev != nil {
|
||||
visibleLines++
|
||||
m.cursor++
|
||||
m.head = m.head.prev
|
||||
m.head = m.head.Prev
|
||||
}
|
||||
}
|
||||
|
||||
func (m *model) View() string {
|
||||
if m.showHelp {
|
||||
statusBar := flex(m.termWidth, ": press q or ? to close help", "")
|
||||
return m.help.View() + "\n" + string(currentTheme.StatusBar([]byte(statusBar)))
|
||||
return m.help.View() + "\n" + string(theme.CurrentTheme.StatusBar([]byte(statusBar)))
|
||||
}
|
||||
|
||||
if m.showPreview {
|
||||
statusBar := flex(m.termWidth, m.cursorPath(), m.fileName)
|
||||
return m.preview.View() + "\n" + string(currentTheme.StatusBar([]byte(statusBar)))
|
||||
return m.preview.View() + "\n" + string(theme.CurrentTheme.StatusBar([]byte(statusBar)))
|
||||
}
|
||||
|
||||
var screen []byte
|
||||
@ -695,7 +697,7 @@ func (m *model) View() string {
|
||||
if n == nil {
|
||||
break
|
||||
}
|
||||
for ident := 0; ident < int(n.depth); ident++ {
|
||||
for ident := 0; ident < int(n.Depth); ident++ {
|
||||
screen = append(screen, ' ', ' ')
|
||||
}
|
||||
|
||||
@ -704,47 +706,47 @@ func (m *model) View() string {
|
||||
isSelected = false // don't highlight the cursor while iterating search results
|
||||
}
|
||||
|
||||
if n.key != nil {
|
||||
if n.Key != nil {
|
||||
screen = append(screen, m.prettyKey(n, isSelected)...)
|
||||
screen = append(screen, colon...)
|
||||
screen = append(screen, theme.Colon...)
|
||||
isSelected = false // don't highlight the key's value
|
||||
}
|
||||
|
||||
screen = append(screen, m.prettyPrint(n, isSelected)...)
|
||||
|
||||
if n.isCollapsed() {
|
||||
if n.value[0] == '{' {
|
||||
if n.collapsed.key != nil {
|
||||
screen = append(screen, currentTheme.Preview(n.collapsed.key)...)
|
||||
screen = append(screen, colonPreview...)
|
||||
if n.IsCollapsed() {
|
||||
if n.Value[0] == '{' {
|
||||
if n.Collapsed.Key != nil {
|
||||
screen = append(screen, theme.CurrentTheme.Preview(n.Collapsed.Key)...)
|
||||
screen = append(screen, theme.ColonPreview...)
|
||||
}
|
||||
screen = append(screen, dot3...)
|
||||
screen = append(screen, closeCurlyBracket...)
|
||||
} else if n.value[0] == '[' {
|
||||
screen = append(screen, dot3...)
|
||||
screen = append(screen, closeSquareBracket...)
|
||||
screen = append(screen, theme.Dot3...)
|
||||
screen = append(screen, theme.CloseCurlyBracket...)
|
||||
} else if n.Value[0] == '[' {
|
||||
screen = append(screen, theme.Dot3...)
|
||||
screen = append(screen, theme.CloseSquareBracket...)
|
||||
}
|
||||
if n.end != nil && n.end.comma {
|
||||
screen = append(screen, comma...)
|
||||
if n.End != nil && n.End.Comma {
|
||||
screen = append(screen, theme.Comma...)
|
||||
}
|
||||
}
|
||||
if n.comma {
|
||||
screen = append(screen, comma...)
|
||||
if n.Comma {
|
||||
screen = append(screen, theme.Comma...)
|
||||
}
|
||||
|
||||
if showSizes && len(n.value) > 0 && (n.value[0] == '{' || n.value[0] == '[') {
|
||||
if n.isCollapsed() || n.size > 1 {
|
||||
screen = append(screen, currentTheme.Size([]byte(fmt.Sprintf(" // %d", n.size)))...)
|
||||
if theme.ShowSizes && len(n.Value) > 0 && (n.Value[0] == '{' || n.Value[0] == '[') {
|
||||
if n.IsCollapsed() || n.Size > 1 {
|
||||
screen = append(screen, theme.CurrentTheme.Size([]byte(fmt.Sprintf(" // %d", n.Size)))...)
|
||||
}
|
||||
}
|
||||
|
||||
screen = append(screen, '\n')
|
||||
printedLines++
|
||||
n = n.next
|
||||
n = n.Next
|
||||
}
|
||||
|
||||
for i := printedLines; i < m.viewHeight(); i++ {
|
||||
screen = append(screen, empty...)
|
||||
screen = append(screen, theme.Empty...)
|
||||
screen = append(screen, '\n')
|
||||
}
|
||||
|
||||
@ -752,7 +754,7 @@ func (m *model) View() string {
|
||||
screen = append(screen, m.digInput.View()...)
|
||||
} else {
|
||||
statusBar := flex(m.termWidth, m.cursorPath(), m.fileName)
|
||||
screen = append(screen, currentTheme.StatusBar([]byte(statusBar))...)
|
||||
screen = append(screen, theme.CurrentTheme.StatusBar([]byte(statusBar))...)
|
||||
}
|
||||
|
||||
if m.yank {
|
||||
@ -781,12 +783,12 @@ func (m *model) View() string {
|
||||
return string(screen)
|
||||
}
|
||||
|
||||
func (m *model) prettyKey(node *node, selected bool) []byte {
|
||||
b := node.key
|
||||
func (m *model) prettyKey(node *Node, selected bool) []byte {
|
||||
b := node.Key
|
||||
|
||||
style := currentTheme.Key
|
||||
style := theme.CurrentTheme.Key
|
||||
if selected {
|
||||
style = currentTheme.Cursor
|
||||
style = theme.CurrentTheme.Cursor
|
||||
}
|
||||
|
||||
if indexes, ok := m.search.keys[node]; ok {
|
||||
@ -795,9 +797,9 @@ func (m *model) prettyKey(node *node, selected bool) []byte {
|
||||
if i%2 == 0 {
|
||||
out = append(out, style(p.b)...)
|
||||
} else if p.index == m.search.cursor {
|
||||
out = append(out, currentTheme.Cursor(p.b)...)
|
||||
out = append(out, theme.CurrentTheme.Cursor(p.b)...)
|
||||
} else {
|
||||
out = append(out, currentTheme.Search(p.b)...)
|
||||
out = append(out, theme.CurrentTheme.Search(p.b)...)
|
||||
}
|
||||
}
|
||||
return out
|
||||
@ -806,19 +808,19 @@ func (m *model) prettyKey(node *node, selected bool) []byte {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *model) prettyPrint(node *node, selected bool) []byte {
|
||||
func (m *model) prettyPrint(node *Node, selected bool) []byte {
|
||||
var b []byte
|
||||
if node.chunk != nil {
|
||||
b = node.chunk
|
||||
if node.Chunk != nil {
|
||||
b = node.Chunk
|
||||
} else {
|
||||
b = node.value
|
||||
b = node.Value
|
||||
}
|
||||
|
||||
if len(b) == 0 {
|
||||
return b
|
||||
}
|
||||
|
||||
style := valueStyle(b, selected, node.chunk != nil)
|
||||
style := theme.Value(b, selected, node.Chunk != nil)
|
||||
|
||||
if indexes, ok := m.search.values[node]; ok {
|
||||
var out []byte
|
||||
@ -826,9 +828,9 @@ func (m *model) prettyPrint(node *node, selected bool) []byte {
|
||||
if i%2 == 0 {
|
||||
out = append(out, style(p.b)...)
|
||||
} else if p.index == m.search.cursor {
|
||||
out = append(out, currentTheme.Cursor(p.b)...)
|
||||
out = append(out, theme.CurrentTheme.Cursor(p.b)...)
|
||||
} else {
|
||||
out = append(out, currentTheme.Search(p.b)...)
|
||||
out = append(out, theme.CurrentTheme.Search(p.b)...)
|
||||
}
|
||||
}
|
||||
return out
|
||||
@ -847,34 +849,34 @@ func (m *model) viewHeight() int {
|
||||
return m.termHeight - 1
|
||||
}
|
||||
|
||||
func (m *model) cursorPointsTo() *node {
|
||||
func (m *model) cursorPointsTo() *Node {
|
||||
return m.at(m.cursor)
|
||||
}
|
||||
|
||||
func (m *model) at(pos int) *node {
|
||||
func (m *model) at(pos int) *Node {
|
||||
head := m.head
|
||||
for i := 0; i < pos; i++ {
|
||||
if head == nil {
|
||||
break
|
||||
}
|
||||
head = head.next
|
||||
head = head.Next
|
||||
}
|
||||
return head
|
||||
}
|
||||
|
||||
func (m *model) findBottom() *node {
|
||||
func (m *model) findBottom() *Node {
|
||||
n := m.head
|
||||
for n.next != nil {
|
||||
if n.end != nil {
|
||||
n = n.end
|
||||
for n.Next != nil {
|
||||
if n.End != nil {
|
||||
n = n.End
|
||||
} else {
|
||||
n = n.next
|
||||
n = n.Next
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *model) nodeInsideView(n *node) bool {
|
||||
func (m *model) nodeInsideView(n *Node) bool {
|
||||
if n == nil {
|
||||
return false
|
||||
}
|
||||
@ -886,12 +888,12 @@ func (m *model) nodeInsideView(n *node) bool {
|
||||
if head == n {
|
||||
return true
|
||||
}
|
||||
head = head.next
|
||||
head = head.Next
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *model) selectNodeInView(n *node) {
|
||||
func (m *model) selectNodeInView(n *Node) {
|
||||
head := m.head
|
||||
for i := 0; i < m.viewHeight(); i++ {
|
||||
if head == nil {
|
||||
@ -901,11 +903,11 @@ func (m *model) selectNodeInView(n *node) {
|
||||
m.cursor = i
|
||||
return
|
||||
}
|
||||
head = head.next
|
||||
head = head.Next
|
||||
}
|
||||
}
|
||||
|
||||
func (m *model) selectNode(n *node) {
|
||||
func (m *model) selectNode(n *Node) {
|
||||
m.showCursor = true
|
||||
if m.nodeInsideView(n) {
|
||||
m.selectNodeInView(n)
|
||||
@ -915,10 +917,10 @@ func (m *model) selectNode(n *node) {
|
||||
m.head = n
|
||||
m.scrollIntoView()
|
||||
}
|
||||
parent := n.parent()
|
||||
parent := n.Parent()
|
||||
for parent != nil {
|
||||
parent.expand()
|
||||
parent = parent.parent()
|
||||
parent.Expand()
|
||||
parent = parent.Parent()
|
||||
}
|
||||
}
|
||||
|
||||
@ -926,23 +928,23 @@ func (m *model) cursorPath() string {
|
||||
path := ""
|
||||
at := m.cursorPointsTo()
|
||||
for at != nil {
|
||||
if at.prev != nil {
|
||||
if at.chunk != nil && at.value == nil {
|
||||
at = at.parent()
|
||||
if at.Prev != nil {
|
||||
if at.Chunk != nil && at.Value == nil {
|
||||
at = at.Parent()
|
||||
}
|
||||
if at.key != nil {
|
||||
quoted := string(at.key)
|
||||
if at.Key != nil {
|
||||
quoted := string(at.Key)
|
||||
unquoted, err := strconv.Unquote(quoted)
|
||||
if err == nil && jsonpath.Identifier.MatchString(unquoted) {
|
||||
path = "." + unquoted + path
|
||||
} else {
|
||||
path = "[" + quoted + "]" + path
|
||||
}
|
||||
} else if at.index >= 0 {
|
||||
path = "[" + strconv.Itoa(at.index) + "]" + path
|
||||
} else if at.Index >= 0 {
|
||||
path = "[" + strconv.Itoa(at.Index) + "]" + path
|
||||
}
|
||||
}
|
||||
at = at.parent()
|
||||
at = at.Parent()
|
||||
}
|
||||
return path
|
||||
}
|
||||
@ -952,55 +954,55 @@ func (m *model) cursorValue() string {
|
||||
if at == nil {
|
||||
return ""
|
||||
}
|
||||
parent := at.parent()
|
||||
parent := at.Parent()
|
||||
if parent != nil {
|
||||
// wrapped string part
|
||||
if at.chunk != nil && at.value == nil {
|
||||
if at.Chunk != nil && at.Value == nil {
|
||||
at = parent
|
||||
}
|
||||
if len(at.value) == 1 && at.value[0] == '}' || at.value[0] == ']' {
|
||||
if len(at.Value) == 1 && at.Value[0] == '}' || at.Value[0] == ']' {
|
||||
at = parent
|
||||
}
|
||||
}
|
||||
|
||||
if len(at.value) > 0 && at.value[0] == '"' {
|
||||
str, err := strconv.Unquote(string(at.value))
|
||||
if len(at.Value) > 0 && at.Value[0] == '"' {
|
||||
str, err := strconv.Unquote(string(at.Value))
|
||||
if err == nil {
|
||||
return str
|
||||
}
|
||||
return string(at.value)
|
||||
return string(at.Value)
|
||||
}
|
||||
|
||||
var out strings.Builder
|
||||
out.Write(at.value)
|
||||
out.Write(at.Value)
|
||||
out.WriteString("\n")
|
||||
if at.hasChildren() {
|
||||
it := at.next
|
||||
if at.isCollapsed() {
|
||||
it = at.collapsed
|
||||
if at.HasChildren() {
|
||||
it := at.Next
|
||||
if at.IsCollapsed() {
|
||||
it = at.Collapsed
|
||||
}
|
||||
for it != nil {
|
||||
out.WriteString(strings.Repeat(" ", int(it.depth-at.depth)))
|
||||
if it.key != nil {
|
||||
out.Write(it.key)
|
||||
out.WriteString(strings.Repeat(" ", int(it.Depth-at.Depth)))
|
||||
if it.Key != nil {
|
||||
out.Write(it.Key)
|
||||
out.WriteString(": ")
|
||||
}
|
||||
if it.value != nil {
|
||||
out.Write(it.value)
|
||||
if it.Value != nil {
|
||||
out.Write(it.Value)
|
||||
}
|
||||
if it == at.end {
|
||||
if it == at.End {
|
||||
break
|
||||
}
|
||||
if it.comma {
|
||||
if it.Comma {
|
||||
out.WriteString(",")
|
||||
}
|
||||
out.WriteString("\n")
|
||||
if it.chunkEnd != nil {
|
||||
it = it.chunkEnd.next
|
||||
} else if it.isCollapsed() {
|
||||
it = it.collapsed
|
||||
if it.ChunkEnd != nil {
|
||||
it = it.ChunkEnd.Next
|
||||
} else if it.IsCollapsed() {
|
||||
it = it.Collapsed
|
||||
} else {
|
||||
it = it.next
|
||||
it = it.Next
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1012,16 +1014,16 @@ func (m *model) cursorKey() string {
|
||||
if at == nil {
|
||||
return ""
|
||||
}
|
||||
if at.key != nil {
|
||||
if at.Key != nil {
|
||||
var v string
|
||||
_ = json.Unmarshal(at.key, &v)
|
||||
_ = json.Unmarshal(at.Key, &v)
|
||||
return v
|
||||
}
|
||||
return strconv.Itoa(at.index)
|
||||
return strconv.Itoa(at.Index)
|
||||
|
||||
}
|
||||
|
||||
func (m *model) selectByPath(path []any) *node {
|
||||
func (m *model) selectByPath(path []any) *Node {
|
||||
n := m.currentTopNode()
|
||||
for _, part := range path {
|
||||
if n == nil {
|
||||
@ -1029,21 +1031,21 @@ func (m *model) selectByPath(path []any) *node {
|
||||
}
|
||||
switch part := part.(type) {
|
||||
case string:
|
||||
n = n.findChildByKey(part)
|
||||
n = n.FindChildByKey(part)
|
||||
case int:
|
||||
n = n.findChildByIndex(part)
|
||||
n = n.FindChildByIndex(part)
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *model) currentTopNode() *node {
|
||||
func (m *model) currentTopNode() *Node {
|
||||
at := m.cursorPointsTo()
|
||||
if at == nil {
|
||||
return nil
|
||||
}
|
||||
for at.parent() != nil {
|
||||
at = at.parent()
|
||||
for at.Parent() != nil {
|
||||
at = at.Parent()
|
||||
}
|
||||
return at
|
||||
}
|
||||
@ -1069,8 +1071,8 @@ func (m *model) doSearch(s string) {
|
||||
n := m.top
|
||||
searchIndex := 0
|
||||
for n != nil {
|
||||
if n.key != nil {
|
||||
indexes := re.FindAllIndex(n.key, -1)
|
||||
if n.Key != nil {
|
||||
indexes := re.FindAllIndex(n.Key, -1)
|
||||
if len(indexes) > 0 {
|
||||
for i, pair := range indexes {
|
||||
m.search.results = append(m.search.results, n)
|
||||
@ -1079,24 +1081,24 @@ func (m *model) doSearch(s string) {
|
||||
searchIndex += len(indexes)
|
||||
}
|
||||
}
|
||||
indexes := re.FindAllIndex(n.value, -1)
|
||||
indexes := re.FindAllIndex(n.Value, -1)
|
||||
if len(indexes) > 0 {
|
||||
for range indexes {
|
||||
m.search.results = append(m.search.results, n)
|
||||
}
|
||||
if n.chunk != nil {
|
||||
if n.Chunk != nil {
|
||||
// String can be split into chunks, so we need to map the indexes to the chunks.
|
||||
chunks := [][]byte{n.chunk}
|
||||
chunkNodes := []*node{n}
|
||||
chunks := [][]byte{n.Chunk}
|
||||
chunkNodes := []*Node{n}
|
||||
|
||||
it := n.next
|
||||
it := n.Next
|
||||
for it != nil {
|
||||
chunkNodes = append(chunkNodes, it)
|
||||
chunks = append(chunks, it.chunk)
|
||||
if it == n.chunkEnd {
|
||||
chunks = append(chunks, it.Chunk)
|
||||
if it == n.ChunkEnd {
|
||||
break
|
||||
}
|
||||
it = it.next
|
||||
it = it.Next
|
||||
}
|
||||
|
||||
chunkMatches := splitIndexesToChunks(chunks, indexes, searchIndex)
|
||||
@ -1111,10 +1113,10 @@ func (m *model) doSearch(s string) {
|
||||
searchIndex += len(indexes)
|
||||
}
|
||||
|
||||
if n.isCollapsed() {
|
||||
n = n.collapsed
|
||||
if n.IsCollapsed() {
|
||||
n = n.Collapsed
|
||||
} else {
|
||||
n = n.next
|
||||
n = n.Next
|
||||
}
|
||||
}
|
||||
|
||||
@ -1145,7 +1147,7 @@ func (m *model) redoSearch() {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *model) dig(v string) *node {
|
||||
func (m *model) dig(v string) *Node {
|
||||
p, ok := jsonpath.Split(v)
|
||||
if !ok {
|
||||
return nil
|
||||
@ -1167,7 +1169,7 @@ func (m *model) dig(v string) *node {
|
||||
return nil
|
||||
}
|
||||
|
||||
keys, nodes := at.children()
|
||||
keys, nodes := at.Children()
|
||||
|
||||
matches := fuzzy.Find(searchTerm, keys)
|
||||
if len(matches) == 0 {
|
||||
|
@ -13,6 +13,9 @@ import (
|
||||
"github.com/charmbracelet/x/exp/teatest"
|
||||
"github.com/muesli/termenv"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/antonmedv/fx/internal/jsonx"
|
||||
"github.com/antonmedv/fx/internal/theme"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -26,7 +29,7 @@ func prepare(t *testing.T) *teatest.TestModel {
|
||||
json, err := io.ReadAll(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
head, err := parse(json)
|
||||
head, err := jsonx.Parse(json)
|
||||
require.NoError(t, err)
|
||||
|
||||
m := &model{
|
||||
@ -103,8 +106,8 @@ func TestCollapseRecursive(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCollapseRecursiveWithSizes(t *testing.T) {
|
||||
showSizes = true
|
||||
defer func() { showSizes = true }()
|
||||
theme.ShowSizes = true
|
||||
defer func() { theme.ShowSizes = true }()
|
||||
|
||||
tm := prepare(t)
|
||||
|
||||
|
259
node.go
259
node.go
@ -1,259 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
jsonpath "github.com/antonmedv/fx/path"
|
||||
)
|
||||
|
||||
type node struct {
|
||||
prev, next, end *node
|
||||
directParent *node
|
||||
indirectParent *node
|
||||
collapsed *node
|
||||
depth uint8
|
||||
key []byte
|
||||
value []byte
|
||||
size int
|
||||
chunk []byte
|
||||
chunkEnd *node
|
||||
comma bool
|
||||
index int
|
||||
}
|
||||
|
||||
// append ands a node as a child to the current node (body of {...} or [...]).
|
||||
func (n *node) append(child *node) {
|
||||
if n.end == nil {
|
||||
n.end = n
|
||||
}
|
||||
n.end.next = child
|
||||
child.prev = n.end
|
||||
if child.end == nil {
|
||||
n.end = child
|
||||
} else {
|
||||
n.end = child.end
|
||||
}
|
||||
}
|
||||
|
||||
// adjacent adds a node as a sibling to the current node ({}{}{} or [][][]).
|
||||
func (n *node) adjacent(child *node) {
|
||||
end := n.end
|
||||
if end == nil {
|
||||
end = n
|
||||
}
|
||||
end.next = child
|
||||
child.prev = end
|
||||
}
|
||||
|
||||
func (n *node) insertChunk(chunk *node) {
|
||||
if n.chunkEnd == nil {
|
||||
n.insertAfter(chunk)
|
||||
} else {
|
||||
n.chunkEnd.insertAfter(chunk)
|
||||
}
|
||||
n.chunkEnd = chunk
|
||||
}
|
||||
|
||||
func (n *node) insertAfter(child *node) {
|
||||
if n.next == nil {
|
||||
n.next = child
|
||||
child.prev = n
|
||||
} else {
|
||||
old := n.next
|
||||
n.next = child
|
||||
child.prev = n
|
||||
child.next = old
|
||||
old.prev = child
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) dropChunks() {
|
||||
if n.chunkEnd == nil {
|
||||
return
|
||||
}
|
||||
|
||||
n.chunk = nil
|
||||
|
||||
n.next = n.chunkEnd.next
|
||||
if n.next != nil {
|
||||
n.next.prev = n
|
||||
}
|
||||
|
||||
n.chunkEnd = nil
|
||||
}
|
||||
|
||||
func (n *node) hasChildren() bool {
|
||||
return n.end != nil
|
||||
}
|
||||
|
||||
func (n *node) parent() *node {
|
||||
if n.directParent == nil {
|
||||
return nil
|
||||
}
|
||||
parent := n.directParent
|
||||
if parent.indirectParent != nil {
|
||||
parent = parent.indirectParent
|
||||
}
|
||||
return parent
|
||||
}
|
||||
|
||||
func (n *node) isCollapsed() bool {
|
||||
return n.collapsed != nil
|
||||
}
|
||||
|
||||
func (n *node) collapse() *node {
|
||||
if n.end != nil && !n.isCollapsed() {
|
||||
n.collapsed = n.next
|
||||
n.next = n.end.next
|
||||
if n.next != nil {
|
||||
n.next.prev = n
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (n *node) collapseRecursively() {
|
||||
var at *node
|
||||
if n.isCollapsed() {
|
||||
at = n.collapsed
|
||||
} else {
|
||||
at = n.next
|
||||
}
|
||||
for at != nil && at != n.end {
|
||||
if at.hasChildren() {
|
||||
at.collapseRecursively()
|
||||
at.collapse()
|
||||
}
|
||||
at = at.next
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) expand() {
|
||||
if n.isCollapsed() {
|
||||
if n.next != nil {
|
||||
n.next.prev = n.end
|
||||
}
|
||||
n.next = n.collapsed
|
||||
n.collapsed = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) expandRecursively(level, maxLevel int) {
|
||||
if level >= maxLevel {
|
||||
return
|
||||
}
|
||||
if n.isCollapsed() {
|
||||
n.expand()
|
||||
}
|
||||
it := n.next
|
||||
for it != nil && it != n.end {
|
||||
if it.hasChildren() {
|
||||
it.expandRecursively(level+1, maxLevel)
|
||||
it = it.end.next
|
||||
} else {
|
||||
it = it.next
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) findChildByKey(key string) *node {
|
||||
it := n.next
|
||||
for it != nil && it != n.end {
|
||||
if it.key != nil {
|
||||
k, err := strconv.Unquote(string(it.key))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if k == key {
|
||||
return it
|
||||
}
|
||||
}
|
||||
if it.chunkEnd != nil {
|
||||
it = it.chunkEnd.next
|
||||
} else if it.end != nil {
|
||||
it = it.end.next
|
||||
} else {
|
||||
it = it.next
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *node) findChildByIndex(index int) *node {
|
||||
for at := n.next; at != nil && at != n.end; {
|
||||
if at.index == index {
|
||||
return at
|
||||
}
|
||||
if at.end != nil {
|
||||
at = at.end.next
|
||||
} else {
|
||||
at = at.next
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *node) paths(prefix string, paths *[]string, nodes *[]*node) {
|
||||
it := n.next
|
||||
for it != nil && it != n.end {
|
||||
var path string
|
||||
|
||||
if it.key != nil {
|
||||
quoted := string(it.key)
|
||||
unquoted, err := strconv.Unquote(quoted)
|
||||
if err == nil && jsonpath.Identifier.MatchString(unquoted) {
|
||||
path = prefix + "." + unquoted
|
||||
} else {
|
||||
path = prefix + "[" + quoted + "]"
|
||||
}
|
||||
} else if it.index >= 0 {
|
||||
path = prefix + "[" + strconv.Itoa(it.index) + "]"
|
||||
}
|
||||
|
||||
*paths = append(*paths, path)
|
||||
*nodes = append(*nodes, it)
|
||||
|
||||
if it.hasChildren() {
|
||||
it.paths(path, paths, nodes)
|
||||
it = it.end.next
|
||||
} else {
|
||||
it = it.next
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) children() ([]string, []*node) {
|
||||
if !n.hasChildren() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var paths []string
|
||||
var nodes []*node
|
||||
|
||||
var it *node
|
||||
if n.isCollapsed() {
|
||||
it = n.collapsed
|
||||
} else {
|
||||
it = n.next
|
||||
}
|
||||
|
||||
for it != nil && it != n.end {
|
||||
if it.key != nil {
|
||||
key := string(it.key)
|
||||
unquoted, err := strconv.Unquote(key)
|
||||
if err == nil {
|
||||
key = unquoted
|
||||
}
|
||||
paths = append(paths, key)
|
||||
nodes = append(nodes, it)
|
||||
}
|
||||
|
||||
if it.hasChildren() {
|
||||
it = it.end.next
|
||||
} else {
|
||||
it = it.next
|
||||
}
|
||||
}
|
||||
|
||||
return paths, nodes
|
||||
}
|
27
reduce.go
27
reduce.go
@ -6,14 +6,28 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
|
||||
"github.com/antonmedv/fx/internal/engine"
|
||||
)
|
||||
|
||||
//go:embed npm/index.js
|
||||
var src []byte
|
||||
|
||||
func reduce(fns []string) {
|
||||
var deno bool
|
||||
|
||||
bin, err := exec.LookPath("node")
|
||||
if err != nil {
|
||||
bin, err = exec.LookPath("deno")
|
||||
if err != nil {
|
||||
engine.Reduce(fns)
|
||||
return
|
||||
}
|
||||
deno = true
|
||||
}
|
||||
|
||||
script := path.Join(os.TempDir(), fmt.Sprintf("fx-%v.js", version))
|
||||
_, err := os.Stat(script)
|
||||
_, err = os.Stat(script)
|
||||
if os.IsNotExist(err) {
|
||||
err := os.WriteFile(script, src, 0644)
|
||||
if err != nil {
|
||||
@ -21,17 +35,6 @@ func reduce(fns []string) {
|
||||
}
|
||||
}
|
||||
|
||||
deno := false
|
||||
bin, err := exec.LookPath("node")
|
||||
if err != nil {
|
||||
bin, err = exec.LookPath("deno")
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Node.js or Deno is required to run fx with reducers.\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
deno = true
|
||||
}
|
||||
|
||||
env := os.Environ()
|
||||
var args []string
|
||||
|
||||
|
16
search.go
16
search.go
@ -1,18 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
. "github.com/antonmedv/fx/internal/jsonx"
|
||||
)
|
||||
|
||||
type search struct {
|
||||
err error
|
||||
results []*node
|
||||
results []*Node
|
||||
cursor int
|
||||
values map[*node][]match
|
||||
keys map[*node][]match
|
||||
values map[*Node][]match
|
||||
keys map[*Node][]match
|
||||
}
|
||||
|
||||
func newSearch() *search {
|
||||
return &search{
|
||||
results: make([]*node, 0),
|
||||
values: make(map[*node][]match),
|
||||
keys: make(map[*node][]match),
|
||||
results: make([]*Node, 0),
|
||||
values: make(map[*Node][]match),
|
||||
keys: make(map[*Node][]match),
|
||||
}
|
||||
}
|
||||
|
||||
|
15
utils.go
15
utils.go
@ -4,21 +4,6 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func isHexDigit(ch byte) bool {
|
||||
return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')
|
||||
}
|
||||
|
||||
func isDigit(ch byte) bool {
|
||||
return ch >= '0' && ch <= '9'
|
||||
}
|
||||
|
||||
func max(i, j int) int {
|
||||
if i > j {
|
||||
return i
|
||||
}
|
||||
return j
|
||||
}
|
||||
|
||||
func regexCase(code string) (string, bool) {
|
||||
if strings.HasSuffix(code, "/i") {
|
||||
return code[:len(code)-2], true
|
||||
|
Loading…
Reference in New Issue
Block a user