Skip to content

Commit

Permalink
Return unconsumed part instead of end index from matcher
Browse files Browse the repository at this point in the history
Also removed unused regexes
  • Loading branch information
tulir committed Mar 25, 2020
1 parent 02b3f7d commit a1aba2d
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 75 deletions.
62 changes: 30 additions & 32 deletions reminder/locale_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand All @@ -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):
Expand All @@ -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


Expand All @@ -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]
44 changes: 27 additions & 17 deletions reminder/locales.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,40 +30,44 @@
rf"(?:(?P<days>[-+]?\d+)\s?d(?:ays?)?{td_sep_en})?"
rf"(?:(?P<hours>[-+]?\d+)\s?h(?:(?:r|our)?s?){td_sep_en})?"
rf"(?:(?P<minutes>[-+]?\d+)\s?m(?:in(?:ute)?s?)?{td_sep_en})?"
r"(?:(?P<seconds>[-+]?\d+)\s?s(?:ec(?:ond)?s?)?)?"),
date=RegexMatcher(r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})"),
weekday=WeekdayMatcher(pattern=r"today"
r"(?:(?P<seconds>[-+]?\d+)\s?s(?:ec(?:ond)?s?)?)?"
r"(?:\s|$)"),
date=RegexMatcher(r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})\s"),
weekday=WeekdayMatcher(pattern=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)?",
r"|sun(?:day)?)"
r"(?:\s|$)",
map={
"tod": +0, "tom": +1, "mon": MO, "tue": TU, "wed": WE, "thu": TH,
"fri": FR, "sat": SA, "sun": SU,
}, substr=3),
time=RegexMatcher(r"\s?(?:at\s)?"
r"(?P<hour>\d{2})"
r"[:.](?P<minute>\d{2})"
r"(?:[:.](?P<second>\d{2}))?"),
r"(?:[:.](?P<second>\d{2}))?"
r"(?:\s|$)"),
)

time_12_en = TimeMatcher(r"\s?(?:at\s)?"
r"(?P<hour>\d{2})"
r"(?:[:.](?P<minute>\d{2}))?"
r"(?:[:.](?P<second>\d{2}))?"
r"(?:\s(?P<meridiem>a\.?m|p\.?m)\.?)?")
r"(?:\s(?P<meridiem>a\.?m|p\.?m)\.?)?"
r"(?:\s|$)")

locales["en_us"] = locales["en_iso"].replace(
name="English (US)", time=time_12_en,
date=ShortYearMatcher(r"(?P<month>\d{1,2})/(?P<day>\d{1,2})(?:/(?P<year>\d{2}(?:\d{2})?))?"))
name="English (US)", time=time_12_en, date=ShortYearMatcher(
r"(?P<month>\d{1,2})/(?P<day>\d{1,2})(?:/(?P<year>\d{2}(?:\d{2})?))?(?:\s|$)"))

locales["en_uk"] = locales["en_iso"].replace(
name="English (UK)", time=time_12_en,
date=ShortYearMatcher(r"(?P<day>\d{1,2})/(?P<month>\d{1,2})(?:/(?P<year>\d{2}(?:\d{2})?))?"))
name="English (UK)", time=time_12_en, date=ShortYearMatcher(
r"(?P<day>\d{1,2})/(?P<month>\d{1,2})(?:/(?P<year>\d{2}(?:\d{2})?))?(?:\s|$)"))

td_sep_fi = r"(?:[\s,]{1,3}(?:ja\s)?)"
locales["fi_fi"] = Locale(
Expand All @@ -75,8 +79,9 @@
rf"(?:(?P<hours>[-+]?\d+)\s?t(?:un(?:nin?|tia))?{td_sep_fi})?"
rf"(?:(?P<minutes>[-+]?\d+)\s?m(?:in(?:uut(?:in?|tia))?)?{td_sep_fi})?"
r"(?:(?P<seconds>[-+]?\d+)\s?s(?:ek(?:un(?:nin?|tia))?)?)?"
r"(?:\s(?:kuluttua|päästä?))?"),
date=ShortYearMatcher(r"(?P<day>\d{1,2})\.(?P<month>\d{1,2})\.(?P<year>\d{2}(?:\d{2})?)"),
r"(?:\s(?:kuluttua|päästä?))?"
r"(?:\s|$)"),
date=ShortYearMatcher(r"(?P<day>\d{1,2})\.(?P<month>\d{1,2})\.(?P<year>\d{2}(?:\d{2})?)\s"),
weekday=WeekdayMatcher(pattern=r"(?:tänään"
r"|(?:yli)?huomen"
r"|ma(?:aanantai)?"
Expand All @@ -86,15 +91,17 @@
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,
}, substr=2),
time=RegexMatcher(r"\s?(?:ke?ll?o\.?\s)?"
r"(?P<hour>\d{2})"
r"[:.](?P<minute>\d{2})"
r"(?:[:.](?P<second>\d{2}))?"),
r"(?:[:.](?P<second>\d{2}))?"
r"(?:\s|$)"),
)

td_sep_de = r"(?:[\s,]{1,3}(?:und\s)?)"
Expand All @@ -108,7 +115,8 @@
rf"(?:(?P<hours>[-+]?\d+)\s?stunden?{td_sep_de})?"
rf"(?:(?P<minutes>[-+]?\d+)\s?minuten?{td_sep_de})?"
r"(?:(?P<seconds>[-+]?\d+)\s?sekunden?)?"),
date=ShortYearMatcher(r"(?P<day>\d{1,2})\.(?P<month>\d{1,2})\.(?P<year>\d{2}(?:\d{2})?)"),
date=ShortYearMatcher(
r"(?P<day>\d{1,2})\.(?P<month>\d{1,2})\.(?P<year>\d{2}(?:\d{2})?)(?:\s|$)"),
weekday=WeekdayMatcher(pattern=r"(?:heute"
r"|(?:über)?morgen"
r"|mo(?:ntag)?"
Expand All @@ -117,13 +125,15 @@
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,
}, substr=3),
time=RegexMatcher(r"\s?(?:um\s)?"
r"(?P<hour>\d{2})"
r"[:.](?P<minute>\d{2})"
r"(?:[:.](?P<second>\d{2}))?"),
r"(?:[:.](?P<second>\d{2}))?"
r"(?:\s|$)"),
)
27 changes: 1 addition & 26 deletions reminder/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,31 +37,6 @@ def do_update(self, helper: ConfigUpdateHelper) -> None:
helper.copy("base_command")


timedelta_regex = re.compile(r"(?:(?P<years>[-+]?\d+)\s?y(?:ears?)?\s?)?"
r"(?:(?P<months>[-+]?\d+)\s?months?\s?)?"
r"(?:(?P<weeks>[-+]?\d+)\s?w(?:eeks?)?\s?)?"
r"(?:(?P<days>[-+]?\d+)\s?d(?:ays?)?\s?)?"
r"(?:(?P<hours>[-+]?\d+)\s?h(?:ours?)?\s?)?"
r"(?:(?P<minutes>[-+]?\d+)\s?m(?:inutes?)?\s?)?"
r"(?:(?P<seconds>[-+]?\d+)\s?s(?:econds?)?\s?)?",
flags=re.IGNORECASE)
date_regex = re.compile(r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\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<hour>\d{2})"
r"[:.](?P<minute>\d{2})"
r"(?:[:.](?P<second>\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)
Expand All @@ -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


Expand Down

0 comments on commit a1aba2d

Please sign in to comment.