Piszę klasy RecurringInterval
, która - w oparciu o obiekt dateutil.rrule - reprezentuje okresowy okres w czasie. Zdefiniowałem dla niego niestandardową, możliwą do odczytania dla człowieka metodę __str__
i chciałbym również zdefiniować metodę parse
, która (podobnie jak funkcja rrulestr()) parsuje łańcuch z powrotem do obiektu.W języku Python, jak analizować ciąg reprezentujący zestaw argumentów słów kluczowych tak, że zamówienie nie ma znaczenia
Oto metoda parse
i niektóre przypadki testowe, aby przejść z nim:
import re
from dateutil.rrule import FREQNAMES
import pytest
class RecurringInterval(object):
freq_fmt = "{freq}"
start_fmt = "from {start}"
end_fmt = "till {end}"
byweekday_fmt = "by weekday {byweekday}"
bymonth_fmt = "by month {bymonth}"
@classmethod
def match_pattern(cls, string):
SPACES = r'\s*'
freq_names = [freq.lower() for freq in FREQNAMES] + [freq.title() for freq in FREQNAMES] # The frequencies may be either lowercase or start with a capital letter
FREQ_PATTERN = '(?P<freq>{})?'.format("|".join(freq_names))
# Start and end are required (their regular expressions match 1 repetition)
START_PATTERN = cls.start_fmt.format(start=SPACES + r'(?P<start>.+?)')
END_PATTERN = cls.end_fmt.format(end=SPACES + r'(?P<end>.+?)')
# The remaining tokens are optional (their regular expressions match 0 or 1 repetitions)
BYWEEKDAY_PATTERN = cls.optional(cls.byweekday_fmt.format(byweekday=SPACES + r'(?P<byweekday>.+?)'))
BYMONTH_PATTERN = cls.optional(cls.bymonth_fmt.format(bymonth=SPACES + r'(?P<bymonth>.+?)'))
PATTERN = SPACES + FREQ_PATTERN \
+ SPACES + START_PATTERN \
+ SPACES + END_PATTERN \
+ SPACES + BYWEEKDAY_PATTERN \
+ SPACES + BYMONTH_PATTERN \
+ SPACES + "$" # The character '$' is needed to make the non-greedy regular expressions parse till the end of the string
return re.match(PATTERN, string).groupdict()
@staticmethod
def optional(pattern):
'''Encloses the given regular expression in an optional group (i.e., one that matches 0 or 1 repetitions of the original regular expression).'''
return '({})?'.format(pattern)
'''Tests'''
def test_match_pattern_with_byweekday_and_bymonth():
string = "Weekly from 2017-11-03 15:00:00 till 2017-11-03 16:00:00 by weekday Monday, Tuesday by month January, February"
groups = RecurringInterval.match_pattern(string)
assert groups['freq'] == "Weekly"
assert groups['start'].strip() == "2017-11-03 15:00:00"
assert groups['end'].strip() == "2017-11-03 16:00:00"
assert groups['byweekday'].strip() == "Monday, Tuesday"
assert groups['bymonth'].strip() == "January, February"
def test_match_pattern_with_bymonth_and_byweekday():
string = "Weekly from 2017-11-03 15:00:00 till 2017-11-03 16:00:00 by month January, February by weekday Monday, Tuesday "
groups = RecurringInterval.match_pattern(string)
assert groups['freq'] == "Weekly"
assert groups['start'].strip() == "2017-11-03 15:00:00"
assert groups['end'].strip() == "2017-11-03 16:00:00"
assert groups['byweekday'].strip() == "Monday, Tuesday"
assert groups['bymonth'].strip() == "January, February"
if __name__ == "__main__":
# pytest.main([__file__])
pytest.main([__file__+"::test_match_pattern_with_byweekday_and_bymonth"]) # This passes
# pytest.main([__file__+"::test_match_pattern_with_bymonth_and_byweekday"]) # This fails
Chociaż parser działa jeśli podasz argumenty w „prawo” porządku, to jest „sztywne” w tym, że nie robi Dopuszczalne są opcjonalne argumenty w dowolnej kolejności. Właśnie dlatego drugi test kończy się niepowodzeniem.
Jaki byłby sposób, aby analizator składni przeanalizował pola "opcjonalne" w dowolnej kolejności, tak aby oba testy przeszły pomyślnie? (Myślałem o zrobieniu iteratora z wszystkimi permutacjami wyrażeń regularnych i próbowaniu na każdym z nich re.match
, ale to nie wydaje się być eleganckim rozwiązaniem).
Czy możesz nieco zmniejszyć swój kod? Wydaje się, że jest to interesujące pytanie, ale w tej chwili jest to dla mnie po prostu "ściana kodu". A [mcve] byłoby miłe. –
Oczywiście oryginalny fragment zawierał prawie wszystkie parametry [dateutil.rrule] (http://dateutil.readthedocs.io/en/stable/rrule.html), ale usunąłem te, które nie były używane w testach, aby zmniejszyć liczba linii. –
Nie mam czasu ani wglądu, aby odpowiedzieć, ale mam przegłosowanie. –