51cbf86d77
Extracts glyph prefix, @time anchors, #tags, and ^card suffixes from raw input. Table-driven tests cover all grammar forms including edge cases. 24 tests passing.
114 lines
2.2 KiB
Go
114 lines
2.2 KiB
Go
package parse
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type Result struct {
|
|
Body string
|
|
Glyph string
|
|
TimeAnchor *string
|
|
Tags []string
|
|
CardSuffix *string
|
|
}
|
|
|
|
var validCardTypes = map[string]string{
|
|
"card": "snippet",
|
|
"c": "snippet",
|
|
"snippet": "snippet",
|
|
"template": "template",
|
|
"checklist": "checklist",
|
|
"decision": "decision",
|
|
"link": "link",
|
|
}
|
|
|
|
func Parse(input string) (*Result, error) {
|
|
input = strings.TrimSpace(input)
|
|
if input == "" {
|
|
return nil, fmt.Errorf("empty input")
|
|
}
|
|
|
|
r := &Result{
|
|
Glyph: "note",
|
|
Tags: []string{},
|
|
}
|
|
|
|
tokens := strings.Fields(input)
|
|
if len(tokens) == 0 {
|
|
return nil, fmt.Errorf("empty input")
|
|
}
|
|
|
|
first := tokens[0]
|
|
switch first {
|
|
case "-", "▸":
|
|
r.Glyph = "todo"
|
|
tokens = tokens[1:]
|
|
case "*", "◇":
|
|
r.Glyph = "event"
|
|
tokens = tokens[1:]
|
|
}
|
|
|
|
var bodyParts []string
|
|
seen := map[string]bool{}
|
|
|
|
for _, tok := range tokens {
|
|
switch {
|
|
case strings.HasPrefix(tok, "@") && len(tok) > 1:
|
|
timeStr := tok[1:]
|
|
if err := validateTime(timeStr); err != nil {
|
|
return nil, fmt.Errorf("invalid time %q: %w", timeStr, err)
|
|
}
|
|
if r.TimeAnchor != nil {
|
|
return nil, fmt.Errorf("multiple time anchors")
|
|
}
|
|
r.TimeAnchor = &timeStr
|
|
|
|
case strings.HasPrefix(tok, "#") && len(tok) > 1:
|
|
tag := tok[1:]
|
|
if !seen[tag] {
|
|
r.Tags = append(r.Tags, tag)
|
|
seen[tag] = true
|
|
}
|
|
|
|
case strings.HasPrefix(tok, "^") && len(tok) > 1:
|
|
suffix := tok[1:]
|
|
cardType, ok := validCardTypes[suffix]
|
|
if !ok {
|
|
return nil, fmt.Errorf("invalid card type %q", suffix)
|
|
}
|
|
if r.CardSuffix != nil {
|
|
return nil, fmt.Errorf("multiple card suffixes")
|
|
}
|
|
r.CardSuffix = &cardType
|
|
|
|
default:
|
|
bodyParts = append(bodyParts, tok)
|
|
}
|
|
}
|
|
|
|
r.Body = strings.Join(bodyParts, " ")
|
|
if r.Body == "" {
|
|
return nil, fmt.Errorf("empty body after extracting modifiers")
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
func validateTime(s string) error {
|
|
parts := strings.SplitN(s, ":", 2)
|
|
if len(parts) != 2 {
|
|
return fmt.Errorf("expected HH:MM format")
|
|
}
|
|
h, err := strconv.Atoi(parts[0])
|
|
if err != nil || h < 0 || h > 23 {
|
|
return fmt.Errorf("hours must be 0-23")
|
|
}
|
|
m, err := strconv.Atoi(parts[1])
|
|
if err != nil || m < 0 || m > 59 {
|
|
return fmt.Errorf("minutes must be 0-59")
|
|
}
|
|
return nil
|
|
}
|