package models import ( "bytes" "encoding/json" "errors" "fmt" "regexp" "strconv" "strings" "time" ) type Meta struct { Key string Value any } func (m Meta) MarshalText() ([]byte, error) { if regexp.MustCompile(`\s`).MatchString(m.Key) { return []byte{}, fmt.Errorf("whitespace is not allowed in key: %s", m.Key) } 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 int64: buff.WriteString(strconv.FormatInt(v, 10)) 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 } func (m *Meta) UnmarshalText(in []byte) error { if len(in) == 0 { return newParsingError(errors.New("Unable to Unmarshal empty string")) } re := regexp.MustCompile("(?s)^@([^ ]+) (.*)( @end)?$") match := re.FindSubmatch(in) if len(match) == 0 { return newParsingError(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 newParsingError(errors.New("No value found")) } s := strings.TrimSpace(string(in)) if len(s) == 0 { return newParsingError(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 }