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