diff --git a/reminder/locale_util.py b/reminder/locale_util.py index c1e6551..604878b 100644 --- a/reminder/locale_util.py +++ b/reminder/locale_util.py @@ -53,12 +53,12 @@ class RelativeDeltaParams(TypedDict): class MatcherReturn(NamedTuple): params: 'RelativeDeltaParams' - end: int + unconsumed: str class Matcher(ABC): @abstractmethod - def match(self, val: str, start: int = 0) -> Optional[MatcherReturn]: + def match(self, val: str) -> Optional[MatcherReturn]: pass @@ -70,45 +70,44 @@ def __init__(self, pattern: str, value_type: Type = int) -> None: self.regex = re.compile(pattern, re.IGNORECASE) self.value_type = value_type - def match(self, val: str, start: int = 0) -> Optional[MatcherReturn]: - match = self.regex.match(val, pos=start) - if match and match.end() > 0: - return self._convert_match(match) + def match(self, val: str) -> Optional[MatcherReturn]: + match = self.regex.match(val) + if match and match.end() > 0 and len(match.groups()) > 0: + return MatcherReturn(params=self._convert_match(match), unconsumed=val[match.end():]) return None - def _convert_match(self, match: Match) -> MatcherReturn: - return MatcherReturn(params=self._convert_groups(match.groupdict()), - end=match.end()) + def _convert_match(self, match: Match) -> 'RelativeDeltaParams': + return self._convert_groups(match.groupdict()) def _convert_groups(self, groups: Dict[str, str]) -> 'RelativeDeltaParams': return {key: self.value_type(value) for key, value in groups.items() if value} class TimeMatcher(RegexMatcher): - def _convert_match(self, match: Match) -> MatcherReturn: + def _convert_match(self, match: Match) -> 'RelativeDeltaParams': groups = match.groupdict() try: meridiem = groups.pop("meridiem").lower() - except KeyError: + except (KeyError, AttributeError): meridiem = None params = self._convert_groups(groups) if meridiem == "pm": params["hour"] += 12 elif meridiem == "am" and params["hour"] == 12: params["hour"] = 0 - return MatcherReturn(params=params, end=match.end()) + return params class ShortYearMatcher(RegexMatcher): - def _convert_match(self, match: Match) -> MatcherReturn: - rtrn = super()._convert_match(match) - if rtrn.params["year"] < 100: + def _convert_match(self, match: Match) -> 'RelativeDeltaParams': + params = super()._convert_match(match) + if params["year"] < 100: year = datetime.now().year current_century = year // 100 - if rtrn.params["year"] < year % 100: + if params["year"] < year % 100: current_century += 1 - rtrn.params["year"] = (current_century * 100) + rtrn.params["year"] - return rtrn + params["year"] = (current_century * 100) + params["year"] + return params class WeekdayMatcher(Matcher): @@ -121,13 +120,13 @@ def __init__(self, pattern: str, map: Dict[str, Union[int, WeekdayType]], substr self.map = map self.substr = substr - def match(self, val: str, start: int = 0) -> Optional[MatcherReturn]: - match = self.regex.match(val, pos=start) + def match(self, val: str) -> Optional[MatcherReturn]: + match = self.regex.match(val) if match and match.end() > 0: weekday = self.map[match.string[:self.substr].lower()] if isinstance(weekday, int): weekday = (datetime.now().weekday() + weekday) % 7 - return MatcherReturn(params={"weekday": weekday}, end=match.end()) + return MatcherReturn(params={"weekday": weekday}, unconsumed=val[match.end():]) return None @@ -151,26 +150,25 @@ def replace(self, name: str, timedelta: Matcher = None, date: Matcher = None, return Locale(name=name, timedelta=timedelta or self.timedelta, date=date or self.date, weekday=weekday or self.weekday, time=time or self.time) - def match(self, val: str, start: int = 0) -> Optional[MatcherReturn]: - end = start - found_delta = self.timedelta.match(val, start=end) + def match(self, val: str) -> Optional[MatcherReturn]: + found_delta = self.timedelta.match(val) if found_delta: - params, end = found_delta + params, val = found_delta else: params = {} - found_day = self.weekday.match(val, start=end) + found_day = self.weekday.match(val) if found_day: - params, end = found_day + params, val = found_day else: - found_date = self.date.match(val, start=end) + found_date = self.date.match(val) if found_date: - params, end = found_date + params, val = found_date - found_time = self.time.match(val, start=end) + found_time = self.time.match(val) if found_time: params = {**params, **found_time.params} - end = found_time.end - return MatcherReturn(params, end) if len(params) > 0 else None + val = found_time.unconsumed + return MatcherReturn(params=params, unconsumed=val) if len(params) > 0 else None Locales = Dict[str, Locale] diff --git a/reminder/locales.py b/reminder/locales.py index 4b5471f..b29a12d 100644 --- a/reminder/locales.py +++ b/reminder/locales.py @@ -30,9 +30,10 @@ rf"(?:(?P[-+]?\d+)\s?d(?:ays?)?{td_sep_en})?" rf"(?:(?P[-+]?\d+)\s?h(?:(?:r|our)?s?){td_sep_en})?" rf"(?:(?P[-+]?\d+)\s?m(?:in(?:ute)?s?)?{td_sep_en})?" - r"(?:(?P[-+]?\d+)\s?s(?:ec(?:ond)?s?)?)?"), - date=RegexMatcher(r"(?P\d{4})-(?P\d{2})-(?P\d{2})"), - weekday=WeekdayMatcher(pattern=r"today" + r"(?:(?P[-+]?\d+)\s?s(?:ec(?:ond)?s?)?)?" + r"(?:\s|$)"), + date=RegexMatcher(r"(?P\d{4})-(?P\d{2})-(?P\d{2})\s"), + weekday=WeekdayMatcher(pattern=r"(?:today" r"|tomorrow" r"|mon(?:day)?" r"|tues?(?:day)?" @@ -40,7 +41,8 @@ r"|thu(?:rs(?:day)?)?" r"|fri(?:day)?" r"|sat(?:urday)?" - r"|sun(?:day)?", + r"|sun(?:day)?)" + r"(?:\s|$)", map={ "tod": +0, "tom": +1, "mon": MO, "tue": TU, "wed": WE, "thu": TH, "fri": FR, "sat": SA, "sun": SU, @@ -48,22 +50,24 @@ time=RegexMatcher(r"\s?(?:at\s)?" r"(?P\d{2})" r"[:.](?P\d{2})" - r"(?:[:.](?P\d{2}))?"), + r"(?:[:.](?P\d{2}))?" + r"(?:\s|$)"), ) time_12_en = TimeMatcher(r"\s?(?:at\s)?" r"(?P\d{2})" r"(?:[:.](?P\d{2}))?" r"(?:[:.](?P\d{2}))?" - r"(?:\s(?Pa\.?m|p\.?m)\.?)?") + r"(?:\s(?Pa\.?m|p\.?m)\.?)?" + r"(?:\s|$)") locales["en_us"] = locales["en_iso"].replace( - name="English (US)", time=time_12_en, - date=ShortYearMatcher(r"(?P\d{1,2})/(?P\d{1,2})(?:/(?P\d{2}(?:\d{2})?))?")) + name="English (US)", time=time_12_en, date=ShortYearMatcher( + r"(?P\d{1,2})/(?P\d{1,2})(?:/(?P\d{2}(?:\d{2})?))?(?:\s|$)")) locales["en_uk"] = locales["en_iso"].replace( - name="English (UK)", time=time_12_en, - date=ShortYearMatcher(r"(?P\d{1,2})/(?P\d{1,2})(?:/(?P\d{2}(?:\d{2})?))?")) + name="English (UK)", time=time_12_en, date=ShortYearMatcher( + r"(?P\d{1,2})/(?P\d{1,2})(?:/(?P\d{2}(?:\d{2})?))?(?:\s|$)")) td_sep_fi = r"(?:[\s,]{1,3}(?:ja\s)?)" locales["fi_fi"] = Locale( @@ -75,8 +79,9 @@ rf"(?:(?P[-+]?\d+)\s?t(?:un(?:nin?|tia))?{td_sep_fi})?" rf"(?:(?P[-+]?\d+)\s?m(?:in(?:uut(?:in?|tia))?)?{td_sep_fi})?" r"(?:(?P[-+]?\d+)\s?s(?:ek(?:un(?:nin?|tia))?)?)?" - r"(?:\s(?:kuluttua|päästä?))?"), - date=ShortYearMatcher(r"(?P\d{1,2})\.(?P\d{1,2})\.(?P\d{2}(?:\d{2})?)"), + r"(?:\s(?:kuluttua|päästä?))?" + r"(?:\s|$)"), + date=ShortYearMatcher(r"(?P\d{1,2})\.(?P\d{1,2})\.(?P\d{2}(?:\d{2})?)\s"), weekday=WeekdayMatcher(pattern=r"(?:tänään" r"|(?:yli)?huomen" r"|ma(?:aanantai)?" @@ -86,7 +91,8 @@ r"|pe(?:rjantai)?" r"|la(?:uantai)?" r"|su(?:nnuntai)?)" - r"(?:na)?", + r"(?:na)?" + r"(?:\s|$)", map={ "tä": +0, "hu": +1, "yl": +2, "ma": MO, "ti": TU, "ke": WE, "to": TH, "pe": FR, "la": SA, "su": SU, @@ -94,7 +100,8 @@ time=RegexMatcher(r"\s?(?:ke?ll?o\.?\s)?" r"(?P\d{2})" r"[:.](?P\d{2})" - r"(?:[:.](?P\d{2}))?"), + r"(?:[:.](?P\d{2}))?" + r"(?:\s|$)"), ) td_sep_de = r"(?:[\s,]{1,3}(?:und\s)?)" @@ -108,7 +115,8 @@ rf"(?:(?P[-+]?\d+)\s?stunden?{td_sep_de})?" rf"(?:(?P[-+]?\d+)\s?minuten?{td_sep_de})?" r"(?:(?P[-+]?\d+)\s?sekunden?)?"), - date=ShortYearMatcher(r"(?P\d{1,2})\.(?P\d{1,2})\.(?P\d{2}(?:\d{2})?)"), + date=ShortYearMatcher( + r"(?P\d{1,2})\.(?P\d{1,2})\.(?P\d{2}(?:\d{2})?)(?:\s|$)"), weekday=WeekdayMatcher(pattern=r"(?:heute" r"|(?:über)?morgen" r"|mo(?:ntag)?" @@ -117,7 +125,8 @@ r"|do(?:nnerstag)?" r"|fr(?:eitag)?" r"|sa(?:mstag)?" - r"|so(?:nntag)?)", + r"|so(?:nntag)?)" + r"(?:\s|$)", map={ "heu": +0, "mor": +1, "übe": +2, "mon": MO, "die": TU, "mit": WE, "don": TH, "fre": FR, "sam": SA, "son": SU, @@ -125,5 +134,6 @@ time=RegexMatcher(r"\s?(?:um\s)?" r"(?P\d{2})" r"[:.](?P\d{2})" - r"(?:[:.](?P\d{2}))?"), + r"(?:[:.](?P\d{2}))?" + r"(?:\s|$)"), ) diff --git a/reminder/util.py b/reminder/util.py index 3482f82..a29992a 100644 --- a/reminder/util.py +++ b/reminder/util.py @@ -37,31 +37,6 @@ def do_update(self, helper: ConfigUpdateHelper) -> None: helper.copy("base_command") -timedelta_regex = re.compile(r"(?:(?P[-+]?\d+)\s?y(?:ears?)?\s?)?" - r"(?:(?P[-+]?\d+)\s?months?\s?)?" - r"(?:(?P[-+]?\d+)\s?w(?:eeks?)?\s?)?" - r"(?:(?P[-+]?\d+)\s?d(?:ays?)?\s?)?" - r"(?:(?P[-+]?\d+)\s?h(?:ours?)?\s?)?" - r"(?:(?P[-+]?\d+)\s?m(?:inutes?)?\s?)?" - r"(?:(?P[-+]?\d+)\s?s(?:econds?)?\s?)?", - flags=re.IGNORECASE) -date_regex = re.compile(r"(?P\d{4})-(?P\d{1,2})-(?P\d{1,2})") -day_regex = re.compile(r"today" - r"|tomorrow" - r"|mon(?:day)?" - r"|tues?(?:day)?" - r"|wed(?:nesday)?" - r"|thu(?:rs(?:day)?)?" - r"|fri(?:day)?" - r"|sat(?:urday)?" - r"|sun(?:day)?", - flags=re.IGNORECASE) -time_regex = re.compile(r"(?:\sat\s)?(?P\d{2})" - r"[:.](?P\d{2})" - r"(?:[:.](?P\d{2}))?", - flags=re.IGNORECASE) - - class DateArgument(Argument): def __init__(self, name: str, label: str = None, *, required: bool = False): super().__init__(name, label=label, required=required, pass_raw=True) @@ -79,7 +54,7 @@ def match(self, val: str, evt: MessageEvent = None, instance: 'ReminderBot' = No match = locale.match(val) if match: date = (datetime.now(tz=tz) + relativedelta(**match.params)).astimezone(pytz.UTC) - return val[match.end:], date + return match.unconsumed, date return val, None