import re from datetime import date import logging as log from .prop_values import choose, singleton, list_of, compose, number, real, double, color, text, empty, anything, point, move, stone from . import skip_whitespace, ParserError GAME_INFO = 1 UNKNOWN = 99 class DateException(Exception): pass class Property: ident_regexp = re.compile(r"[A-Z]+") def __init__(self): self.name = "" self.value = "" @staticmethod def fits(s, i): return i < len(s) and s[i].isupper() @staticmethod def create(s, start): assert Property.fits(s, start) res = Property() (i, res.name) = Property.ident(s, start) i = skip_whitespace(s, i) try: (i, x) = Property.create_value(s, i, res.name) except ParserError as e: # a malformed value log.warning(e) (i, x) = choose(list_of(anything), singleton(anything))(s, i) res.name = "_"+res.name res.value = x if res.name == "DT": res = DateProperty(x) i = skip_whitespace(s, i) return (i, res) @staticmethod def ident(s, start): m = Property.ident_regexp.match(s, start) if m is None: raise ParserError("expected a property identifier matching '[A-Z]+'", s, start) return (m.end(), m.group()) @staticmethod def create_value(s, start, name): if name in Property.patterns: return Property.patterns[name](s, start) else: log.info("unknown property %s at position %d", name, start) return choose(list_of(anything), singleton(anything))(s, start) @property def type(self): game_info = {"AN", "BR", "BT", "CP", "DT", "EV", "GN", "GC", "ON", "OT", "PB", "PC", "PW", "RE", "RO", "RU", "SO", "TM", "US", "WR", "WT"} if self.name in game_info: return GAME_INFO else: return UNKNOWN def copy(self): res = Property() res.name = self.name res.value = self.value if not isinstance(self.value, list) else self.value[:] return res def __str__(self): name = self.name.lstrip("_") val = "[{0}]".format(self.value) \ if not isinstance(self.value, list) \ else "".join("[{0}]".format(x) for x in self.value) return "{0}{1}".format(name, val) patterns = { "B": singleton(move), "KO": singleton(empty), "MN": singleton(number), "W": singleton(move), "AB": list_of(stone), # "AE": list_of(point), # "AW": list_of(stone), # "PL": singleton(color), "C": singleton(text(simple=False)), "DM": singleton(double), "GB": singleton(double), "GW": singleton(double), "HO": singleton(double), "N": singleton(text()), "UC": singleton(double), "V": singleton(real), "BM": singleton(double), "DO": singleton(empty), "IT": singleton(empty), "TE": singleton(double), "AR": list_of(compose(point, point)), # "CR": list_of(point), # "DD": list_of(point, allow_empty=True), # "LB": list_of(compose(point, text())), # "LN": list_of(compose(point, point)), # "MA": list_of(point), # "SL": list_of(point), # "SQ": list_of(point), # "TR": list_of(point), # "AP": singleton(compose(text(composed=True), text())), # "CA": singleton(text()), "FF": singleton(number), "GM": singleton(number), "ST": singleton(number), "SZ": choose(singleton(number), singleton(compose(number, number))), # "AN": singleton(text()), "BR": singleton(text()), "BT": singleton(text()), "CP": singleton(text()), "DT": singleton(text()), "EV": singleton(text()), "GN": singleton(text()), "GC": singleton(text(simple=False)), "ON": singleton(text()), "OT": singleton(text()), "PB": singleton(text()), "PC": singleton(text()), "PW": singleton(text()), "RE": singleton(text()), "RO": singleton(text()), "RU": singleton(text()), "SO": singleton(text()), "TM": singleton(real), "US": singleton(text()), "WR": singleton(text()), "WT": singleton(text()), "BL": singleton(real), "OB": singleton(number), "OW": singleton(number), "WL": singleton(real), "FG": choose(singleton(empty), singleton(compose(number, text()))), # "PM": singleton(number), "VW": list_of(point, allow_empty=True), # # go specific "HA": singleton(number), "KM": singleton(real), "TB": list_of(point, allow_empty=True), "TW": list_of(point, allow_empty=True) } class DateProperty(Property): def __init__(self, value): super().__init__() self.name = "DT" self.value = [] self.raw_value = value self.parse(value) def parse(self, s): regexp = re.compile(r"\d{4}(-\d\d){0,2}(,(\d{4}(-\d\d){0,2}|\d\d(-\d\d)?))*") match = re.search(regexp, s) if not match: raise DateException('Could not parse a DT value: "{0}"'.format(s)) substr = match.group(0) date_strs = substr.split(",") dates = [] prev_format = None for s in date_strs: try: (prev_format, d) = DateProperty.parse_single(s, prev_format, dates[-1] if dates else None) except ValueError: raise DateException('Could not parse a DT value: "{0}"'.format(s)) dates.append(d) self.value = dates @staticmethod def parse_single(date_str, prev_format, prev=None): tokens = date_str.split("-") num_tokens = list(map(int, tokens)) if len(tokens) == 3: return ("YMD", date(*num_tokens)) elif len(tokens) == 2: if len(tokens[0]) == 4: return ("YM", date(*num_tokens, 1)) else: return ("MD", date(prev.year, *num_tokens)) else: if len(tokens[0]) == 4: return ("Y", date(*num_tokens, 1, 1)) elif prev_format in ("YM", "M"): return ("M", date(prev.year, *num_tokens, 1)) else: return ("D", date(prev.year, prev.month, *num_tokens))