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

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