Functional Weave
Code in Rust

todo.parse-quick-add@1.0.0

README.md

4,716 bytes · view raw

# todo.parse-quick-add

Turns one line typed into a quick-add box into a `TodoDraft` (todo.item):

`Pay rent tomorrow !high #home every month`, typed on Monday 2026-09-28, is
title `Pay rent`, due 2026-09-29, priority `high`, tags `["home"]`, repeating
monthly (interval 1) from 2026-09-29.

`today` is the person's local date, passed in rather than read from the clock.
The function only throws when `today` is not a real ISO date
(dates.add-days's message, e.g. `"28/09/2026" is not an ISO date`). The draft
is not validated: pass it to todo.item's `validateDraft`, which says
"Enter a title." when the line held only tokens.

## How a line is read

The line is split into words on ASCII whitespace (space, tab, CR, LF, VT, FF)
and scanned left to right. At each word the first rule that applies takes it
(and, for phrases, the words after it); a word no rule takes stays in the
title. Keywords are matched case-insensitively, ASCII letters only.

| Order | Token | Meaning | Only once? |
| --- | --- | --- | --- |
| 1 | `every day(s)`, `every week(s)`, `every month(s)`, `every year(s)` | repeat daily / weekly / monthly / yearly, interval 1 | first repeat wins |
| 1 | `every weekday(s)` | repeat on weekdays (Monday to Friday) | |
| 1 | `every <weekday>` (any name todo.parse-date-phrase knows: `monday`, `mon`, `tues`, ...) | repeat weekly, interval 1 | |
| 1 | `every other day/week/month/year` | interval 2 | |
| 1 | `every N days/weeks/months/years`, N = 1 to 999 in digits (no leading zero) or `one` ... `ten` | interval N | |
| 2 | `!high`, `!h`, `!1`, `!!!` | priority high | first priority wins |
| 2 | `!medium`, `!med`, `!m`, `!2`, `!!` | priority medium | |
| 2 | `!low`, `!l`, `!3` | priority low | |
| 2 | `!none` | priority none (the default anyway) | |
| 3 | `#tag`: `#` then at least one character, and not empty once normalised | a tag, normalised by todo.normalise-tags (`#Home-Office` is `home-office`); repeats merge | every one |
| 4 | a date phrase of 1 to 4 words that todo.parse-date-phrase reads as a whole: `tomorrow`, `friday`, `next week`, `in 3 days`, `on 2026-10-01`, `due in 3 days` ... | the due date | first date phrase wins |
| - | anything else | a title word | |

For a date phrase the longest run wins: 4 words are tried, then 3, 2 and 1.
Four, not three, because `due in 3 days` is the longest phrase
todo.parse-date-phrase accepts.

After the scan:

- **title**: the words left over, in order, joined by single spaces, so it is
  trimmed and inner runs of spaces collapse. It may be empty.
- **priority**: `none` unless a priority token was found. **notes**: null.
  **tags**: `[]` when there are none.
- **due**: the date phrase's date. With a repeat and no date phrase, it is
  today; for `every weekday` today if it is Monday to Friday, else next
  Monday; for `every <weekday>` the next such day strictly after today (so
  `every monday` typed on a Monday is due next Monday, as `monday` is).
- **recurrence**: null without a repeat; otherwise the repeat's frequency and
  interval, anchored on the final due date. A date phrase beats
  `every <weekday>` for the due date, and the series then runs weekly from
  that date.

## Traps it avoids

- **Short words that are also English.** On their own, the 3- and 4-letter
  weekday abbreviations (`mon`, `tue`, `tues`, `wed`, `thu`, `thur`, `thurs`,
  `fri`, `sat`, `sun`), `tod` and `weekend` stay in the title: `Read the sun`
  and `Plan weekend trip` keep every word. After `on` or `due` they count:
  `Picnic on sun` is due Sunday. Full weekday names, `today`, `tomorrow`,
  `tmr`, `tmrw`, `yesterday` and ISO dates count on their own. Phrases of two
  or more words always count (`Plan next week` is due next Monday), so
  `Sit on sun lounger` does lose `on sun`: the price of `on sun` working.
- **Repeat words in titles.** Only the `every ...` forms repeat, so
  `Write weekly report` and `Yearly review` keep `weekly` and `Yearly`.
  `daily`, `weekly`, `monthly` and `yearly` on their own are title words.
- **Near misses stay in the title as typed**: a lone `#` or `!`, `every` with
  nothing it understands after it, `in 3 lightyears`, `every 1000 days`,
  `2026-02-30`, `next week's`, an unknown `!urgent`, and a tag such as
  `#日本` that normalises to nothing (text.slugify keeps ASCII and Latin-1
  letters only). A lone `!` is never a priority: it is usually punctuation.
- **Second tokens are not dropped.** Only the first date phrase, priority and
  repeat are used; later ones stay in the title (`!low !high Fix bug` is
  priority low, title `!high Fix bug`) rather than silently vanishing.
- **Month ends and leap days** come from todo.parse-date-phrase:
  `in 1 month` on 31 January is 28 February.