Files
realy/text/helpers.go

298 lines
5.6 KiB
Go

package text
import (
"io"
"github.com/templexxx/xhex"
"realy.lol/hex"
"realy.lol/ints"
"realy.lol/kind"
"realy.lol/kinds"
)
// JSONKey generates the JSON format for an object key and terminates with the
// semicolon.
func JSONKey(dst, k by) (b by) {
dst = append(dst, '"')
dst = append(dst, k...)
dst = append(dst, '"', ':')
b = dst
return
}
// UnmarshalHex takes a byte string that should contain a quoted hexadecimal
// encoded value, decodes it in-place using a SIMD hex codec and returns the
// decoded truncated bytes (the other half will be as it was but no allocation
// is required).
func UnmarshalHex(b by) (h by, rem by, err er) {
rem = b[:]
var inQuote bo
var start no
for i := 0; i < len(b); i++ {
if !inQuote {
if b[i] == '"' {
inQuote = true
start = i + 1
}
} else if b[i] == '"' {
h = b[start:i]
rem = b[i+1:]
break
}
}
if !inQuote {
err = io.EOF
return
}
l := len(h)
if l%2 != 0 {
err = errorf.E("invalid length for hex: %d, %0x", len(h), h)
return
}
if err = xhex.Decode(h, h); chk.E(err) {
return
}
h = h[:l/2]
return
}
// UnmarshalQuoted performs an in-place unquoting of NIP-01 quoted byte string.
func UnmarshalQuoted(b by) (content, rem by, err er) {
if len(b) == 0 {
err = io.EOF
return
}
rem = b[:]
for ; len(rem) >= 0; rem = rem[1:] {
// advance to open quotes
if rem[0] == '"' {
rem = rem[1:]
content = rem
break
}
}
if len(rem) == 0 {
err = io.EOF
return
}
var escaping bo
var contentLen no
for len(rem) > 0 {
if rem[0] == '\\' {
if !escaping {
escaping = true
contentLen++
rem = rem[1:]
} else {
escaping = false
contentLen++
rem = rem[1:]
}
} else if rem[0] == '"' {
if !escaping {
rem = rem[1:]
content = content[:contentLen]
content = NostrUnescape(content)
return
}
contentLen++
rem = rem[1:]
escaping = false
} else {
escaping = false
switch rem[0] {
// none of these characters are allowed inside a JSON string:
//
// backspace, tab, newline, form feed or carriage return.
case '\b', '\t', '\n', '\f', '\r':
err = errorf.E("invalid character '%s' in quoted string",
NostrEscape(nil, rem[:1]))
return
}
contentLen++
rem = rem[1:]
}
}
return
}
func MarshalHexArray(dst by, ha []by) (b by) {
dst = append(dst, '[')
for i := range ha {
dst = AppendQuote(dst, ha[i], hex.EncAppend)
if i != len(ha)-1 {
dst = append(dst, ',')
}
}
dst = append(dst, ']')
b = dst
return
}
// UnmarshalHexArray unpacks a JSON array containing strings with hexadecimal,
// and checks all values have the specified byte size..
func UnmarshalHexArray(b by, size no) (t []by, rem by, err er) {
rem = b
var openBracket bo
for ; len(rem) > 0; rem = rem[1:] {
if rem[0] == '[' {
openBracket = true
} else if openBracket {
if rem[0] == ',' {
continue
} else if rem[0] == ']' {
rem = rem[1:]
return
} else if rem[0] == '"' {
var h by
if h, rem, err = UnmarshalHex(rem); chk.E(err) {
return
}
if len(h) != size {
err = errorf.E("invalid hex array size, got %d expect %d",
len(h), size)
return
}
t = append(t, h)
if rem[0] == ']' {
rem = rem[1:]
// done
return
}
}
}
}
return
}
// UnmarshalStringArray unpacks a JSON array containing strings.
func UnmarshalStringArray(b by) (t []by, rem by, err er) {
rem = b
var openBracket bo
for ; len(rem) > 0; rem = rem[1:] {
if rem[0] == '[' {
openBracket = true
} else if openBracket {
if rem[0] == ',' {
continue
} else if rem[0] == ']' {
rem = rem[1:]
return
} else if rem[0] == '"' {
var h by
if h, rem, err = UnmarshalQuoted(rem); chk.E(err) {
return
}
t = append(t, h)
if rem[0] == ']' {
rem = rem[1:]
// done
return
}
}
}
}
return
}
func MarshalKindsArray(dst by, ka *kinds.T) (b by) {
dst = append(dst, '[')
for i := range ka.K {
dst, _ = ka.K[i].MarshalJSON(dst)
if i != len(ka.K)-1 {
dst = append(dst, ',')
}
}
dst = append(dst, ']')
b = dst
return
}
func UnmarshalKindsArray(b by) (k *kinds.T, rem by, err er) {
rem = b
k = &kinds.T{}
var openedBracket bo
for ; len(rem) > 0; rem = rem[1:] {
if !openedBracket && rem[0] == '[' {
openedBracket = true
continue
} else if openedBracket {
if rem[0] == ']' {
// done
return
} else if rem[0] == ',' {
continue
}
kk := ints.New(0)
if rem, err = kk.UnmarshalJSON(rem); chk.E(err) {
return
}
k.K = append(k.K, kind.New(kk.Uint16()))
if rem[0] == ']' {
rem = rem[1:]
return
}
}
}
if !openedBracket {
log.I.F("\n%v\n%s", k, rem)
return nil, nil, errorf.E("kinds: failed to unmarshal\n%s\n%s\n%s", k,
b, rem)
}
return
}
func True() by { return by("true") }
func False() by { return by("false") }
func MarshalBool(src by, truth bo) by {
if truth {
return append(src, True()...)
}
return append(src, False()...)
}
func UnmarshalBool(src by) (rem by, truth bo, err er) {
rem = src
t, f := True(), False()
for i := range rem {
if rem[i] == t[0] {
if len(rem) < i+len(t) {
err = io.EOF
return
}
if equals(t, rem[i:i+len(t)]) {
truth = true
rem = rem[i+len(t):]
return
}
}
if rem[i] == f[0] {
if len(rem) < i+len(f) {
err = io.EOF
return
}
if equals(f, rem[i:i+len(f)]) {
rem = rem[i+len(f):]
return
}
}
}
// if a truth value is not found in the string it will run to the end
err = io.EOF
return
}
func Comma(b by) (rem by, err er) {
rem = b
for i := range rem {
if rem[i] == ',' {
rem = rem[i:]
return
}
}
err = io.EOF
return
}