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.
This commit is contained in:
2026-07-09 23:54:47 +02:00
parent d692fa5a4c
commit 096216579d
2 changed files with 22 additions and 11 deletions

View File

@@ -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(" %<List>:")
}
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
}

View File

@@ -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