package kind import ( _ "embed" ) //go:embed kinds.json var KindsJSON []byte type KindData struct { Version string `json:"version"` Source string `json:"source"` Ranges Ranges `json:"ranges"` Kinds map[string]KindInfo `json:"kinds"` Privileged []int `json:"privileged"` Directory []int `json:"directory"` Aliases map[string]int `json:"aliases"` } type Ranges struct { Regular Range `json:"regular"` Replaceable Range `json:"replaceable"` Ephemeral Range `json:"ephemeral"` Parameterized Range `json:"parameterized"` } type Range struct { Start int `json:"start"` End int `json:"end"` Description string `json:"description"` } type KindInfo struct { Name string `json:"name"` NIP *string `json:"nip,omitempty"` Description string `json:"description"` Classification string `json:"classification,omitempty"` Deprecated bool `json:"deprecated,omitempty"` DeprecatedBy string `json:"deprecatedBy,omitempty"` Spec string `json:"spec,omitempty"` RangeEnd int `json:"rangeEnd,omitempty"` } var kindData *KindData func init() { kindData = &KindData{Kinds: map[string]KindInfo{}} // Lightweight parse: extract kind entries from "kinds": { "N": {"name":"...",...}, ... } // to populate Map and kindData.Kinds without encoding/json. parseKindsJSON(KindsJSON) } // parseKindsJSON is a minimal parser that extracts kind data from the // embedded JSON. Avoids encoding/json which crashes under moxie's runtime. func parseKindsJSON(data []byte) { // Find "kinds": { kindsKey := []byte(`"kinds"`) idx := indexBytes(data, kindsKey) if idx < 0 { return } data = data[idx+len(kindsKey):] // Skip to opening brace. for len(data) > 0 && data[0] != '{' { data = data[1:] } if len(data) == 0 { return } data = data[1:] // skip { // Parse each "N": { ... } entry. for len(data) > 0 { data = skipWS(data) if len(data) == 0 || data[0] == '}' { break } if data[0] == ',' { data = data[1:] continue } // Parse key (kind number as string). kStr, rest := parseQuoted(data) if rest == nil { break } data = rest data = skipWS(data) if len(data) == 0 || data[0] != ':' { break } data = data[1:] data = skipWS(data) // Parse value object. info, rest := parseKindInfoObj(data) if rest == nil { break } data = rest kindData.Kinds[string(kStr)] = info k := atoi(kStr) if k >= 0 { Map[uint16(k)] = info.Name } } // Parse ranges. rangesKey := []byte(`"ranges"`) idx = indexBytes(KindsJSON, rangesKey) if idx >= 0 { r := KindsJSON[idx+len(rangesKey):] for len(r) > 0 && r[0] != '{' { r = r[1:] } if len(r) > 0 { r = r[1:] kindData.Ranges.Regular = parseRange(r, []byte(`"regular"`)) kindData.Ranges.Replaceable = parseRange(r, []byte(`"replaceable"`)) kindData.Ranges.Ephemeral = parseRange(r, []byte(`"ephemeral"`)) kindData.Ranges.Parameterized = parseRange(r, []byte(`"parameterized"`)) } } } func parseRange(data, key []byte) Range { idx := indexBytes(data, key) if idx < 0 { return Range{} } d := data[idx+len(key):] for len(d) > 0 && d[0] != '{' { d = d[1:] } if len(d) == 0 { return Range{} } end := matchBrace(d) obj := d[:end+1] return Range{ Start: getIntField(obj, []byte(`"start"`)), End: getIntField(obj, []byte(`"end"`)), Description: string(getStringField(obj, []byte(`"description"`))), } } func parseKindInfoObj(data []byte) (KindInfo, []byte) { if len(data) == 0 || data[0] != '{' { return KindInfo{}, nil } end := matchBrace(data) obj := data[1:end] rest := data[end+1:] info := KindInfo{ Name: string(getStringField(obj, []byte(`"name"`))), Description: string(getStringField(obj, []byte(`"description"`))), Classification: string(getStringField(obj, []byte(`"classification"`))), DeprecatedBy: string(getStringField(obj, []byte(`"deprecatedBy"`))), Spec: string(getStringField(obj, []byte(`"spec"`))), RangeEnd: getIntField(obj, []byte(`"rangeEnd"`)), } nip := getStringField(obj, []byte(`"nip"`)) if nip != nil { s := string(nip) info.NIP = &s } if indexBytes(obj, []byte(`"deprecated":true`)) >= 0 || indexBytes(obj, []byte(`"deprecated": true`)) >= 0 { info.Deprecated = true } return info, rest } func matchBrace(data []byte) int { depth := 0 inStr := false for i := 0; i < len(data); i++ { if inStr { if data[i] == '\\' { i++ } else if data[i] == '"' { inStr = false } continue } switch data[i] { case '"': inStr = true case '{': depth++ case '}': depth-- if depth == 0 { return i } } } return len(data) - 1 } func getStringField(obj, key []byte) []byte { idx := indexBytes(obj, key) if idx < 0 { return nil } after := obj[idx+len(key):] // skip : and whitespace for len(after) > 0 && (after[0] == ':' || after[0] == ' ' || after[0] == '\t') { after = after[1:] } if len(after) == 0 || after[0] != '"' { return nil } v, _ := parseQuoted(after) return v } func getIntField(obj, key []byte) int { idx := indexBytes(obj, key) if idx < 0 { return 0 } after := obj[idx+len(key):] for len(after) > 0 && (after[0] == ':' || after[0] == ' ' || after[0] == '\t') { after = after[1:] } n := 0 for len(after) > 0 && after[0] >= '0' && after[0] <= '9' { n = n*10 + int(after[0]-'0') after = after[1:] } return n } func parseQuoted(data []byte) ([]byte, []byte) { if len(data) == 0 || data[0] != '"' { return nil, nil } for i := 1; i < len(data); i++ { if data[i] == '\\' { i++ continue } if data[i] == '"' { return data[1:i], data[i+1:] } } return nil, nil } func indexBytes(data, sep []byte) int { for i := 0; i <= len(data)-len(sep); i++ { match := true for j := 0; j < len(sep); j++ { if data[i+j] != sep[j] { match = false break } } if match { return i } } return -1 } func skipWS(data []byte) []byte { for len(data) > 0 && (data[0] == ' ' || data[0] == '\t' || data[0] == '\n' || data[0] == '\r') { data = data[1:] } return data } func atoi(b []byte) int { if len(b) == 0 { return -1 } n := 0 for _, c := range b { if c < '0' || c > '9' { return -1 } n = n*10 + int(c-'0') } return n } func GetKindData() *KindData { return kindData } func GetKindInfo(k uint16) (KindInfo, bool) { kStr := itoa(int(k)) info, ok := kindData.Kinds[kStr] return info, ok } func itoa(n int) string { if n == 0 { return "0" } var digits []byte negative := n < 0 if negative { n = -n } for n > 0 { digits = append([]byte{byte('0' + n%10)}, digits...) n /= 10 } if negative { digits = append([]byte{'-'}, digits...) } return string(digits) } func GetDescription(k uint16) string { if info, ok := GetKindInfo(k); ok { return info.Description } return "" } func GetNIP(k uint16) string { if info, ok := GetKindInfo(k); ok && info.NIP != nil { return *info.NIP } return "" } func IsDeprecated(k uint16) bool { if info, ok := GetKindInfo(k); ok { return info.Deprecated } return false } func GetClassification(k uint16) string { if info, ok := GetKindInfo(k); ok && info.Classification != "" { return info.Classification } if k >= uint16(kindData.Ranges.Parameterized.Start) && k <= uint16(kindData.Ranges.Parameterized.End) { return "parameterized" } if k >= uint16(kindData.Ranges.Ephemeral.Start) && k < uint16(kindData.Ranges.Ephemeral.End) { return "ephemeral" } if k >= uint16(kindData.Ranges.Replaceable.Start) && k < uint16(kindData.Ranges.Replaceable.End) { return "replaceable" } if k >= uint16(kindData.Ranges.Regular.Start) && k <= uint16(kindData.Ranges.Regular.End) { return "regular" } if k == 0 || k == 3 { return "replaceable" } return "regular" }