import re from dataclasses import replace from typing import Optional from .dates_add_days import add_days from .dates_day_of_week import day_of_week from .todo_item import Recurrence from .todo_parse_date_phrase import parse_date_phrase from .todo_parse_quick_add import parse_quick_add _SPACE = re.compile(r"[ \t\n\r\x0b\x0c]+") def _lower_ascii(text: str) -> str: return "".join(c.lower() if "A" <= c <= "Z" else c for c in text) def parse_repeat(text: str, due: Optional[str], today: str) -> Optional[Recurrence]: """A Repeat box: an "every ..." phrase as todo.parse-quick-add reads it, then optionally a start date.""" # add_days(x, 0) is the date check: it raises dates.add-days's message. add_days(today, 0) if due is not None: add_days(due, 0) words = [w for w in _SPACE.split(text) if w != ""] if len(words) < 2 or _lower_ascii(words[0]) != "every": return None # The repeat is 2 words ("every week") or 3 ("every 2 weeks"); no 2-word # form starts a 3-word one, so the first that parses as a bare repeat wins. repeat: Optional[Recurrence] = None length = 0 for n in (2, 3): if n > len(words): break draft = parse_quick_add(" ".join(words[:n]), today) if draft.recurrence is not None and draft.title == "": repeat = draft.recurrence length = n break if repeat is None: return None start = due if len(words) > length: start = parse_date_phrase(" ".join(words[length:]), today) if start is None: return None if start is None: return repeat if repeat.frequency == "weekdays": day = day_of_week(start) if day >= 6: start = add_days(start, 8 - day) elif repeat.frequency == "weekly" and length == 2 and parse_date_phrase(words[1], today) is not None: # "every monday": quick-add anchored it on the next Monday, so that # anchor's weekday is the one the series keeps. start = add_days(start, (day_of_week(repeat.anchor) - day_of_week(start) + 7) % 7) return replace(repeat, anchor=start)