embed.mx raw
1 package kind
2
3 import (
4 _ "embed"
5 )
6
7 //go:embed kinds.json
8 var KindsJSON []byte
9
10 type KindData struct {
11 Version string `json:"version"`
12 Source string `json:"source"`
13 Ranges Ranges `json:"ranges"`
14 Kinds map[string]KindInfo `json:"kinds"`
15 Privileged []int `json:"privileged"`
16 Directory []int `json:"directory"`
17 Aliases map[string]int `json:"aliases"`
18 }
19
20 type Ranges struct {
21 Regular Range `json:"regular"`
22 Replaceable Range `json:"replaceable"`
23 Ephemeral Range `json:"ephemeral"`
24 Parameterized Range `json:"parameterized"`
25 }
26
27 type Range struct {
28 Start int `json:"start"`
29 End int `json:"end"`
30 Description string `json:"description"`
31 }
32
33 type KindInfo struct {
34 Name string `json:"name"`
35 NIP *string `json:"nip,omitempty"`
36 Description string `json:"description"`
37 Classification string `json:"classification,omitempty"`
38 Deprecated bool `json:"deprecated,omitempty"`
39 DeprecatedBy string `json:"deprecatedBy,omitempty"`
40 Spec string `json:"spec,omitempty"`
41 RangeEnd int `json:"rangeEnd,omitempty"`
42 }
43
44 var kindData *KindData
45
46 func init() {
47 kindData = &KindData{Kinds: map[string]KindInfo{}}
48 // Lightweight parse: extract kind entries from "kinds": { "N": {"name":"...",...}, ... }
49 // to populate Map and kindData.Kinds without encoding/json.
50 parseKindsJSON(KindsJSON)
51 }
52
53 // parseKindsJSON is a minimal parser that extracts kind data from the
54 // embedded JSON. Avoids encoding/json which crashes under moxie's runtime.
55 func parseKindsJSON(data []byte) {
56 // Find "kinds": {
57 kindsKey := []byte(`"kinds"`)
58 idx := indexBytes(data, kindsKey)
59 if idx < 0 {
60 return
61 }
62 data = data[idx+len(kindsKey):]
63 // Skip to opening brace.
64 for len(data) > 0 && data[0] != '{' {
65 data = data[1:]
66 }
67 if len(data) == 0 {
68 return
69 }
70 data = data[1:] // skip {
71
72 // Parse each "N": { ... } entry.
73 for len(data) > 0 {
74 data = skipWS(data)
75 if len(data) == 0 || data[0] == '}' {
76 break
77 }
78 if data[0] == ',' {
79 data = data[1:]
80 continue
81 }
82 // Parse key (kind number as string).
83 kStr, rest := parseQuoted(data)
84 if rest == nil {
85 break
86 }
87 data = rest
88 data = skipWS(data)
89 if len(data) == 0 || data[0] != ':' {
90 break
91 }
92 data = data[1:]
93 data = skipWS(data)
94
95 // Parse value object.
96 info, rest := parseKindInfoObj(data)
97 if rest == nil {
98 break
99 }
100 data = rest
101
102 kindData.Kinds[string(kStr)] = info
103 k := atoi(kStr)
104 if k >= 0 {
105 Map[uint16(k)] = info.Name
106 }
107 }
108
109 // Parse ranges.
110 rangesKey := []byte(`"ranges"`)
111 idx = indexBytes(KindsJSON, rangesKey)
112 if idx >= 0 {
113 r := KindsJSON[idx+len(rangesKey):]
114 for len(r) > 0 && r[0] != '{' {
115 r = r[1:]
116 }
117 if len(r) > 0 {
118 r = r[1:]
119 kindData.Ranges.Regular = parseRange(r, []byte(`"regular"`))
120 kindData.Ranges.Replaceable = parseRange(r, []byte(`"replaceable"`))
121 kindData.Ranges.Ephemeral = parseRange(r, []byte(`"ephemeral"`))
122 kindData.Ranges.Parameterized = parseRange(r, []byte(`"parameterized"`))
123 }
124 }
125 }
126
127 func parseRange(data, key []byte) Range {
128 idx := indexBytes(data, key)
129 if idx < 0 {
130 return Range{}
131 }
132 d := data[idx+len(key):]
133 for len(d) > 0 && d[0] != '{' {
134 d = d[1:]
135 }
136 if len(d) == 0 {
137 return Range{}
138 }
139 end := matchBrace(d)
140 obj := d[:end+1]
141 return Range{
142 Start: getIntField(obj, []byte(`"start"`)),
143 End: getIntField(obj, []byte(`"end"`)),
144 Description: string(getStringField(obj, []byte(`"description"`))),
145 }
146 }
147
148 func parseKindInfoObj(data []byte) (KindInfo, []byte) {
149 if len(data) == 0 || data[0] != '{' {
150 return KindInfo{}, nil
151 }
152 end := matchBrace(data)
153 obj := data[1:end]
154 rest := data[end+1:]
155
156 info := KindInfo{
157 Name: string(getStringField(obj, []byte(`"name"`))),
158 Description: string(getStringField(obj, []byte(`"description"`))),
159 Classification: string(getStringField(obj, []byte(`"classification"`))),
160 DeprecatedBy: string(getStringField(obj, []byte(`"deprecatedBy"`))),
161 Spec: string(getStringField(obj, []byte(`"spec"`))),
162 RangeEnd: getIntField(obj, []byte(`"rangeEnd"`)),
163 }
164 nip := getStringField(obj, []byte(`"nip"`))
165 if nip != nil {
166 s := string(nip)
167 info.NIP = &s
168 }
169 if indexBytes(obj, []byte(`"deprecated":true`)) >= 0 || indexBytes(obj, []byte(`"deprecated": true`)) >= 0 {
170 info.Deprecated = true
171 }
172 return info, rest
173 }
174
175 func matchBrace(data []byte) int {
176 depth := 0
177 inStr := false
178 for i := 0; i < len(data); i++ {
179 if inStr {
180 if data[i] == '\\' {
181 i++
182 } else if data[i] == '"' {
183 inStr = false
184 }
185 continue
186 }
187 switch data[i] {
188 case '"':
189 inStr = true
190 case '{':
191 depth++
192 case '}':
193 depth--
194 if depth == 0 {
195 return i
196 }
197 }
198 }
199 return len(data) - 1
200 }
201
202 func getStringField(obj, key []byte) []byte {
203 idx := indexBytes(obj, key)
204 if idx < 0 {
205 return nil
206 }
207 after := obj[idx+len(key):]
208 // skip : and whitespace
209 for len(after) > 0 && (after[0] == ':' || after[0] == ' ' || after[0] == '\t') {
210 after = after[1:]
211 }
212 if len(after) == 0 || after[0] != '"' {
213 return nil
214 }
215 v, _ := parseQuoted(after)
216 return v
217 }
218
219 func getIntField(obj, key []byte) int {
220 idx := indexBytes(obj, key)
221 if idx < 0 {
222 return 0
223 }
224 after := obj[idx+len(key):]
225 for len(after) > 0 && (after[0] == ':' || after[0] == ' ' || after[0] == '\t') {
226 after = after[1:]
227 }
228 n := 0
229 for len(after) > 0 && after[0] >= '0' && after[0] <= '9' {
230 n = n*10 + int(after[0]-'0')
231 after = after[1:]
232 }
233 return n
234 }
235
236 func parseQuoted(data []byte) ([]byte, []byte) {
237 if len(data) == 0 || data[0] != '"' {
238 return nil, nil
239 }
240 for i := 1; i < len(data); i++ {
241 if data[i] == '\\' {
242 i++
243 continue
244 }
245 if data[i] == '"' {
246 return data[1:i], data[i+1:]
247 }
248 }
249 return nil, nil
250 }
251
252 func indexBytes(data, sep []byte) int {
253 for i := 0; i <= len(data)-len(sep); i++ {
254 match := true
255 for j := 0; j < len(sep); j++ {
256 if data[i+j] != sep[j] {
257 match = false
258 break
259 }
260 }
261 if match {
262 return i
263 }
264 }
265 return -1
266 }
267
268 func skipWS(data []byte) []byte {
269 for len(data) > 0 && (data[0] == ' ' || data[0] == '\t' || data[0] == '\n' || data[0] == '\r') {
270 data = data[1:]
271 }
272 return data
273 }
274
275 func atoi(b []byte) int {
276 if len(b) == 0 {
277 return -1
278 }
279 n := 0
280 for _, c := range b {
281 if c < '0' || c > '9' {
282 return -1
283 }
284 n = n*10 + int(c-'0')
285 }
286 return n
287 }
288
289 func GetKindData() *KindData { return kindData }
290
291 func GetKindInfo(k uint16) (KindInfo, bool) {
292 kStr := itoa(int(k))
293 info, ok := kindData.Kinds[kStr]
294 return info, ok
295 }
296
297 func itoa(n int) string {
298 if n == 0 {
299 return "0"
300 }
301 var digits []byte
302 negative := n < 0
303 if negative {
304 n = -n
305 }
306 for n > 0 {
307 digits = append([]byte{byte('0' + n%10)}, digits...)
308 n /= 10
309 }
310 if negative {
311 digits = append([]byte{'-'}, digits...)
312 }
313 return string(digits)
314 }
315
316 func GetDescription(k uint16) string {
317 if info, ok := GetKindInfo(k); ok {
318 return info.Description
319 }
320 return ""
321 }
322
323 func GetNIP(k uint16) string {
324 if info, ok := GetKindInfo(k); ok && info.NIP != nil {
325 return *info.NIP
326 }
327 return ""
328 }
329
330 func IsDeprecated(k uint16) bool {
331 if info, ok := GetKindInfo(k); ok {
332 return info.Deprecated
333 }
334 return false
335 }
336
337 func GetClassification(k uint16) string {
338 if info, ok := GetKindInfo(k); ok && info.Classification != "" {
339 return info.Classification
340 }
341 if k >= uint16(kindData.Ranges.Parameterized.Start) && k <= uint16(kindData.Ranges.Parameterized.End) {
342 return "parameterized"
343 }
344 if k >= uint16(kindData.Ranges.Ephemeral.Start) && k < uint16(kindData.Ranges.Ephemeral.End) {
345 return "ephemeral"
346 }
347 if k >= uint16(kindData.Ranges.Replaceable.Start) && k < uint16(kindData.Ranges.Replaceable.End) {
348 return "replaceable"
349 }
350 if k >= uint16(kindData.Ranges.Regular.Start) && k <= uint16(kindData.Ranges.Regular.End) {
351 return "regular"
352 }
353 if k == 0 || k == 3 {
354 return "replaceable"
355 }
356 return "regular"
357 }
358