From 096216579d529cd33f1a9aedbe235f466460ab83 Mon Sep 17 00:00:00 2001 From: Paolo Donadeo Date: Thu, 9 Jul 2026 23:54:47 +0200 Subject: [PATCH] fix(parser): warn instead of silently dropping tasks with invalid due dates A malformed due: value (e.g. due:2024-13-99) left DueDate nil, causing ToRemind to silently emit nothing for that task. Now ParseLine reports an error so the problem surfaces on stderr instead of vanishing. Also fixes staticcheck QF1012 (WriteString(Sprintf(...)) -> Fprintf) flagged by the IDE in ToRemind. --- internal/parser/parser.go | 30 +++++++++++++++++++----------- test.todo.txt | 3 +++ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/internal/parser/parser.go b/internal/parser/parser.go index 6151333..ac43c12 100644 --- a/internal/parser/parser.go +++ b/internal/parser/parser.go @@ -44,10 +44,10 @@ func (t Task) ToRemind(indent int) string { // k must have the first letter uppercase for Remind kUpper := strings.ToUpper(k[:1]) + k[1:] if len(values) == 1 { - sb.WriteString(fmt.Sprintf("INFO \"%s: %s\" ", kUpper, values[0])) + fmt.Fprintf(&sb, "INFO \"%s: %s\" ", kUpper, values[0]) } else { for i, v := range values { - sb.WriteString(fmt.Sprintf("INFO \"%s%d: %s\" ", kUpper, i+1, v)) + fmt.Fprintf(&sb, "INFO \"%s%d: %s\" ", kUpper, i+1, v) } } } @@ -55,22 +55,22 @@ func (t Task) ToRemind(indent int) string { if len(t.Projects) > 0 { for _, p := range t.Projects { - sb.WriteString(fmt.Sprintf("INFO \"List: %s\" ", p)) + fmt.Fprintf(&sb, "INFO \"List: %s\" ", p) } } if len(t.Contexts) > 0 { for _, c := range t.Contexts { - sb.WriteString(fmt.Sprintf("INFO \"Tag: %s\" ", c)) + fmt.Fprintf(&sb, "INFO \"Tag: %s\" ", c) } } if t.Completed { - sb.WriteString(fmt.Sprintf("COMPLETE-THROUGH %s ", t.DueDate.Format("2006-01-02"))) + fmt.Fprintf(&sb, "COMPLETE-THROUGH %s ", t.DueDate.Format("2006-01-02")) } if t.Priority != nil { - sb.WriteString(fmt.Sprintf("PRIORITY %d ", t.PriorityAsRemind())) + fmt.Fprintf(&sb, "PRIORITY %d ", t.PriorityAsRemind()) } sb.WriteString("MSG") @@ -79,7 +79,7 @@ func (t Task) ToRemind(indent int) string { sb.WriteString(" %:") } - sb.WriteString(fmt.Sprintf(" %s", t.Description)) + fmt.Fprintf(&sb, " %s", t.Description) if t.Completed { sb.WriteString("%:") @@ -97,11 +97,11 @@ func (t Task) ToRemind(indent int) string { // uppercase first letter for Remind kUpper := strings.ToUpper(k[:1]) + k[1:] if len(values) == 1 { - sb.WriteString(fmt.Sprintf("%%_%s%s: %%<%s>", headingSpaces, kUpper, kUpper)) + fmt.Fprintf(&sb, "%%_%s%s: %%<%s>", headingSpaces, kUpper, kUpper) } else { for i := range values { kNumbered := fmt.Sprintf("%s%d", kUpper, i+1) - sb.WriteString(fmt.Sprintf("%%_%s%s: %%<%s>", headingSpaces, kNumbered, kNumbered)) + fmt.Fprintf(&sb, "%%_%s%s: %%<%s>", headingSpaces, kNumbered, kNumbered) } } } @@ -202,6 +202,7 @@ func ParseLine(line string) (Task, error) { working := strings.TrimSpace(line) toks := splitTokens(working) i := 0 + var invalidDue string // 1) Completed? if i < len(toks) && toks[i] == "x" { @@ -285,9 +286,12 @@ func ParseLine(line string) (Task, error) { k, v := parts[0], parts[1] if k != "" && v != "" && !isProtocolKey(k) { t.Metadata[k] = append(t.Metadata[k], v) - if k == "due" && dateRe.MatchString(v) { - if dt, err := time.Parse(dateLayout, v); err == nil { + if k == "due" { + dt, err := time.Parse(dateLayout, v) + if dateRe.MatchString(v) && err == nil { t.DueDate = &dt + } else { + invalidDue = v } } continue @@ -302,6 +306,10 @@ func ParseLine(line string) (Task, error) { return t, errors.New("completed task missing completion date (spec requires completion date directly after 'x')") } + if invalidDue != "" { + return t, fmt.Errorf("invalid due date %q: expected format YYYY-MM-DD (task will be dropped, no Remind entry generated)", invalidDue) + } + return t, nil } diff --git a/test.todo.txt b/test.todo.txt index 385f81d..1f33379 100644 --- a/test.todo.txt +++ b/test.todo.txt @@ -73,6 +73,9 @@ X 2012-01-01 Make resolutions (A) 2024-13-99 Call Mom x 2024-99-99 Call Mom +# Malformed: due date with invalid format (should error, task dropped from output) +(A) Call Mom due:2024-13-99 + # Malformed: key:value with whitespace in key or value foo bar:baz foo:bar baz