import re from typing import List, Optional, Tuple from .dates_add_days import add_days from .dates_day_of_week import day_of_week from .todo_item import Recurrence, TodoDraft from .todo_normalise_tags import normalise_tags from .todo_parse_date_phrase import parse_date_phrase _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, )