mirror of https://github.com/antonmedv/fx
Add new json parser
parent
ce2e3a4bad
commit
260e7cc444
@ -0,0 +1,26 @@
|
||||
module github.com/antonmedv/fx/new
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/charmbracelet/bubbles v0.16.1
|
||||
github.com/charmbracelet/bubbletea v0.24.2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.18 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/muesli/termenv v0.15.1 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
golang.org/x/term v0.6.0 // indirect
|
||||
golang.org/x/text v0.3.8 // indirect
|
||||
)
|
@ -0,0 +1,37 @@
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=
|
||||
github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
|
||||
github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY=
|
||||
github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||
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-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
||||
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs=
|
||||
github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
@ -0,0 +1,336 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type jsonParser struct {
|
||||
data []byte
|
||||
end int
|
||||
lastChar byte
|
||||
lineNumber uint
|
||||
sourceTail *ring
|
||||
depth uint8
|
||||
skipFirstIdent bool
|
||||
}
|
||||
|
||||
func parse(data []byte) (line *node, err error) {
|
||||
p := &jsonParser{
|
||||
data: data,
|
||||
lineNumber: 1,
|
||||
sourceTail: &ring{},
|
||||
}
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = p.errorSnippet(fmt.Sprintf("%v", r))
|
||||
}
|
||||
}()
|
||||
p.next()
|
||||
line = p.parseValue()
|
||||
if p.lastChar != 0 {
|
||||
panic(fmt.Sprintf("Unexpected character %q after root node", p.lastChar))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *jsonParser) next() {
|
||||
if p.end < len(p.data) {
|
||||
p.lastChar = p.data[p.end]
|
||||
p.end++
|
||||
} else {
|
||||
p.lastChar = 0
|
||||
}
|
||||
p.sourceTail.writeByte(p.lastChar)
|
||||
}
|
||||
|
||||
func (p *jsonParser) parseValue() *node {
|
||||
p.skipWhitespace()
|
||||
|
||||
var l *node
|
||||
switch p.lastChar {
|
||||
case '"':
|
||||
l = p.parseString()
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-':
|
||||
l = p.parseNumber()
|
||||
case '{':
|
||||
l = p.parseObject()
|
||||
case '[':
|
||||
l = p.parseArray()
|
||||
case 't':
|
||||
l = p.parseKeyword("true")
|
||||
case 'f':
|
||||
l = p.parseKeyword("false")
|
||||
case 'n':
|
||||
l = p.parseKeyword("null")
|
||||
default:
|
||||
panic(fmt.Sprintf("Unexpected character %q", p.lastChar))
|
||||
}
|
||||
|
||||
p.skipWhitespace()
|
||||
return l
|
||||
}
|
||||
|
||||
func (p *jsonParser) parseString() *node {
|
||||
str := &node{depth: p.depth}
|
||||
start := p.end - 1
|
||||
p.next()
|
||||
escaped := false
|
||||
for {
|
||||
if escaped {
|
||||
switch p.lastChar {
|
||||
case 'u':
|
||||
var unicode string
|
||||
for i := 0; i < 4; i++ {
|
||||
p.next()
|
||||
if !isHexDigit(p.lastChar) {
|
||||
panic(fmt.Sprintf("Invalid Unicode escape sequence '\\u%s%c'", unicode, p.lastChar))
|
||||
}
|
||||
unicode += string(p.lastChar)
|
||||
}
|
||||
_, err := strconv.ParseInt(unicode, 16, 32)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Invalid Unicode escape sequence '\\u%s'", unicode))
|
||||
}
|
||||
case '"', '\\', '/', 'b', 'f', 'n', 'r', 't':
|
||||
default:
|
||||
panic(fmt.Sprintf("Invalid escape sequence '\\%c'", p.lastChar))
|
||||
}
|
||||
escaped = false
|
||||
} else if p.lastChar == '\\' {
|
||||
escaped = true
|
||||
} else if p.lastChar == '"' {
|
||||
break
|
||||
} else if p.lastChar == 0 {
|
||||
panic("Unexpected end of input in string")
|
||||
} else if p.lastChar < 0x1F {
|
||||
panic(fmt.Sprintf("Invalid character %q in string", p.lastChar))
|
||||
}
|
||||
p.next()
|
||||
}
|
||||
|
||||
str.value = p.data[start:p.end]
|
||||
p.next()
|
||||
return str
|
||||
}
|
||||
|
||||
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) {
|
||||
panic(fmt.Sprintf("Invalid character %q in number", p.lastChar))
|
||||
}
|
||||
}
|
||||
|
||||
// Leading zero
|
||||
if p.lastChar == '0' {
|
||||
p.next()
|
||||
} else {
|
||||
for isDigit(p.lastChar) {
|
||||
p.next()
|
||||
}
|
||||
}
|
||||
|
||||
// Decimal portion
|
||||
if p.lastChar == '.' {
|
||||
p.next()
|
||||
if !isDigit(p.lastChar) {
|
||||
panic(fmt.Sprintf("Invalid character %q in number", p.lastChar))
|
||||
}
|
||||
for isDigit(p.lastChar) {
|
||||
p.next()
|
||||
}
|
||||
}
|
||||
|
||||
// Exponent
|
||||
if p.lastChar == 'e' || p.lastChar == 'E' {
|
||||
p.next()
|
||||
if p.lastChar == '+' || p.lastChar == '-' {
|
||||
p.next()
|
||||
}
|
||||
if !isDigit(p.lastChar) {
|
||||
panic(fmt.Sprintf("Invalid character %q in number", p.lastChar))
|
||||
}
|
||||
for isDigit(p.lastChar) {
|
||||
p.next()
|
||||
}
|
||||
}
|
||||
|
||||
num.value = p.data[start : p.end-1]
|
||||
return num
|
||||
}
|
||||
|
||||
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, '}')
|
||||
p.next()
|
||||
return object
|
||||
}
|
||||
|
||||
for {
|
||||
// Expecting a key which should be a string
|
||||
if p.lastChar != '"' {
|
||||
panic(fmt.Sprintf("Expected object key to be a string, got %q", p.lastChar))
|
||||
}
|
||||
|
||||
p.depth++
|
||||
key := p.parseString()
|
||||
key.key, key.value = key.value, nil
|
||||
|
||||
p.skipWhitespace()
|
||||
|
||||
// Expecting colon after key
|
||||
if p.lastChar != ':' {
|
||||
panic(fmt.Sprintf("Expected colon after object key, got %q", p.lastChar))
|
||||
}
|
||||
|
||||
p.next()
|
||||
|
||||
p.skipFirstIdent = true
|
||||
value := p.parseValue()
|
||||
p.depth--
|
||||
|
||||
key.value = value.value
|
||||
key.next = value.next
|
||||
if key.next != nil {
|
||||
key.next.prev = key
|
||||
}
|
||||
key.end = value.end
|
||||
object.append(key)
|
||||
|
||||
p.skipWhitespace()
|
||||
|
||||
// End of object
|
||||
if p.lastChar == '}' {
|
||||
closeBracket := &node{depth: p.depth}
|
||||
closeBracket.value = []byte{'}'}
|
||||
object.append(closeBracket)
|
||||
p.next()
|
||||
return object
|
||||
}
|
||||
|
||||
// Multiple key-value pairs separated by comma
|
||||
if p.lastChar == ',' {
|
||||
object.end.comma = true
|
||||
p.next()
|
||||
p.skipWhitespace()
|
||||
continue
|
||||
}
|
||||
|
||||
// Unexpected character
|
||||
panic(fmt.Sprintf("Unexpected character %q in object", p.lastChar))
|
||||
}
|
||||
}
|
||||
|
||||
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, ']')
|
||||
p.next()
|
||||
return arr
|
||||
}
|
||||
for {
|
||||
p.depth++
|
||||
value := p.parseValue()
|
||||
p.depth--
|
||||
arr.append(value)
|
||||
p.skipWhitespace()
|
||||
if p.lastChar == ']' {
|
||||
closeBracket := &node{depth: p.depth}
|
||||
closeBracket.value = []byte{']'}
|
||||
arr.append(closeBracket)
|
||||
p.next()
|
||||
return arr
|
||||
} else if p.lastChar == ',' {
|
||||
arr.end.comma = true
|
||||
p.next()
|
||||
p.skipWhitespace()
|
||||
continue
|
||||
} else {
|
||||
panic(fmt.Sprintf("Invalid character %q in array", p.lastChar))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *jsonParser) parseKeyword(name string) *node {
|
||||
for i := 1; i < len(name); i++ {
|
||||
p.next()
|
||||
if p.lastChar != name[i] {
|
||||
panic(fmt.Sprintf("Unexpected character %q in keyword", p.lastChar))
|
||||
}
|
||||
}
|
||||
p.next()
|
||||
|
||||
nextCharIsSpecial := isWhitespace(p.lastChar) || p.lastChar == ',' || p.lastChar == '}' || p.lastChar == ']' || p.lastChar == 0
|
||||
if nextCharIsSpecial {
|
||||
keyword := &node{depth: p.depth}
|
||||
keyword.value = []byte(name)
|
||||
return keyword
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("Unexpected character %q in keyword", p.lastChar))
|
||||
}
|
||||
|
||||
func isWhitespace(ch byte) bool {
|
||||
return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'
|
||||
}
|
||||
|
||||
func (p *jsonParser) skipWhitespace() {
|
||||
for {
|
||||
switch p.lastChar {
|
||||
case ' ', '\t', '\n', '\r':
|
||||
p.next()
|
||||
case '/':
|
||||
p.skipComment()
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *jsonParser) skipComment() {
|
||||
p.next()
|
||||
switch p.lastChar {
|
||||
case '/':
|
||||
for p.lastChar != '\n' && p.lastChar != 0 {
|
||||
p.next()
|
||||
}
|
||||
case '*':
|
||||
for {
|
||||
p.next()
|
||||
if p.lastChar == '*' {
|
||||
p.next()
|
||||
if p.lastChar == '/' {
|
||||
p.next()
|
||||
return
|
||||
}
|
||||
}
|
||||
if p.lastChar == 0 {
|
||||
panic("Unexpected end of input in comment")
|
||||
}
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("Invalid comment: '/%c'", p.lastChar))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *jsonParser) errorSnippet(message string) error {
|
||||
if p.lastChar == 0 {
|
||||
message = "Unexpected end of input"
|
||||
}
|
||||
return fmt.Errorf("%s on node %d.\n%s\n", message, p.lineNumber, p.sourceTail.string())
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
package main
|
||||
|
||||
import "github.com/charmbracelet/bubbles/key"
|
||||
|
||||
type KeyMap struct {
|
||||
Quit key.Binding
|
||||
Help key.Binding
|
||||
PageDown key.Binding
|
||||
PageUp key.Binding
|
||||
HalfPageUp key.Binding
|
||||
HalfPageDown key.Binding
|
||||
GotoTop key.Binding
|
||||
GotoBottom key.Binding
|
||||
Down key.Binding
|
||||
Up key.Binding
|
||||
Expand key.Binding
|
||||
Collapse key.Binding
|
||||
ExpandRecursively key.Binding
|
||||
CollapseRecursively key.Binding
|
||||
ExpandAll key.Binding
|
||||
CollapseAll key.Binding
|
||||
NextSibling key.Binding
|
||||
PrevSibling key.Binding
|
||||
ToggleWrap key.Binding
|
||||
Search key.Binding
|
||||
Next key.Binding
|
||||
Prev key.Binding
|
||||
}
|
||||
|
||||
var keyMap KeyMap
|
||||
|
||||
func init() {
|
||||
keyMap = KeyMap{
|
||||
Quit: key.NewBinding(
|
||||
key.WithKeys("q", "ctrl+c", "esc"),
|
||||
key.WithHelp("", "exit program"),
|
||||
),
|
||||
Help: key.NewBinding(
|
||||
key.WithKeys("?"),
|
||||
key.WithHelp("", "show help"),
|
||||
),
|
||||
PageDown: key.NewBinding(
|
||||
key.WithKeys("pgdown", " ", "f"),
|
||||
key.WithHelp("pgdown, space, f", "page down"),
|
||||
),
|
||||
PageUp: key.NewBinding(
|
||||
key.WithKeys("pgup", "b"),
|
||||
key.WithHelp("pgup, b", "page up"),
|
||||
),
|
||||
HalfPageUp: key.NewBinding(
|
||||
key.WithKeys("u", "ctrl+u"),
|
||||
key.WithHelp("", "half page up"),
|
||||
),
|
||||
HalfPageDown: key.NewBinding(
|
||||
key.WithKeys("d", "ctrl+d"),
|
||||
key.WithHelp("", "half page down"),
|
||||
),
|
||||
GotoTop: key.NewBinding(
|
||||
key.WithKeys("g"),
|
||||
key.WithHelp("", "goto top"),
|
||||
),
|
||||
GotoBottom: key.NewBinding(
|
||||
key.WithKeys("G"),
|
||||
key.WithHelp("", "goto bottom"),
|
||||
),
|
||||
Down: key.NewBinding(
|
||||
key.WithKeys("down", "j"),
|
||||
key.WithHelp("", "down"),
|
||||
),
|
||||
Up: key.NewBinding(
|
||||
key.WithKeys("up", "k"),
|
||||
key.WithHelp("", "up"),
|
||||
),
|
||||
Expand: key.NewBinding(
|
||||
key.WithKeys("right", "l"),
|
||||
key.WithHelp("", "expand"),
|
||||
),
|
||||
Collapse: key.NewBinding(
|
||||
key.WithKeys("left", "h"),
|
||||
key.WithHelp("", "collapse"),
|
||||
),
|
||||
ExpandRecursively: key.NewBinding(
|
||||
key.WithKeys("L"),
|
||||
key.WithHelp("", "expand recursively"),
|
||||
),
|
||||
CollapseRecursively: key.NewBinding(
|
||||
key.WithKeys("H"),
|
||||
key.WithHelp("", "collapse recursively"),
|
||||
),
|
||||
ExpandAll: key.NewBinding(
|
||||
key.WithKeys("e"),
|
||||
key.WithHelp("", "expand all"),
|
||||
),
|
||||
CollapseAll: key.NewBinding(
|
||||
key.WithKeys("E"),
|
||||
key.WithHelp("", "collapse all"),
|
||||
),
|
||||
NextSibling: key.NewBinding(
|
||||
key.WithKeys("J"),
|
||||
key.WithHelp("", "next sibling"),
|
||||
),
|
||||
PrevSibling: key.NewBinding(
|
||||
key.WithKeys("K"),
|
||||
key.WithHelp("", "previous sibling"),
|
||||
),
|
||||
ToggleWrap: key.NewBinding(
|
||||
key.WithKeys("z"),
|
||||
key.WithHelp("", "toggle strings wrap"),
|
||||
),
|
||||
Search: key.NewBinding(
|
||||
key.WithKeys("/"),
|
||||
key.WithHelp("", "search regexp"),
|
||||
),
|
||||
Next: key.NewBinding(
|
||||
key.WithKeys("n"),
|
||||
key.WithHelp("", "next search result"),
|
||||
),
|
||||
Prev: key.NewBinding(
|
||||
key.WithKeys("N"),
|
||||
key.WithHelp("", "prev search result"),
|
||||
),
|
||||
}
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime/pprof"
|
||||
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cpuProfile := os.Getenv("CPU_PROFILE")
|
||||
if cpuProfile != "" {
|
||||
f, err := os.Create(cpuProfile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = pprof.StartCPUProfile(f)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
head, err := parse(data)
|
||||
if err != nil {
|
||||
fmt.Print(err.Error())
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
|
||||
m := &model{
|
||||
head: head,
|
||||
}
|
||||
|
||||
p := tea.NewProgram(m, tea.WithAltScreen(), tea.WithMouseCellMotion())
|
||||
_, err = p.Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if cpuProfile != "" {
|
||||
pprof.StopCPUProfile()
|
||||
}
|
||||
}
|
||||
|
||||
type model struct {
|
||||
windowWidth, windowHeight int
|
||||
head *node
|
||||
}
|
||||
|
||||
func (m *model) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.windowWidth = msg.Width
|
||||
m.windowHeight = msg.Height
|
||||
|
||||
case tea.MouseMsg:
|
||||
switch msg.Type {
|
||||
case tea.MouseWheelUp:
|
||||
case tea.MouseWheelDown:
|
||||
}
|
||||
|
||||
case tea.KeyMsg:
|
||||
return m.handleKey(msg)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
switch {
|
||||
case key.Matches(msg, keyMap.Quit):
|
||||
return m, tea.Quit
|
||||
|
||||
case key.Matches(msg, keyMap.Up):
|
||||
m.head = m.head.prev
|
||||
return m, nil
|
||||
|
||||
case key.Matches(msg, keyMap.Down):
|
||||
m.head = m.head.next
|
||||
return m, nil
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *model) View() string {
|
||||
var screen []byte
|
||||
head := m.head
|
||||
for i := 0; i < m.windowHeight; i++ {
|
||||
if head == nil {
|
||||
break
|
||||
}
|
||||
for ident := 0; ident < int(head.depth); ident++ {
|
||||
screen = append(screen, ' ', ' ')
|
||||
}
|
||||
if head.key != nil {
|
||||
screen = append(screen, head.key...)
|
||||
screen = append(screen, ':', ' ')
|
||||
}
|
||||
screen = append(screen, head.value...)
|
||||
if head.comma {
|
||||
screen = append(screen, ',')
|
||||
}
|
||||
screen = append(screen, '\n')
|
||||
head = head.next
|
||||
}
|
||||
if len(screen) > 0 && screen[len(screen)-1] == '\n' {
|
||||
screen = screen[:len(screen)-1]
|
||||
}
|
||||
return string(screen)
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
type node struct {
|
||||
prev, next, end *node
|
||||
depth uint8
|
||||
key []byte
|
||||
value []byte
|
||||
comma bool
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package main
|
||||
|
||||
type ring struct {
|
||||
buf [100]byte
|
||||
start, end int
|
||||
}
|
||||
|
||||
func (r *ring) writeByte(b byte) {
|
||||
if b == '\n' {
|
||||
r.end = 0
|
||||
r.start = r.end
|
||||
return
|
||||
}
|
||||
r.buf[r.end] = b
|
||||
r.end++
|
||||
if r.end >= len(r.buf) {
|
||||
r.end = 0
|
||||
}
|
||||
if r.end == r.start {
|
||||
r.start++
|
||||
if r.start >= len(r.buf) {
|
||||
r.start = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ring) string() string {
|
||||
if r.start < r.end {
|
||||
return string(r.buf[r.start:r.end])
|
||||
}
|
||||
return string(r.buf[r.start:]) + string(r.buf[:r.end])
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package main
|
||||
|
||||
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'
|
||||
}
|
Loading…
Reference in New Issue