mirror of
https://github.com/antonmedv/fx
synced 2024-11-01 21:40:20 +00:00
Move part of the code to pkg
This commit is contained in:
parent
aa0964a12e
commit
bf166fafda
26
dict.go
26
dict.go
@ -1,26 +0,0 @@
|
||||
package main
|
||||
|
||||
type dict struct {
|
||||
keys []string
|
||||
values map[string]interface{}
|
||||
}
|
||||
|
||||
func newDict() *dict {
|
||||
return &dict{
|
||||
keys: make([]string, 0),
|
||||
values: make(map[string]interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dict) get(key string) (interface{}, bool) {
|
||||
val, exists := d.values[key]
|
||||
return val, exists
|
||||
}
|
||||
|
||||
func (d *dict) set(key string, value interface{}) {
|
||||
_, exists := d.values[key]
|
||||
if !exists {
|
||||
d.keys = append(d.keys, key)
|
||||
}
|
||||
d.values[key] = value
|
||||
}
|
66
main.go
66
main.go
@ -3,21 +3,23 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
. "github.com/antonmedv/fx/pkg/dict"
|
||||
. "github.com/antonmedv/fx/pkg/json"
|
||||
"github.com/antonmedv/fx/pkg/reducer"
|
||||
. "github.com/antonmedv/fx/pkg/theme"
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/muesli/termenv"
|
||||
"golang.org/x/term"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"io/fs"
|
||||
"runtime/pprof"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type number = json.Number
|
||||
|
||||
func main() {
|
||||
cpuProfile := os.Getenv("CPU_PROFILE")
|
||||
if cpuProfile != "" {
|
||||
@ -35,12 +37,12 @@ func main() {
|
||||
if !ok {
|
||||
themeId = "1"
|
||||
}
|
||||
theme, ok := themes[themeId]
|
||||
theme, ok := Themes[themeId]
|
||||
if !ok {
|
||||
theme = themes["1"]
|
||||
theme = Themes["1"]
|
||||
}
|
||||
if termenv.ColorProfile() == termenv.Ascii {
|
||||
theme = themes["0"]
|
||||
theme = Themes["0"]
|
||||
}
|
||||
|
||||
filePath := ""
|
||||
@ -71,7 +73,7 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
dec.UseNumber()
|
||||
jsonObject, err := parse(dec)
|
||||
jsonObject, err := Parse(dec)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -79,17 +81,17 @@ func main() {
|
||||
tty := isatty.IsTerminal(os.Stdout.Fd())
|
||||
if len(args) > 0 || !tty {
|
||||
if len(args) > 0 && args[0] == "--print-code" {
|
||||
fmt.Print(generateCode(args[1:]))
|
||||
fmt.Print(reducer.GenerateCode(args[1:]))
|
||||
return
|
||||
}
|
||||
reduce(jsonObject, args, theme)
|
||||
reducer.Reduce(jsonObject, args, theme)
|
||||
return
|
||||
}
|
||||
|
||||
expand := map[string]bool{
|
||||
"": true,
|
||||
}
|
||||
if array, ok := jsonObject.(array); ok {
|
||||
if array, ok := jsonObject.(Array); ok {
|
||||
for i := range array {
|
||||
expand[accessor("", i)] = true
|
||||
}
|
||||
@ -97,14 +99,14 @@ func main() {
|
||||
parents := map[string]string{}
|
||||
children := map[string][]string{}
|
||||
canBeExpanded := map[string]bool{}
|
||||
dfs(jsonObject, func(it iterator) {
|
||||
parents[it.path] = it.parent
|
||||
children[it.parent] = append(children[it.parent], it.path)
|
||||
switch it.object.(type) {
|
||||
case *dict:
|
||||
canBeExpanded[it.path] = len(it.object.(*dict).keys) > 0
|
||||
case array:
|
||||
canBeExpanded[it.path] = len(it.object.(array)) > 0
|
||||
Dfs(jsonObject, func(it Iterator) {
|
||||
parents[it.Path] = it.Parent
|
||||
children[it.Parent] = append(children[it.Parent], it.Path)
|
||||
switch it.Object.(type) {
|
||||
case *Dict:
|
||||
canBeExpanded[it.Path] = len(it.Object.(*Dict).Keys) > 0
|
||||
case Array:
|
||||
canBeExpanded[it.Path] = len(it.Object.(Array)) > 0
|
||||
}
|
||||
})
|
||||
|
||||
@ -152,7 +154,7 @@ type model struct {
|
||||
json interface{}
|
||||
lines []string
|
||||
|
||||
mouseWheelDelta int // number of lines the mouse wheel will scroll
|
||||
mouseWheelDelta int // Number of lines the mouse wheel will scroll
|
||||
offset int // offset is the vertical scroll position
|
||||
|
||||
keyMap KeyMap
|
||||
@ -161,9 +163,9 @@ type model struct {
|
||||
expandedPaths map[string]bool // set of expanded paths
|
||||
canBeExpanded map[string]bool // set of path => can be expanded (i.e. dict or array)
|
||||
paths []string // array of paths on screen
|
||||
pathToLineNumber map[string]int // map of path => line number
|
||||
pathToLineNumber map[string]int // map of path => line Number
|
||||
pathToIndex map[string]int // map of path => index in m.paths
|
||||
lineNumberToPath map[int]string // map of line number => path
|
||||
lineNumberToPath map[int]string // map of line Number => path
|
||||
parents map[string]string // map of subpath => parent path
|
||||
children map[string][]string // map of path => child paths
|
||||
nextSiblings, prevSiblings map[string]string // map of path => sibling path
|
||||
@ -342,10 +344,10 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.render()
|
||||
|
||||
case key.Matches(msg, m.keyMap.ExpandAll):
|
||||
dfs(m.json, func(it iterator) {
|
||||
switch it.object.(type) {
|
||||
case *dict, array:
|
||||
m.expandedPaths[it.path] = true
|
||||
Dfs(m.json, func(it Iterator) {
|
||||
switch it.Object.(type) {
|
||||
case *Dict, Array:
|
||||
m.expandedPaths[it.Path] = true
|
||||
}
|
||||
})
|
||||
m.render()
|
||||
@ -400,13 +402,13 @@ func (m *model) View() string {
|
||||
if m.showHelp {
|
||||
statusBar := "Press Esc or q to close help."
|
||||
statusBar += strings.Repeat(" ", max(0, m.width-width(statusBar)))
|
||||
statusBar = m.theme.statusBar(statusBar)
|
||||
statusBar = m.theme.StatusBar(statusBar)
|
||||
return strings.Join(lines, "\n") + extraLines + "\n" + statusBar
|
||||
}
|
||||
statusBar := m.cursorPath() + " "
|
||||
statusBar += strings.Repeat(" ", max(0, m.width-width(statusBar)-width(m.fileName)))
|
||||
statusBar += m.fileName
|
||||
statusBar = m.theme.statusBar(statusBar)
|
||||
statusBar = m.theme.StatusBar(statusBar)
|
||||
output := strings.Join(lines, "\n") + extraLines + "\n" + statusBar
|
||||
if m.searchInput.Focused() {
|
||||
output += "\n/" + m.searchInput.View()
|
||||
@ -506,22 +508,22 @@ func (m *model) collapseRecursively(path string) {
|
||||
|
||||
func (m *model) collectSiblings(v interface{}, path string) {
|
||||
switch v.(type) {
|
||||
case *dict:
|
||||
case *Dict:
|
||||
prev := ""
|
||||
for _, k := range v.(*dict).keys {
|
||||
for _, k := range v.(*Dict).Keys {
|
||||
subpath := path + "." + k
|
||||
if prev != "" {
|
||||
m.nextSiblings[prev] = subpath
|
||||
m.prevSiblings[subpath] = prev
|
||||
}
|
||||
prev = subpath
|
||||
value, _ := v.(*dict).get(k)
|
||||
value, _ := v.(*Dict).Get(k)
|
||||
m.collectSiblings(value, subpath)
|
||||
}
|
||||
|
||||
case array:
|
||||
case Array:
|
||||
prev := ""
|
||||
for i, value := range v.(array) {
|
||||
for i, value := range v.(Array) {
|
||||
subpath := fmt.Sprintf("%v[%v]", path, i)
|
||||
if prev != "" {
|
||||
m.nextSiblings[prev] = subpath
|
||||
|
@ -1,50 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_parse(t *testing.T) {
|
||||
input := `{
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
"a": 3,
|
||||
"slice": [{"z": "z", "1": "1"}]
|
||||
}`
|
||||
|
||||
p, err := parse(json.NewDecoder(strings.NewReader(input)))
|
||||
if err != nil {
|
||||
t.Error("JSON parse error", err)
|
||||
}
|
||||
o := p.(*dict)
|
||||
|
||||
expectedKeys := []string{
|
||||
"a",
|
||||
"b",
|
||||
"slice",
|
||||
}
|
||||
for i := range o.keys {
|
||||
if o.keys[i] != expectedKeys[i] {
|
||||
t.Error("Wrong key order ", i, o.keys[i], "!=", expectedKeys[i])
|
||||
}
|
||||
}
|
||||
|
||||
s, ok := o.get("slice")
|
||||
if !ok {
|
||||
t.Error("slice missing")
|
||||
}
|
||||
a := s.(array)
|
||||
z := a[0].(*dict)
|
||||
|
||||
expectedKeys = []string{
|
||||
"z",
|
||||
"1",
|
||||
}
|
||||
for i := range z.keys {
|
||||
if z.keys[i] != expectedKeys[i] {
|
||||
t.Error("Wrong key order for nested map ", i, z.keys[i], "!=", expectedKeys[i])
|
||||
}
|
||||
}
|
||||
}
|
26
pkg/dict/dict.go
Normal file
26
pkg/dict/dict.go
Normal file
@ -0,0 +1,26 @@
|
||||
package dict
|
||||
|
||||
type Dict struct {
|
||||
Keys []string
|
||||
Values map[string]interface{}
|
||||
}
|
||||
|
||||
func NewDict() *Dict {
|
||||
return &Dict{
|
||||
Keys: make([]string, 0),
|
||||
Values: make(map[string]interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Dict) Get(key string) (interface{}, bool) {
|
||||
val, exists := d.Values[key]
|
||||
return val, exists
|
||||
}
|
||||
|
||||
func (d *Dict) Set(key string, value interface{}) {
|
||||
_, exists := d.Values[key]
|
||||
if !exists {
|
||||
d.Keys = append(d.Keys, key)
|
||||
}
|
||||
d.Values[key] = value
|
||||
}
|
@ -1,26 +1,26 @@
|
||||
package main
|
||||
package dict
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test_dict(t *testing.T) {
|
||||
d := newDict()
|
||||
d.set("number", 3)
|
||||
v, _ := d.get("number")
|
||||
d := NewDict()
|
||||
d.Set("number", 3)
|
||||
v, _ := d.Get("number")
|
||||
if v.(int) != 3 {
|
||||
t.Error("Set number")
|
||||
}
|
||||
// string
|
||||
d.set("string", "x")
|
||||
v, _ = d.get("string")
|
||||
d.Set("string", "x")
|
||||
v, _ = d.Get("string")
|
||||
if v.(string) != "x" {
|
||||
t.Error("Set string")
|
||||
}
|
||||
// string slice
|
||||
d.set("strings", []string{
|
||||
d.Set("strings", []string{
|
||||
"t",
|
||||
"u",
|
||||
})
|
||||
v, _ = d.get("strings")
|
||||
v, _ = d.Get("strings")
|
||||
if v.([]string)[0] != "t" {
|
||||
t.Error("Set strings first index")
|
||||
}
|
||||
@ -28,11 +28,11 @@ func Test_dict(t *testing.T) {
|
||||
t.Error("Set strings second index")
|
||||
}
|
||||
// mixed slice
|
||||
d.set("mixed", []interface{}{
|
||||
d.Set("mixed", []interface{}{
|
||||
1,
|
||||
"1",
|
||||
})
|
||||
v, _ = d.get("mixed")
|
||||
v, _ = d.Get("mixed")
|
||||
if v.([]interface{})[0].(int) != 1 {
|
||||
t.Error("Set mixed int")
|
||||
}
|
||||
@ -40,19 +40,19 @@ func Test_dict(t *testing.T) {
|
||||
t.Error("Set mixed string")
|
||||
}
|
||||
// overriding existing key
|
||||
d.set("number", 4)
|
||||
v, _ = d.get("number")
|
||||
d.Set("number", 4)
|
||||
v, _ = d.Get("number")
|
||||
if v.(int) != 4 {
|
||||
t.Error("Override existing key")
|
||||
}
|
||||
// keys
|
||||
// Keys
|
||||
expectedKeys := []string{
|
||||
"number",
|
||||
"string",
|
||||
"strings",
|
||||
"mixed",
|
||||
}
|
||||
for i, key := range d.keys {
|
||||
for i, key := range d.Keys {
|
||||
if key != expectedKeys[i] {
|
||||
t.Error("Keys method", key, "!=", expectedKeys[i])
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
package main
|
||||
package json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
. "github.com/antonmedv/fx/pkg/dict"
|
||||
)
|
||||
|
||||
func parse(dec *json.Decoder) (interface{}, error) {
|
||||
func Parse(dec *json.Decoder) (interface{}, error) {
|
||||
token, err := dec.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -20,8 +21,8 @@ func parse(dec *json.Decoder) (interface{}, error) {
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func decodeDict(dec *json.Decoder) (*dict, error) {
|
||||
d := newDict()
|
||||
func decodeDict(dec *json.Decoder) (*Dict, error) {
|
||||
d := NewDict()
|
||||
for {
|
||||
token, err := dec.Token()
|
||||
if err != nil {
|
||||
@ -50,14 +51,12 @@ func decodeDict(dec *json.Decoder) (*dict, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
d.set(key, value)
|
||||
d.Set(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
type array = []interface{}
|
||||
|
||||
func decodeArray(dec *json.Decoder) ([]interface{}, error) {
|
||||
slice := make(array, 0)
|
||||
slice := make(Array, 0)
|
||||
for index := 0; ; index++ {
|
||||
token, err := dec.Token()
|
||||
if err != nil {
|
51
pkg/json/parse_test.go
Normal file
51
pkg/json/parse_test.go
Normal file
@ -0,0 +1,51 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
. "github.com/antonmedv/fx/pkg/dict"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_parse(t *testing.T) {
|
||||
input := `{
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
"a": 3,
|
||||
"slice": [{"z": "z", "1": "1"}]
|
||||
}`
|
||||
|
||||
p, err := Parse(json.NewDecoder(strings.NewReader(input)))
|
||||
if err != nil {
|
||||
t.Error("JSON parse error", err)
|
||||
}
|
||||
o := p.(*Dict)
|
||||
|
||||
expectedKeys := []string{
|
||||
"a",
|
||||
"b",
|
||||
"slice",
|
||||
}
|
||||
for i := range o.Keys {
|
||||
if o.Keys[i] != expectedKeys[i] {
|
||||
t.Error("Wrong key order ", i, o.Keys[i], "!=", expectedKeys[i])
|
||||
}
|
||||
}
|
||||
|
||||
s, ok := o.Get("slice")
|
||||
if !ok {
|
||||
t.Error("slice missing")
|
||||
}
|
||||
a := s.(Array)
|
||||
z := a[0].(*Dict)
|
||||
|
||||
expectedKeys = []string{
|
||||
"z",
|
||||
"1",
|
||||
}
|
||||
for i := range z.Keys {
|
||||
if z.Keys[i] != expectedKeys[i] {
|
||||
t.Error("Wrong key order for nested map ", i, z.Keys[i], "!=", expectedKeys[i])
|
||||
}
|
||||
}
|
||||
}
|
71
pkg/json/pretty_print.go
Normal file
71
pkg/json/pretty_print.go
Normal file
@ -0,0 +1,71 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/antonmedv/fx/pkg/dict"
|
||||
"github.com/antonmedv/fx/pkg/theme"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func PrettyPrint(v interface{}, level int, theme theme.Theme) string {
|
||||
ident := strings.Repeat(" ", level)
|
||||
subident := strings.Repeat(" ", level-1)
|
||||
switch v.(type) {
|
||||
case nil:
|
||||
return theme.Null("null")
|
||||
|
||||
case bool:
|
||||
if v.(bool) {
|
||||
return theme.Boolean("true")
|
||||
} else {
|
||||
return theme.Boolean("false")
|
||||
}
|
||||
|
||||
case json.Number:
|
||||
return theme.Number(v.(json.Number).String())
|
||||
|
||||
case string:
|
||||
return theme.String(fmt.Sprintf("%q", v))
|
||||
|
||||
case *dict.Dict:
|
||||
keys := v.(*dict.Dict).Keys
|
||||
if len(keys) == 0 {
|
||||
return theme.Syntax("{}")
|
||||
}
|
||||
output := theme.Syntax("{")
|
||||
output += "\n"
|
||||
for i, k := range keys {
|
||||
key := theme.Key(i, len(keys))(fmt.Sprintf("%q", k))
|
||||
value, _ := v.(*dict.Dict).Get(k)
|
||||
delim := theme.Syntax(": ")
|
||||
line := ident + key + delim + PrettyPrint(value, level+1, theme)
|
||||
if i < len(keys)-1 {
|
||||
line += theme.Syntax(",")
|
||||
}
|
||||
line += "\n"
|
||||
output += line
|
||||
}
|
||||
return output + subident + theme.Syntax("}")
|
||||
|
||||
case []interface{}:
|
||||
slice := v.([]interface{})
|
||||
if len(slice) == 0 {
|
||||
return theme.Syntax("[]")
|
||||
}
|
||||
output := theme.Syntax("[\n")
|
||||
for i, value := range v.([]interface{}) {
|
||||
line := ident + PrettyPrint(value, level+1, theme)
|
||||
if i < len(slice)-1 {
|
||||
line += ",\n"
|
||||
} else {
|
||||
line += "\n"
|
||||
}
|
||||
output += line
|
||||
}
|
||||
return output + subident + theme.Syntax("]")
|
||||
|
||||
default:
|
||||
return "unknown type"
|
||||
}
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
package main
|
||||
package json
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
. "github.com/antonmedv/fx/pkg/dict"
|
||||
)
|
||||
|
||||
func stringify(v interface{}) string {
|
||||
func Stringify(v interface{}) string {
|
||||
switch v.(type) {
|
||||
case nil:
|
||||
return "null"
|
||||
@ -16,28 +17,28 @@ func stringify(v interface{}) string {
|
||||
return "false"
|
||||
}
|
||||
|
||||
case number:
|
||||
return v.(number).String()
|
||||
case Number:
|
||||
return v.(Number).String()
|
||||
|
||||
case string:
|
||||
return fmt.Sprintf("%q", v)
|
||||
|
||||
case *dict:
|
||||
case *Dict:
|
||||
result := "{"
|
||||
for i, key := range v.(*dict).keys {
|
||||
line := fmt.Sprintf("%q", key) + ": " + stringify(v.(*dict).values[key])
|
||||
if i < len(v.(*dict).keys)-1 {
|
||||
for i, key := range v.(*Dict).Keys {
|
||||
line := fmt.Sprintf("%q", key) + ": " + Stringify(v.(*Dict).Values[key])
|
||||
if i < len(v.(*Dict).Keys)-1 {
|
||||
line += ","
|
||||
}
|
||||
result += line
|
||||
}
|
||||
return result + "}"
|
||||
|
||||
case array:
|
||||
case Array:
|
||||
result := "["
|
||||
for i, value := range v.(array) {
|
||||
line := stringify(value)
|
||||
if i < len(v.(array))-1 {
|
||||
for i, value := range v.(Array) {
|
||||
line := Stringify(value)
|
||||
if i < len(v.(Array))-1 {
|
||||
line += ","
|
||||
}
|
||||
result += line
|
@ -1,28 +1,31 @@
|
||||
package main
|
||||
package json
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
. "github.com/antonmedv/fx/pkg/dict"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_stringify(t *testing.T) {
|
||||
t.Run("dict", func(t *testing.T) {
|
||||
arg := newDict()
|
||||
arg.set("a", number("1"))
|
||||
arg.set("b", number("2"))
|
||||
arg := NewDict()
|
||||
arg.Set("a", Number("1"))
|
||||
arg.Set("b", Number("2"))
|
||||
want := `{"a": 1,"b": 2}`
|
||||
if got := stringify(arg); got != want {
|
||||
if got := Stringify(arg); got != want {
|
||||
t.Errorf("stringify() = %v, want %v", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("array", func(t *testing.T) {
|
||||
arg := array{number("1"), number("2")}
|
||||
arg := Array{Number("1"), Number("2")}
|
||||
want := `[1,2]`
|
||||
if got := stringify(arg); got != want {
|
||||
if got := Stringify(arg); got != want {
|
||||
t.Errorf("stringify() = %v, want %v", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("array_with_dict", func(t *testing.T) {
|
||||
arg := array{newDict(), array{}}
|
||||
arg := Array{NewDict(), Array{}}
|
||||
want := `[{},[]]`
|
||||
if got := stringify(arg); got != want {
|
||||
if got := Stringify(arg); got != want {
|
||||
t.Errorf("stringify() = %v, want %v", got, want)
|
||||
}
|
||||
})
|
47
pkg/json/traverse.go
Normal file
47
pkg/json/traverse.go
Normal file
@ -0,0 +1,47 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
. "github.com/antonmedv/fx/pkg/dict"
|
||||
)
|
||||
|
||||
type Iterator struct {
|
||||
Object interface{}
|
||||
Path, Parent string
|
||||
}
|
||||
|
||||
func Dfs(object interface{}, f func(it Iterator)) {
|
||||
sub(Iterator{Object: object}, f)
|
||||
}
|
||||
|
||||
func sub(it Iterator, f func(it Iterator)) {
|
||||
f(it)
|
||||
switch it.Object.(type) {
|
||||
case *Dict:
|
||||
keys := it.Object.(*Dict).Keys
|
||||
for _, k := range keys {
|
||||
subpath := it.Path + "." + k
|
||||
value, _ := it.Object.(*Dict).Get(k)
|
||||
sub(Iterator{
|
||||
Object: value,
|
||||
Path: subpath,
|
||||
Parent: it.Path,
|
||||
}, f)
|
||||
}
|
||||
|
||||
case Array:
|
||||
slice := it.Object.(Array)
|
||||
for i, value := range slice {
|
||||
subpath := accessor(it.Path, i)
|
||||
sub(Iterator{
|
||||
Object: value,
|
||||
Path: subpath,
|
||||
Parent: it.Path,
|
||||
}, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func accessor(path string, to interface{}) string {
|
||||
return fmt.Sprintf("%v[%v]", path, to)
|
||||
}
|
6
pkg/json/types.go
Normal file
6
pkg/json/types.go
Normal file
@ -0,0 +1,6 @@
|
||||
package json
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
type Number = json.Number
|
||||
type Array = []interface{}
|
@ -1,10 +1,12 @@
|
||||
package main
|
||||
package reducer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
. "github.com/antonmedv/fx/pkg/json"
|
||||
. "github.com/antonmedv/fx/pkg/theme"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
@ -16,7 +18,7 @@ var template string
|
||||
|
||||
var flatMapRegex = regexp.MustCompile("^(\\.\\w*)+\\[]")
|
||||
|
||||
func generateCode(args []string) string {
|
||||
func GenerateCode(args []string) string {
|
||||
rs := "\n"
|
||||
for i, a := range args {
|
||||
rs += " try {"
|
||||
@ -74,9 +76,9 @@ func generateCode(args []string) string {
|
||||
}
|
||||
pointer := fmt.Sprintf(
|
||||
"%v %v %v",
|
||||
strings.Repeat(" ", width(pre)),
|
||||
strings.Repeat("^", width(a)),
|
||||
strings.Repeat(" ", width(post)),
|
||||
strings.Repeat(" ", len(pre)),
|
||||
strings.Repeat("^", len(a)),
|
||||
strings.Repeat(" ", len(post)),
|
||||
)
|
||||
rs += fmt.Sprintf(
|
||||
" throw `\\n"+
|
||||
@ -104,24 +106,24 @@ func fold(s []string) string {
|
||||
return fmt.Sprintf("x => Object.values(%v).flatMap(%v)", obj, fold(s[1:]))
|
||||
}
|
||||
|
||||
func reduce(object interface{}, args []string, theme Theme) {
|
||||
func Reduce(object interface{}, args []string, theme Theme) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd := exec.Command("node", "-e", generateCode(args))
|
||||
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.Stderr = &stderr
|
||||
err := cmd.Run()
|
||||
if err == nil {
|
||||
dec := json.NewDecoder(&stdout)
|
||||
dec.UseNumber()
|
||||
jsonObject, err := parse(dec)
|
||||
jsonObject, err := Parse(dec)
|
||||
if err == nil {
|
||||
if str, ok := jsonObject.(string); ok {
|
||||
fmt.Println(str)
|
||||
} else {
|
||||
fmt.Println(prettyPrint(jsonObject, 1, theme))
|
||||
fmt.Println(PrettyPrint(jsonObject, 1, theme))
|
||||
}
|
||||
} else {
|
||||
_, _ = fmt.Fprint(os.Stderr, stderr.String())
|
||||
@ -136,65 +138,3 @@ func reduce(object interface{}, args []string, theme Theme) {
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
}
|
||||
|
||||
func prettyPrint(v interface{}, level int, theme Theme) string {
|
||||
ident := strings.Repeat(" ", level)
|
||||
subident := strings.Repeat(" ", level-1)
|
||||
switch v.(type) {
|
||||
case nil:
|
||||
return theme.null("null")
|
||||
|
||||
case bool:
|
||||
if v.(bool) {
|
||||
return theme.boolean("true")
|
||||
} else {
|
||||
return theme.boolean("false")
|
||||
}
|
||||
|
||||
case number:
|
||||
return theme.number(v.(number).String())
|
||||
|
||||
case string:
|
||||
return theme.string(fmt.Sprintf("%q", v))
|
||||
|
||||
case *dict:
|
||||
keys := v.(*dict).keys
|
||||
if len(keys) == 0 {
|
||||
return theme.syntax("{}")
|
||||
}
|
||||
output := theme.syntax("{")
|
||||
output += "\n"
|
||||
for i, k := range keys {
|
||||
key := theme.key(i, len(keys))(fmt.Sprintf("%q", k))
|
||||
value, _ := v.(*dict).get(k)
|
||||
delim := theme.syntax(": ")
|
||||
line := ident + key + delim + prettyPrint(value, level+1, theme)
|
||||
if i < len(keys)-1 {
|
||||
line += theme.syntax(",")
|
||||
}
|
||||
line += "\n"
|
||||
output += line
|
||||
}
|
||||
return output + subident + theme.syntax("}")
|
||||
|
||||
case array:
|
||||
slice := v.(array)
|
||||
if len(slice) == 0 {
|
||||
return theme.syntax("[]")
|
||||
}
|
||||
output := theme.syntax("[\n")
|
||||
for i, value := range v.(array) {
|
||||
line := ident + prettyPrint(value, level+1, theme)
|
||||
if i < len(slice)-1 {
|
||||
line += ",\n"
|
||||
} else {
|
||||
line += "\n"
|
||||
}
|
||||
output += line
|
||||
}
|
||||
return output + subident + theme.syntax("]")
|
||||
|
||||
default:
|
||||
return "unknown type"
|
||||
}
|
||||
}
|
185
pkg/theme/theme.go
Normal file
185
pkg/theme/theme.go
Normal file
@ -0,0 +1,185 @@
|
||||
package theme
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/mazznoer/colorgrad"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Theme struct {
|
||||
Cursor Color
|
||||
Syntax Color
|
||||
Preview Color
|
||||
StatusBar Color
|
||||
Search Color
|
||||
Key func(i, len int) Color
|
||||
String Color
|
||||
Null Color
|
||||
Boolean Color
|
||||
Number Color
|
||||
}
|
||||
type Color func(s string) string
|
||||
|
||||
var (
|
||||
defaultCursor = lipgloss.NewStyle().Reverse(true).Render
|
||||
defaultPreview = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("8")).Render
|
||||
defaultStatusBar = lipgloss.NewStyle().Background(lipgloss.Color("7")).Foreground(lipgloss.Color("0")).Render
|
||||
defaultSearch = lipgloss.NewStyle().Background(lipgloss.Color("11")).Foreground(lipgloss.Color("16")).Render
|
||||
defaultNull = fg("8")
|
||||
)
|
||||
|
||||
var Themes = map[string]Theme{
|
||||
"0": {
|
||||
Cursor: defaultCursor,
|
||||
Syntax: noColor,
|
||||
Preview: noColor,
|
||||
StatusBar: noColor,
|
||||
Search: defaultSearch,
|
||||
Key: func(_, _ int) Color { return noColor },
|
||||
String: noColor,
|
||||
Null: noColor,
|
||||
Boolean: noColor,
|
||||
Number: noColor,
|
||||
},
|
||||
"1": {
|
||||
Cursor: defaultCursor,
|
||||
Syntax: noColor,
|
||||
Preview: defaultPreview,
|
||||
StatusBar: defaultStatusBar,
|
||||
Search: defaultSearch,
|
||||
Key: func(_, _ int) Color { return boldFg("4") },
|
||||
String: boldFg("2"),
|
||||
Null: defaultNull,
|
||||
Boolean: boldFg("3"),
|
||||
Number: boldFg("6"),
|
||||
},
|
||||
"2": {
|
||||
Cursor: defaultCursor,
|
||||
Syntax: noColor,
|
||||
Preview: defaultPreview,
|
||||
StatusBar: defaultStatusBar,
|
||||
Search: defaultSearch,
|
||||
Key: func(_, _ int) Color { return fg("#00F5D4") },
|
||||
String: fg("#00BBF9"),
|
||||
Null: defaultNull,
|
||||
Boolean: fg("#F15BB5"),
|
||||
Number: fg("#9B5DE5"),
|
||||
},
|
||||
"3": {
|
||||
Cursor: defaultCursor,
|
||||
Syntax: noColor,
|
||||
Preview: defaultPreview,
|
||||
StatusBar: defaultStatusBar,
|
||||
Search: defaultSearch,
|
||||
Key: func(_, _ int) Color { return fg("#faf0ca") },
|
||||
String: fg("#f4d35e"),
|
||||
Null: defaultNull,
|
||||
Boolean: fg("#ee964b"),
|
||||
Number: fg("#ee964b"),
|
||||
},
|
||||
"4": {
|
||||
Cursor: defaultCursor,
|
||||
Syntax: noColor,
|
||||
Preview: defaultPreview,
|
||||
StatusBar: defaultStatusBar,
|
||||
Search: defaultSearch,
|
||||
Key: func(_, _ int) Color { return fg("#4D96FF") },
|
||||
String: fg("#6BCB77"),
|
||||
Null: defaultNull,
|
||||
Boolean: fg("#FF6B6B"),
|
||||
Number: fg("#FFD93D"),
|
||||
},
|
||||
"5": {
|
||||
Cursor: defaultCursor,
|
||||
Syntax: noColor,
|
||||
Preview: defaultPreview,
|
||||
StatusBar: defaultStatusBar,
|
||||
Search: defaultSearch,
|
||||
Key: func(_, _ int) Color { return boldFg("42") },
|
||||
String: boldFg("213"),
|
||||
Null: defaultNull,
|
||||
Boolean: boldFg("201"),
|
||||
Number: boldFg("201"),
|
||||
},
|
||||
"6": {
|
||||
Cursor: defaultCursor,
|
||||
Syntax: noColor,
|
||||
Preview: defaultPreview,
|
||||
StatusBar: defaultStatusBar,
|
||||
Search: defaultSearch,
|
||||
Key: func(_, _ int) Color { return gradient("rgb(125,110,221)", "rgb(90%,45%,97%)", "hsl(229,79%,85%)") },
|
||||
String: fg("195"),
|
||||
Null: defaultNull,
|
||||
Boolean: fg("195"),
|
||||
Number: fg("195"),
|
||||
},
|
||||
"7": {
|
||||
Cursor: defaultCursor,
|
||||
Syntax: noColor,
|
||||
Preview: defaultPreview,
|
||||
StatusBar: defaultStatusBar,
|
||||
Search: defaultSearch,
|
||||
Key: func(_, _ int) Color { return gradient("rgb(123,216,96)", "rgb(255,255,255)") },
|
||||
String: noColor,
|
||||
Null: defaultNull,
|
||||
Boolean: noColor,
|
||||
Number: noColor,
|
||||
},
|
||||
"8": {
|
||||
Cursor: defaultCursor,
|
||||
Syntax: noColor,
|
||||
Preview: defaultPreview,
|
||||
StatusBar: defaultStatusBar,
|
||||
Search: defaultSearch,
|
||||
Key: gradientKeys("#ff0000", "#ff8700", "#ffd300", "#deff0a", "#a1ff0a", "#0aff99", "#0aefff", "#147df5", "#580aff", "#be0aff"),
|
||||
String: noColor,
|
||||
Null: defaultNull,
|
||||
Boolean: noColor,
|
||||
Number: noColor,
|
||||
},
|
||||
"9": {
|
||||
Cursor: defaultCursor,
|
||||
Syntax: noColor,
|
||||
Preview: defaultPreview,
|
||||
StatusBar: defaultStatusBar,
|
||||
Search: defaultSearch,
|
||||
Key: gradientKeys("rgb(34,126,34)", "rgb(168,251,60)"),
|
||||
String: gradient("rgb(34,126,34)", "rgb(168,251,60)"),
|
||||
Null: defaultNull,
|
||||
Boolean: noColor,
|
||||
Number: noColor,
|
||||
},
|
||||
}
|
||||
|
||||
func noColor(s string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
func fg(color string) Color {
|
||||
return lipgloss.NewStyle().Foreground(lipgloss.Color(color)).Render
|
||||
}
|
||||
|
||||
func boldFg(color string) Color {
|
||||
return lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color(color)).Render
|
||||
}
|
||||
|
||||
func gradient(colors ...string) Color {
|
||||
grad, _ := colorgrad.NewGradient().HtmlColors(colors...).Build()
|
||||
return func(s string) string {
|
||||
runes := []rune(s)
|
||||
colors := grad.ColorfulColors(uint(len(runes)))
|
||||
var out strings.Builder
|
||||
for i, r := range runes {
|
||||
style := lipgloss.NewStyle().Foreground(lipgloss.Color(colors[i].Hex()))
|
||||
out.WriteString(style.Render(string(r)))
|
||||
}
|
||||
return out.String()
|
||||
}
|
||||
}
|
||||
|
||||
func gradientKeys(colors ...string) func(i, len int) Color {
|
||||
grad, _ := colorgrad.NewGradient().HtmlColors(colors...).Build()
|
||||
return func(i, len int) Color {
|
||||
return lipgloss.NewStyle().Foreground(lipgloss.Color(grad.At(float64(i) / float64(len)).Hex())).Render
|
||||
}
|
||||
}
|
81
print.go
81
print.go
@ -2,6 +2,9 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
. "github.com/antonmedv/fx/pkg/dict"
|
||||
. "github.com/antonmedv/fx/pkg/json"
|
||||
"github.com/antonmedv/fx/pkg/theme"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -27,34 +30,34 @@ func (m *model) print(v interface{}, level, lineNumber, keyEndPos int, path stri
|
||||
|
||||
switch v.(type) {
|
||||
case nil:
|
||||
return []string{merge(m.explode("null", searchValue, m.theme.null, path, selectableValues))}
|
||||
return []string{merge(m.explode("null", searchValue, m.theme.Null, path, selectableValues))}
|
||||
|
||||
case bool:
|
||||
if v.(bool) {
|
||||
return []string{merge(m.explode("true", searchValue, m.theme.boolean, path, selectableValues))}
|
||||
return []string{merge(m.explode("true", searchValue, m.theme.Boolean, path, selectableValues))}
|
||||
} else {
|
||||
return []string{merge(m.explode("false", searchValue, m.theme.boolean, path, selectableValues))}
|
||||
return []string{merge(m.explode("false", searchValue, m.theme.Boolean, path, selectableValues))}
|
||||
}
|
||||
|
||||
case number:
|
||||
return []string{merge(m.explode(v.(number).String(), searchValue, m.theme.number, path, selectableValues))}
|
||||
case Number:
|
||||
return []string{merge(m.explode(v.(Number).String(), searchValue, m.theme.Number, path, selectableValues))}
|
||||
|
||||
case string:
|
||||
line := fmt.Sprintf("%q", v)
|
||||
chunks := m.explode(line, searchValue, m.theme.string, path, selectableValues)
|
||||
chunks := m.explode(line, searchValue, m.theme.String, path, selectableValues)
|
||||
if m.wrap && keyEndPos+width(line) > m.width {
|
||||
return wrapLines(chunks, keyEndPos, m.width, subident)
|
||||
}
|
||||
// No wrap
|
||||
return []string{merge(chunks)}
|
||||
|
||||
case *dict:
|
||||
case *Dict:
|
||||
if !m.expandedPaths[path] {
|
||||
return []string{m.preview(v, path, selectableValues)}
|
||||
}
|
||||
output := []string{m.printOpenBracket("{", highlight, path, selectableValues)}
|
||||
lineNumber++ // bracket is on separate line
|
||||
keys := v.(*dict).keys
|
||||
keys := v.(*Dict).Keys
|
||||
for i, k := range keys {
|
||||
subpath := path + "." + k
|
||||
highlight := m.highlightIndex[subpath]
|
||||
@ -65,10 +68,10 @@ func (m *model) print(v interface{}, level, lineNumber, keyEndPos int, path stri
|
||||
}
|
||||
m.connect(subpath, lineNumber)
|
||||
key := fmt.Sprintf("%q", k)
|
||||
keyTheme := m.theme.key(i, len(keys))
|
||||
keyTheme := m.theme.Key(i, len(keys))
|
||||
key = merge(m.explode(key, keyRanges, keyTheme, subpath, true))
|
||||
value, _ := v.(*dict).get(k)
|
||||
delim := merge(m.explode(": ", delimRanges, m.theme.syntax, subpath, false))
|
||||
value, _ := v.(*Dict).Get(k)
|
||||
delim := merge(m.explode(": ", delimRanges, m.theme.Syntax, subpath, false))
|
||||
keyEndPos := width(ident) + width(key) + width(delim)
|
||||
lines := m.print(value, level+1, lineNumber, keyEndPos, subpath, false)
|
||||
lines[0] = ident + key + delim + lines[0]
|
||||
@ -81,13 +84,13 @@ func (m *model) print(v interface{}, level, lineNumber, keyEndPos int, path stri
|
||||
output = append(output, subident+m.printCloseBracket("}", highlight, path, false))
|
||||
return output
|
||||
|
||||
case array:
|
||||
case Array:
|
||||
if !m.expandedPaths[path] {
|
||||
return []string{m.preview(v, path, selectableValues)}
|
||||
}
|
||||
output := []string{m.printOpenBracket("[", highlight, path, selectableValues)}
|
||||
lineNumber++ // bracket is on separate line
|
||||
slice := v.(array)
|
||||
slice := v.(Array)
|
||||
for i, value := range slice {
|
||||
subpath := fmt.Sprintf("%v[%v]", path, i)
|
||||
s := m.highlightIndex[subpath]
|
||||
@ -110,32 +113,32 @@ func (m *model) print(v interface{}, level, lineNumber, keyEndPos int, path stri
|
||||
|
||||
func (m *model) preview(v interface{}, path string, selectableValues bool) string {
|
||||
searchResult := m.highlightIndex[path]
|
||||
previewStyle := m.theme.preview
|
||||
previewStyle := m.theme.Preview
|
||||
if selectableValues && m.cursorPath() == path {
|
||||
previewStyle = m.theme.cursor
|
||||
previewStyle = m.theme.Cursor
|
||||
}
|
||||
printValue := func(value interface{}) string {
|
||||
switch value.(type) {
|
||||
case nil, bool, number:
|
||||
case nil, bool, Number:
|
||||
return previewStyle(fmt.Sprintf("%v", value))
|
||||
case string:
|
||||
return previewStyle(fmt.Sprintf("%q", value))
|
||||
case *dict:
|
||||
case *Dict:
|
||||
return previewStyle("{\u2026}")
|
||||
case array:
|
||||
case Array:
|
||||
return previewStyle("[\u2026]")
|
||||
}
|
||||
return "..."
|
||||
}
|
||||
|
||||
switch v.(type) {
|
||||
case *dict:
|
||||
case *Dict:
|
||||
output := m.printOpenBracket("{", searchResult, path, selectableValues)
|
||||
keys := v.(*dict).keys
|
||||
keys := v.(*Dict).Keys
|
||||
for _, k := range keys {
|
||||
key := fmt.Sprintf("%q", k)
|
||||
output += previewStyle(key + ": ")
|
||||
value, _ := v.(*dict).get(k)
|
||||
value, _ := v.(*Dict).Get(k)
|
||||
output += printValue(value)
|
||||
break
|
||||
}
|
||||
@ -145,9 +148,9 @@ func (m *model) preview(v interface{}, path string, selectableValues bool) strin
|
||||
output += m.printCloseBracket("}", searchResult, path, selectableValues)
|
||||
return output
|
||||
|
||||
case array:
|
||||
case Array:
|
||||
output := m.printOpenBracket("[", searchResult, path, selectableValues)
|
||||
slice := v.(array)
|
||||
slice := v.(Array)
|
||||
for _, value := range slice {
|
||||
output += printValue(value)
|
||||
break
|
||||
@ -194,62 +197,62 @@ func (w withStyle) Render(s string) string {
|
||||
|
||||
func (m *model) printOpenBracket(line string, s *rangeGroup, path string, selectableValues bool) string {
|
||||
if selectableValues && m.cursorPath() == path {
|
||||
return m.theme.cursor(line)
|
||||
return m.theme.Cursor(line)
|
||||
}
|
||||
if s != nil && s.openBracket != nil {
|
||||
if s.openBracket.parent.index == m.searchResultsCursor {
|
||||
return m.theme.cursor(line)
|
||||
return m.theme.Cursor(line)
|
||||
} else {
|
||||
return m.theme.search(line)
|
||||
return m.theme.Search(line)
|
||||
}
|
||||
} else {
|
||||
return m.theme.syntax(line)
|
||||
return m.theme.Syntax(line)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *model) printCloseBracket(line string, s *rangeGroup, path string, selectableValues bool) string {
|
||||
if selectableValues && m.cursorPath() == path {
|
||||
return m.theme.cursor(line)
|
||||
return m.theme.Cursor(line)
|
||||
}
|
||||
if s != nil && s.closeBracket != nil {
|
||||
if s.closeBracket.parent.index == m.searchResultsCursor {
|
||||
return m.theme.cursor(line)
|
||||
return m.theme.Cursor(line)
|
||||
} else {
|
||||
return m.theme.search(line)
|
||||
return m.theme.Search(line)
|
||||
}
|
||||
} else {
|
||||
return m.theme.syntax(line)
|
||||
return m.theme.Syntax(line)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *model) printComma(line string, s *rangeGroup) string {
|
||||
if s != nil && s.comma != nil {
|
||||
if s.comma.parent.index == m.searchResultsCursor {
|
||||
return m.theme.cursor(line)
|
||||
return m.theme.Cursor(line)
|
||||
} else {
|
||||
return m.theme.search(line)
|
||||
return m.theme.Search(line)
|
||||
}
|
||||
} else {
|
||||
return m.theme.syntax(line)
|
||||
return m.theme.Syntax(line)
|
||||
}
|
||||
}
|
||||
|
||||
type withStyle struct {
|
||||
value string
|
||||
style Color
|
||||
style theme.Color
|
||||
}
|
||||
|
||||
func (m *model) explode(line string, highlightRanges []*foundRange, defaultStyle Color, path string, selectable bool) []withStyle {
|
||||
func (m *model) explode(line string, highlightRanges []*foundRange, defaultStyle theme.Color, path string, selectable bool) []withStyle {
|
||||
if selectable && m.cursorPath() == path && m.showCursor {
|
||||
return []withStyle{{line, m.theme.cursor}}
|
||||
return []withStyle{{line, m.theme.Cursor}}
|
||||
}
|
||||
|
||||
out := make([]withStyle, 0, 1)
|
||||
pos := 0
|
||||
for _, r := range highlightRanges {
|
||||
style := m.theme.search
|
||||
style := m.theme.Search
|
||||
if r.parent.index == m.searchResultsCursor {
|
||||
style = m.theme.cursor
|
||||
style = m.theme.Cursor
|
||||
}
|
||||
out = append(out, withStyle{
|
||||
value: line[pos:r.start],
|
||||
|
22
search.go
22
search.go
@ -2,6 +2,8 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
. "github.com/antonmedv/fx/pkg/dict"
|
||||
. "github.com/antonmedv/fx/pkg/json"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
@ -54,7 +56,7 @@ func (m *model) doSearch(s string) {
|
||||
m.searchInput.Blur()
|
||||
return
|
||||
}
|
||||
indexes := re.FindAllStringIndex(stringify(m.json), -1)
|
||||
indexes := re.FindAllStringIndex(Stringify(m.json), -1)
|
||||
m.remapSearchResult(m.json, "", 0, indexes, 0, nil)
|
||||
m.indexSearchResults()
|
||||
m.searchInput.Blur()
|
||||
@ -79,8 +81,8 @@ func (m *model) remapSearchResult(object interface{}, path string, pos int, inde
|
||||
id, current = m.findRanges(valueRange, s, path, pos, indexes, id, current)
|
||||
return pos + len(s), id, current
|
||||
|
||||
case number:
|
||||
s := object.(number).String()
|
||||
case Number:
|
||||
s := object.(Number).String()
|
||||
id, current = m.findRanges(valueRange, s, path, pos, indexes, id, current)
|
||||
return pos + len(s), id, current
|
||||
|
||||
@ -89,10 +91,10 @@ func (m *model) remapSearchResult(object interface{}, path string, pos int, inde
|
||||
id, current = m.findRanges(valueRange, s, path, pos, indexes, id, current)
|
||||
return pos + len(s), id, current
|
||||
|
||||
case *dict:
|
||||
case *Dict:
|
||||
id, current = m.findRanges(openBracketRange, "{", path, pos, indexes, id, current)
|
||||
pos++ // {
|
||||
for i, k := range object.(*dict).keys {
|
||||
for i, k := range object.(*Dict).Keys {
|
||||
subpath := path + "." + k
|
||||
|
||||
key := fmt.Sprintf("%q", k)
|
||||
@ -103,8 +105,8 @@ func (m *model) remapSearchResult(object interface{}, path string, pos int, inde
|
||||
id, current = m.findRanges(delimRange, delim, subpath, pos, indexes, id, current)
|
||||
pos += len(delim)
|
||||
|
||||
pos, id, current = m.remapSearchResult(object.(*dict).values[k], subpath, pos, indexes, id, current)
|
||||
if i < len(object.(*dict).keys)-1 {
|
||||
pos, id, current = m.remapSearchResult(object.(*Dict).Values[k], subpath, pos, indexes, id, current)
|
||||
if i < len(object.(*Dict).Keys)-1 {
|
||||
comma := ","
|
||||
id, current = m.findRanges(commaRange, comma, subpath, pos, indexes, id, current)
|
||||
pos += len(comma)
|
||||
@ -114,13 +116,13 @@ func (m *model) remapSearchResult(object interface{}, path string, pos int, inde
|
||||
pos++ // }
|
||||
return pos, id, current
|
||||
|
||||
case array:
|
||||
case Array:
|
||||
id, current = m.findRanges(openBracketRange, "[", path, pos, indexes, id, current)
|
||||
pos++ // [
|
||||
for i, v := range object.(array) {
|
||||
for i, v := range object.(Array) {
|
||||
subpath := fmt.Sprintf("%v[%v]", path, i)
|
||||
pos, id, current = m.remapSearchResult(v, subpath, pos, indexes, id, current)
|
||||
if i < len(object.(array))-1 {
|
||||
if i < len(object.(Array))-1 {
|
||||
comma := ","
|
||||
id, current = m.findRanges(commaRange, comma, subpath, pos, indexes, id, current)
|
||||
pos += len(comma)
|
||||
|
@ -1,6 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
. "github.com/antonmedv/fx/pkg/dict"
|
||||
. "github.com/antonmedv/fx/pkg/json"
|
||||
"github.com/stretchr/testify/require"
|
||||
"regexp"
|
||||
"testing"
|
||||
@ -15,7 +17,7 @@ func Test_search_values(t *testing.T) {
|
||||
{name: "null", object: nil},
|
||||
{name: "true", object: true},
|
||||
{name: "false", object: false},
|
||||
{name: "number", object: number("42")},
|
||||
{name: "Number", object: Number("42")},
|
||||
{name: "string", object: "Hello, World!"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@ -24,7 +26,7 @@ func Test_search_values(t *testing.T) {
|
||||
json: tt.object,
|
||||
}
|
||||
re, _ := regexp.Compile(".+")
|
||||
str := stringify(m.json)
|
||||
str := Stringify(m.json)
|
||||
indexes := re.FindAllStringIndex(str, -1)
|
||||
m.remapSearchResult(m.json, "", 0, indexes, 0, nil)
|
||||
|
||||
@ -47,10 +49,10 @@ func Test_search_array(t *testing.T) {
|
||||
^^^^^ ^^^^^^
|
||||
`
|
||||
m := &model{
|
||||
json: array{"first", "second"},
|
||||
json: Array{"first", "second"},
|
||||
}
|
||||
re, _ := regexp.Compile("\\w+")
|
||||
indexes := re.FindAllStringIndex(stringify(m.json), -1)
|
||||
indexes := re.FindAllStringIndex(Stringify(m.json), -1)
|
||||
m.remapSearchResult(m.json, "", 0, indexes, 0, nil)
|
||||
|
||||
s1 := &searchResult{path: "[0]"}
|
||||
@ -82,10 +84,10 @@ func Test_search_between_array(t *testing.T) {
|
||||
^^^^^^^^^^^^^^
|
||||
`
|
||||
m := &model{
|
||||
json: array{"first", "second"},
|
||||
json: Array{"first", "second"},
|
||||
}
|
||||
re, _ := regexp.Compile("\\w.+\\w")
|
||||
indexes := re.FindAllStringIndex(stringify(m.json), -1)
|
||||
indexes := re.FindAllStringIndex(Stringify(m.json), -1)
|
||||
m.remapSearchResult(m.json, "", 0, indexes, 0, nil)
|
||||
|
||||
s := &searchResult{path: "[0]"}
|
||||
@ -120,13 +122,13 @@ func Test_search_dict(t *testing.T) {
|
||||
{"key": "hello world"}
|
||||
^^^^^ ^^^^^^^^^^^^^
|
||||
`
|
||||
d := newDict()
|
||||
d.set("key", "hello world")
|
||||
d := NewDict()
|
||||
d.Set("key", "hello world")
|
||||
m := &model{
|
||||
json: d,
|
||||
}
|
||||
re, _ := regexp.Compile("\"[\\w\\s]+\"")
|
||||
indexes := re.FindAllStringIndex(stringify(m.json), -1)
|
||||
indexes := re.FindAllStringIndex(Stringify(m.json), -1)
|
||||
m.remapSearchResult(m.json, "", 0, indexes, 0, nil)
|
||||
|
||||
s1 := &searchResult{path: ".key"}
|
||||
@ -157,14 +159,14 @@ func Test_search_dict_with_array(t *testing.T) {
|
||||
{"first": [1,2],"second": []}
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
`
|
||||
d := newDict()
|
||||
d.set("first", array{number("1"), number("2")})
|
||||
d.set("second", array{})
|
||||
d := NewDict()
|
||||
d.Set("first", Array{Number("1"), Number("2")})
|
||||
d.Set("second", Array{})
|
||||
m := &model{
|
||||
json: d,
|
||||
}
|
||||
re, _ := regexp.Compile(".+")
|
||||
indexes := re.FindAllStringIndex(stringify(m.json), -1)
|
||||
indexes := re.FindAllStringIndex(Stringify(m.json), -1)
|
||||
m.remapSearchResult(m.json, "", 0, indexes, 0, nil)
|
||||
|
||||
s := &searchResult{path: ""}
|
||||
|
185
theme.go
185
theme.go
@ -1,185 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/mazznoer/colorgrad"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Theme struct {
|
||||
cursor Color
|
||||
syntax Color
|
||||
preview Color
|
||||
statusBar Color
|
||||
search Color
|
||||
key func(i, len int) Color
|
||||
string Color
|
||||
null Color
|
||||
boolean Color
|
||||
number Color
|
||||
}
|
||||
type Color func(s string) string
|
||||
|
||||
func fg(color string) Color {
|
||||
return lipgloss.NewStyle().Foreground(lipgloss.Color(color)).Render
|
||||
}
|
||||
|
||||
func boldFg(color string) Color {
|
||||
return lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color(color)).Render
|
||||
}
|
||||
|
||||
var (
|
||||
defaultCursor = lipgloss.NewStyle().Reverse(true).Render
|
||||
defaultPreview = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("8")).Render
|
||||
defaultStatusBar = lipgloss.NewStyle().Background(lipgloss.Color("7")).Foreground(lipgloss.Color("0")).Render
|
||||
defaultSearch = lipgloss.NewStyle().Background(lipgloss.Color("11")).Foreground(lipgloss.Color("16")).Render
|
||||
defaultNull = fg("8")
|
||||
)
|
||||
|
||||
var themes = map[string]Theme{
|
||||
"0": {
|
||||
cursor: defaultCursor,
|
||||
syntax: noColor,
|
||||
preview: noColor,
|
||||
statusBar: noColor,
|
||||
search: defaultSearch,
|
||||
key: func(_, _ int) Color { return noColor },
|
||||
string: noColor,
|
||||
null: noColor,
|
||||
boolean: noColor,
|
||||
number: noColor,
|
||||
},
|
||||
"1": {
|
||||
cursor: defaultCursor,
|
||||
syntax: noColor,
|
||||
preview: defaultPreview,
|
||||
statusBar: defaultStatusBar,
|
||||
search: defaultSearch,
|
||||
key: func(_, _ int) Color { return boldFg("4") },
|
||||
string: boldFg("2"),
|
||||
null: defaultNull,
|
||||
boolean: boldFg("3"),
|
||||
number: boldFg("6"),
|
||||
},
|
||||
"2": {
|
||||
cursor: defaultCursor,
|
||||
syntax: noColor,
|
||||
preview: defaultPreview,
|
||||
statusBar: defaultStatusBar,
|
||||
search: defaultSearch,
|
||||
key: func(_, _ int) Color { return fg("#00F5D4") },
|
||||
string: fg("#00BBF9"),
|
||||
null: defaultNull,
|
||||
boolean: fg("#F15BB5"),
|
||||
number: fg("#9B5DE5"),
|
||||
},
|
||||
"3": {
|
||||
cursor: defaultCursor,
|
||||
syntax: noColor,
|
||||
preview: defaultPreview,
|
||||
statusBar: defaultStatusBar,
|
||||
search: defaultSearch,
|
||||
key: func(_, _ int) Color { return fg("#faf0ca") },
|
||||
string: fg("#f4d35e"),
|
||||
null: defaultNull,
|
||||
boolean: fg("#ee964b"),
|
||||
number: fg("#ee964b"),
|
||||
},
|
||||
"4": {
|
||||
cursor: defaultCursor,
|
||||
syntax: noColor,
|
||||
preview: defaultPreview,
|
||||
statusBar: defaultStatusBar,
|
||||
search: defaultSearch,
|
||||
key: func(_, _ int) Color { return fg("#4D96FF") },
|
||||
string: fg("#6BCB77"),
|
||||
null: defaultNull,
|
||||
boolean: fg("#FF6B6B"),
|
||||
number: fg("#FFD93D"),
|
||||
},
|
||||
"5": {
|
||||
cursor: defaultCursor,
|
||||
syntax: noColor,
|
||||
preview: defaultPreview,
|
||||
statusBar: defaultStatusBar,
|
||||
search: defaultSearch,
|
||||
key: func(_, _ int) Color { return boldFg("42") },
|
||||
string: boldFg("213"),
|
||||
null: defaultNull,
|
||||
boolean: boldFg("201"),
|
||||
number: boldFg("201"),
|
||||
},
|
||||
"6": {
|
||||
cursor: defaultCursor,
|
||||
syntax: noColor,
|
||||
preview: defaultPreview,
|
||||
statusBar: defaultStatusBar,
|
||||
search: defaultSearch,
|
||||
key: func(_, _ int) Color { return gradient("rgb(125,110,221)", "rgb(90%,45%,97%)", "hsl(229,79%,85%)") },
|
||||
string: fg("195"),
|
||||
null: defaultNull,
|
||||
boolean: fg("195"),
|
||||
number: fg("195"),
|
||||
},
|
||||
"7": {
|
||||
cursor: defaultCursor,
|
||||
syntax: noColor,
|
||||
preview: defaultPreview,
|
||||
statusBar: defaultStatusBar,
|
||||
search: defaultSearch,
|
||||
key: func(_, _ int) Color { return gradient("rgb(123,216,96)", "rgb(255,255,255)") },
|
||||
string: noColor,
|
||||
null: defaultNull,
|
||||
boolean: noColor,
|
||||
number: noColor,
|
||||
},
|
||||
"8": {
|
||||
cursor: defaultCursor,
|
||||
syntax: noColor,
|
||||
preview: defaultPreview,
|
||||
statusBar: defaultStatusBar,
|
||||
search: defaultSearch,
|
||||
key: gradientKeys("#ff0000", "#ff8700", "#ffd300", "#deff0a", "#a1ff0a", "#0aff99", "#0aefff", "#147df5", "#580aff", "#be0aff"),
|
||||
string: noColor,
|
||||
null: defaultNull,
|
||||
boolean: noColor,
|
||||
number: noColor,
|
||||
},
|
||||
"9": {
|
||||
cursor: defaultCursor,
|
||||
syntax: noColor,
|
||||
preview: defaultPreview,
|
||||
statusBar: defaultStatusBar,
|
||||
search: defaultSearch,
|
||||
key: gradientKeys("rgb(34,126,34)", "rgb(168,251,60)"),
|
||||
string: gradient("rgb(34,126,34)", "rgb(168,251,60)"),
|
||||
null: defaultNull,
|
||||
boolean: noColor,
|
||||
number: noColor,
|
||||
},
|
||||
}
|
||||
|
||||
func noColor(s string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
func gradient(colors ...string) Color {
|
||||
grad, _ := colorgrad.NewGradient().HtmlColors(colors...).Build()
|
||||
return func(s string) string {
|
||||
runes := []rune(s)
|
||||
colors := grad.ColorfulColors(uint(len(runes)))
|
||||
var out strings.Builder
|
||||
for i, r := range runes {
|
||||
style := lipgloss.NewStyle().Foreground(lipgloss.Color(colors[i].Hex()))
|
||||
out.WriteString(style.Render(string(r)))
|
||||
}
|
||||
return out.String()
|
||||
}
|
||||
}
|
||||
|
||||
func gradientKeys(colors ...string) func(i, len int) Color {
|
||||
grad, _ := colorgrad.NewGradient().HtmlColors(colors...).Build()
|
||||
return func(i, len int) Color {
|
||||
return lipgloss.NewStyle().Foreground(lipgloss.Color(grad.At(float64(i) / float64(len)).Hex())).Render
|
||||
}
|
||||
}
|
38
traverse.go
38
traverse.go
@ -1,38 +0,0 @@
|
||||
package main
|
||||
|
||||
type iterator struct {
|
||||
object interface{}
|
||||
path, parent string
|
||||
}
|
||||
|
||||
func dfs(object interface{}, f func(it iterator)) {
|
||||
sub(iterator{object: object}, f)
|
||||
}
|
||||
|
||||
func sub(it iterator, f func(it iterator)) {
|
||||
f(it)
|
||||
switch it.object.(type) {
|
||||
case *dict:
|
||||
keys := it.object.(*dict).keys
|
||||
for _, k := range keys {
|
||||
subpath := it.path + "." + k
|
||||
value, _ := it.object.(*dict).get(k)
|
||||
sub(iterator{
|
||||
object: value,
|
||||
path: subpath,
|
||||
parent: it.path,
|
||||
}, f)
|
||||
}
|
||||
|
||||
case array:
|
||||
slice := it.object.(array)
|
||||
for i, value := range slice {
|
||||
subpath := accessor(it.path, i)
|
||||
sub(iterator{
|
||||
object: value,
|
||||
path: subpath,
|
||||
parent: it.path,
|
||||
}, f)
|
||||
}
|
||||
}
|
||||
}
|
@ -81,8 +81,8 @@ func (m *model) LineDown(n int) {
|
||||
return
|
||||
}
|
||||
|
||||
// Make sure the number of lines by which we're going to scroll isn't
|
||||
// greater than the number of lines we actually have left before we reach
|
||||
// Make sure the Number of lines by which we're going to scroll isn't
|
||||
// greater than the Number of lines we actually have left before we reach
|
||||
// the bottom.
|
||||
m.SetOffset(m.offset + n)
|
||||
}
|
||||
@ -92,8 +92,8 @@ func (m *model) LineUp(n int) {
|
||||
return
|
||||
}
|
||||
|
||||
// Make sure the number of lines by which we're going to scroll isn't
|
||||
// greater than the number of lines we are from the top.
|
||||
// Make sure the Number of lines by which we're going to scroll isn't
|
||||
// greater than the Number of lines we are from the top.
|
||||
m.SetOffset(m.offset - n)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user