Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import re
from typing import List, Optional, Tuple
from .dates_add_days import add_days ← from dates.add-days ^1.0.0 · built alongside by fune
from .dates_day_of_week import day_of_week ← from dates.day-of-week ^1.0.0 · built alongside by fune
from .todo_item import Recurrence, TodoDraft ← from todo.item ^1.0.0 · built alongside by fune
from .todo_normalise_tags import normalise_tags ← from todo.normalise-tags ^1.0.0 · built alongside by fune
from .todo_parse_date_phrase import parse_date_phrase ← from todo.parse-date-phrase ^1.0.0 · built alongside by fune
_PRIORITIES = {
"!high": "high", "!h": "high", "!1": "high", "!!!": "high",
"!medium": "medium", "!med": "medium", "!m": "medium", "!2": "medium", "!!": "medium",
"!low": "low", "!l": "low", "!3": "low",
"!none": "none",
}
# Short words that are also ordinary title words ("Read the sun"): on their
# own they stay in the title; "on sun" or "due sat" still sets the date.
_GUARDED = {"mon", "tue", "tues", "wed", "thu", "thur", "thurs", "fri", "sat", "sun", "tod", "weekend"}
_WEEKDAY_WORDS = {
"monday", "mon", "tuesday", "tue", "tues", "wednesday", "wed", "thursday", "thu", "thur", "thurs",
"friday", "fri", "saturday", "sat", "sunday", "sun",
}
_UNIT_FREQUENCIES = {
"day": "daily", "days": "daily", "week": "weekly", "weeks": "weekly",
"month": "monthly", "months": "monthly", "year": "yearly", "years": "yearly",
}
_NUMBER_WORDS = {"one": 1, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 8, "nine": 9, "ten": 10}
# The longest phrase todo.parse-date-phrase accepts: "due in 3 days".
_MAX_DATE_WORDS = 4
_SPACES = re.compile(r"[ \t\n\r\x0b\x0c]+")
_COUNT = re.compile(r"[1-9][0-9]{0,2}")
# (frequency, interval, weekday word or None, words used)
_Repeat = Tuple[str, int, Optional[str], int]
def _lower_ascii(text: str) -> str:
return "".join(chr(ord(c) + 32) if "A" <= c <= "Z" else c for c in text)
def _count(word: str) -> Optional[int]:
if _COUNT.fullmatch(word):
return int(word)
return _NUMBER_WORDS.get(word)
def _repeat_at(words: List[str], i: int) -> Optional[_Repeat]:
"""The repeat phrase starting with "every" at words[i], or None."""
if i + 1 >= len(words):
return None
a = _lower_ascii(words[i + 1])
if a in ("weekday", "weekdays"):
return ("weekdays", 1, None, 2)
if a in _UNIT_FREQUENCIES:
return (_UNIT_FREQUENCIES[a], 1, None, 2)
if a in _WEEKDAY_WORDS:
return ("weekly", 1, a, 2)
if i + 2 >= len(words):
return None
every = _UNIT_FREQUENCIES.get(_lower_ascii(words[i + 2]))
if every is None:
return None
n = 2 if a == "other" else _count(a)
return None if n is None else (every, n, None, 3)
def parse_quick_add(text: str, today: str) -> TodoDraft:
"""One typed line into a TodoDraft: #tags, a !priority, the first date
phrase and the first "every ..." repeat come out; every other word stays in
the title, in order, single-spaced. The title can come out empty."""
# add_days(x, 0) is the date check: it raises dates.add-days's message.
add_days(today, 0)
words = [w for w in _SPACES.split(text) if w != ""]
title: List[str] = []
tag_words: List[str] = []
priority: Optional[str] = None
due: Optional[str] = None
repeat: Optional[_Repeat] = None
i = 0
while i < len(words):
word = words[i]
lower = _lower_ascii(word)
if repeat is None and lower == "every":
found = _repeat_at(words, i)
if found is not None:
repeat = found
i += found[3]
continue
if priority is None and lower in _PRIORITIES:
priority = _PRIORITIES[lower]
i += 1
continue
if len(word) > 1 and word[0] == "#" and len(normalise_tags([word])) > 0:
tag_words.append(word)
i += 1
continue
if due is None:
taken = 0
for length in range(min(_MAX_DATE_WORDS, len(words) - i), 0, -1):
if length == 1 and lower in _GUARDED:
continue
date = parse_date_phrase(" ".join(words[i:i + length]), today)
if date is not None:
due = date
taken = length
break
if taken > 0:
i += taken
continue
title.append(word)
i += 1
recurrence: Optional[Recurrence] = None
if repeat is not None:
frequency, interval, weekday, _ = repeat
if due is None:
if frequency == "weekdays":
dow = day_of_week(today)
due = today if dow <= 5 else add_days(today, 8 - dow)
elif weekday is not None:
due = parse_date_phrase(weekday, today)
else:
due = today
recurrence = Recurrence(frequency=frequency, interval=interval, anchor=due)
return TodoDraft(
title=" ".join(title),
notes=None,
priority=priority if priority is not None else "none",
due=due,
tags=normalise_tags(tag_words),
recurrence=recurrence,
)