2024-01-26 19:40:38 -06:00
|
|
|
package models
|
2024-01-26 20:00:03 -06:00
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"encoding/json"
|
2024-01-27 16:07:27 -06:00
|
|
|
"errors"
|
2024-01-26 20:00:03 -06:00
|
|
|
"fmt"
|
|
|
|
|
"regexp"
|
|
|
|
|
"strconv"
|
2024-01-27 16:07:27 -06:00
|
|
|
"strings"
|
2024-01-26 20:00:03 -06:00
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type Meta struct {
|
|
|
|
|
Key string
|
|
|
|
|
Value any
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m Meta) MarshalText() ([]byte, error) {
|
|
|
|
|
if regexp.MustCompile(`\s`).MatchString(m.Key) {
|
2024-01-27 16:07:27 -06:00
|
|
|
return []byte{}, fmt.Errorf("whitespace is not allowed in key: %s", m.Key)
|
2024-01-26 20:00:03 -06:00
|
|
|
}
|
|
|
|
|
buff := &bytes.Buffer{}
|
|
|
|
|
buff.WriteRune('@')
|
|
|
|
|
buff.WriteString(m.Key)
|
|
|
|
|
buff.WriteRune(' ')
|
|
|
|
|
switch v := m.Value.(type) {
|
|
|
|
|
default:
|
|
|
|
|
return nil, fmt.Errorf("Unknown type %T", v)
|
|
|
|
|
case nil:
|
|
|
|
|
return []byte{}, nil
|
|
|
|
|
case string:
|
|
|
|
|
buff.WriteString(v)
|
|
|
|
|
case int:
|
|
|
|
|
buff.WriteString(strconv.Itoa(v))
|
|
|
|
|
case float64:
|
|
|
|
|
buff.WriteString(strconv.FormatFloat(v, 'f', -1, 64))
|
|
|
|
|
case json.Number:
|
|
|
|
|
buff.WriteString(v.String())
|
|
|
|
|
case json.RawMessage:
|
|
|
|
|
buff.Write(v)
|
|
|
|
|
case []byte:
|
|
|
|
|
buff.Write(v)
|
|
|
|
|
case byte:
|
|
|
|
|
buff.WriteByte(v)
|
|
|
|
|
case rune:
|
|
|
|
|
buff.WriteString(string(v))
|
|
|
|
|
case bool:
|
|
|
|
|
buff.WriteString(strconv.FormatBool(v))
|
|
|
|
|
case time.Time:
|
|
|
|
|
buff.WriteString(v.Format(time.RFC3339))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return buff.Bytes(), nil
|
|
|
|
|
}
|
2024-01-27 16:07:27 -06:00
|
|
|
|
|
|
|
|
func (m *Meta) UnmarshalText(in []byte) error {
|
|
|
|
|
if len(in) == 0 {
|
|
|
|
|
return errors.New("Unable to Unmarshal empty string")
|
|
|
|
|
}
|
|
|
|
|
re := regexp.MustCompile("(?s)^@([^ ]+) (.*)( @end)?$")
|
|
|
|
|
match := re.FindSubmatch(in)
|
|
|
|
|
if len(match) == 0 {
|
|
|
|
|
return fmt.Errorf("Failed to match %s", in)
|
|
|
|
|
}
|
|
|
|
|
m.Key = string(match[1])
|
|
|
|
|
return m.processMeta(match[2])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m *Meta) processMeta(in []byte) error {
|
|
|
|
|
if len(in) == 0 {
|
|
|
|
|
return errors.New("No value found")
|
|
|
|
|
}
|
|
|
|
|
s := strings.TrimSpace(string(in))
|
|
|
|
|
if len(s) == 0 {
|
|
|
|
|
return errors.New("No value found")
|
|
|
|
|
}
|
|
|
|
|
yesno := regexp.MustCompile("^(y|Y|yes|Yes|YES|n|N|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF)$")
|
|
|
|
|
yes := regexp.MustCompile("^(y|Y|yes|Yes|YES|true|True|TRUE|on|On|ON)$")
|
|
|
|
|
null := regexp.MustCompile("^(~|null|Null|NULL|none|None|NONE|nil|Nil|NIL)$")
|
|
|
|
|
var j json.RawMessage
|
|
|
|
|
if null.MatchString(s) {
|
|
|
|
|
m.Value = nil
|
|
|
|
|
} else if yesno.MatchString(s) {
|
|
|
|
|
if yes.MatchString(s) {
|
|
|
|
|
m.Value = true
|
|
|
|
|
} else {
|
|
|
|
|
m.Value = false
|
|
|
|
|
}
|
|
|
|
|
} else if i, err := strconv.Atoi(s); err == nil {
|
|
|
|
|
m.Value = i
|
|
|
|
|
} else if f, err := strconv.ParseFloat(s, 64); err == nil {
|
|
|
|
|
m.Value = f
|
|
|
|
|
} else if t, err := time.Parse(time.RFC3339, s); err == nil {
|
|
|
|
|
m.Value = t
|
|
|
|
|
} else if err := json.Unmarshal(in, &j); err == nil {
|
|
|
|
|
m.Value = j
|
|
|
|
|
} else {
|
|
|
|
|
m.Value = s
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|