refactor(parser): simplify ParseLine logic for completed, priority, and date parsing

- Refactor ParseLine to use token-based parsing for completed status,
  priority, and dates.
- Improve handling of priority and creation date for both completed and
  incomplete tasks.
This commit is contained in:
2025-11-25 00:41:36 +01:00
parent 57ac41ff72
commit 5ef3057d98

View File

@@ -64,11 +64,9 @@ func (t Task) MarshalJSON() ([]byte, error) {
} }
var ( var (
dateRe = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}$`) dateRe = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}$`)
priorityHead = regexp.MustCompile(`^\([A-Z]\)\s+`) spaceRe = regexp.MustCompile(`\s+`)
completedHead = regexp.MustCompile(`^x\s+`) dateLayout = "2006-01-02"
spaceRe = regexp.MustCompile(`\s+`)
dateLayout = "2006-01-02"
// Protocols to exclude as metadata keys // Protocols to exclude as metadata keys
protocols = map[string]struct{}{ protocols = map[string]struct{}{
"http": {}, "http": {},
@@ -119,48 +117,56 @@ func ParseLine(line string) (Task, error) {
} }
working := strings.TrimSpace(line) working := strings.TrimSpace(line)
toks := splitTokens(working)
i := 0
// 1) Completed? // 1) Completed?
if completedHead.MatchString(working) { if i < len(toks) && toks[i] == "x" {
t.Completed = true t.Completed = true
working = completedHead.ReplaceAllString(working, "") i++
toks := splitTokens(working) // 2) Completion date
consumed := 0 if i < len(toks) && dateRe.MatchString(toks[i]) {
if len(toks) > 0 && dateRe.MatchString(toks[0]) { if dt, err := time.Parse(dateLayout, toks[i]); err == nil {
if dt, err := time.Parse(dateLayout, toks[0]); err == nil {
t.CompletionDate = &dt t.CompletionDate = &dt
consumed = 1 i++
} }
} }
if consumed < len(toks) && dateRe.MatchString(toks[consumed]) { // 3) Creation date
if dt, err := time.Parse(dateLayout, toks[consumed]); err == nil { if i < len(toks) && dateRe.MatchString(toks[i]) {
if dt, err := time.Parse(dateLayout, toks[i]); err == nil {
t.CreationDate = &dt t.CreationDate = &dt
consumed++ i++
}
}
if consumed > 0 {
working = strings.TrimSpace(strings.Join(toks[consumed:], " "))
}
} else {
if priorityHead.MatchString(working) {
if len(working) >= 3 {
r := rune(working[1])
t.Priority = &r
}
working = priorityHead.ReplaceAllString(working, "")
}
toks := splitTokens(working)
if len(toks) > 0 && dateRe.MatchString(toks[0]) {
if dt, err := time.Parse(dateLayout, toks[0]); err == nil {
t.CreationDate = &dt
working = strings.TrimSpace(strings.Join(toks[1:], " "))
} }
} }
} }
tokens := splitTokens(working) // 4) Priority (can appear after x, dates)
descParts := make([]string, 0, len(tokens)) if i < len(toks) && len(toks[i]) == 3 && toks[i][0] == '(' && toks[i][2] == ')' && toks[i][1] >= 'A' && toks[i][1] <= 'Z' {
for _, tok := range tokens { r := rune(toks[i][1])
t.Priority = &r
i++
}
// 5) If not completed, check for priority at start
if !t.Completed {
if i < len(toks) && len(toks[i]) == 3 && toks[i][0] == '(' && toks[i][2] == ')' && toks[i][1] >= 'A' && toks[i][1] <= 'Z' {
r := rune(toks[i][1])
t.Priority = &r
i++
}
// Creation date for incomplete tasks
if i < len(toks) && dateRe.MatchString(toks[i]) {
if dt, err := time.Parse(dateLayout, toks[i]); err == nil {
t.CreationDate = &dt
i++
}
}
}
// 6) Parse remaining tokens
descParts := make([]string, 0)
for ; i < len(toks); i++ {
tok := toks[i]
if strings.HasPrefix(tok, "+") && len(tok) > 1 { if strings.HasPrefix(tok, "+") && len(tok) > 1 {
t.Projects = append(t.Projects, tok[1:]) t.Projects = append(t.Projects, tok[1:])
continue continue
@@ -174,7 +180,6 @@ func ParseLine(line string) (Task, error) {
k, v := parts[0], parts[1] k, v := parts[0], parts[1]
if k != "" && v != "" && !isProtocolKey(k) { if k != "" && v != "" && !isProtocolKey(k) {
t.Metadata[k] = v t.Metadata[k] = v
// If the field is due:YYYY-MM-DD, also parse it as DueDate
if k == "due" && dateRe.MatchString(v) { if k == "due" && dateRe.MatchString(v) {
if dt, err := time.Parse(dateLayout, v); err == nil { if dt, err := time.Parse(dateLayout, v); err == nil {
t.DueDate = &dt t.DueDate = &dt
@@ -183,7 +188,6 @@ func ParseLine(line string) (Task, error) {
continue continue
} }
} }
// else part of description
descParts = append(descParts, tok) descParts = append(descParts, tok)
} }
t.Description = strings.TrimSpace(strings.Join(descParts, " ")) t.Description = strings.TrimSpace(strings.Join(descParts, " "))