class ConstrainedNodeFetcher(object): SHEET_NAME = 'trapables' _value_value_map = { 0: 0, 1: 1, 2: 5, 3: 15, 4: 30, 5: 55, } _legal_groups = { 'WHITE', 'BLUE', 'BLACK', 'RED', 'GREEN', 'drawgo', 'mud', 'post', 'midrange', 'mill', 'reanimate', 'burn', 'hatebear', 'removal', 'lock', 'yardvalue', 'ld', 'storm', 'tezz', 'lands', 'shatter', 'bounce', 'shadow', 'stifle', 'beat', 'cheat', 'pox', 'counter', 'discard', 'cantrip', 'balance', 'stasis', 'standstill', 'whitehate', 'bluehate', 'blackhate', 'redhate', 'greenhate', 'antiwaste', 'delirium', 'sacvalue', 'lowtoughnesshate', 'armageddon', 'stax', 'bloom', 'weldingjar', 'drawhate', 'pluscard', 'ramp', 'devoteddruid', 'fetchhate', 'dragon', 'company', 'naturalorder', 'flash', 'wincon', 'vial', 'fixing', 'colorlessvalue', 'fetchable', 'indestructable', 'legendarymatters', 'sol', 'manland', 'storage', 'croprotate', 'dnt', 'equipment', 'livingdeath', 'eggskci', 'hightide', 'fatty', 'walker', 'blink', 'miracles', 'city', 'wrath', 'landtax', 'discardvalue', 'edict', 'phoenix', 'enchantress', 'dork', 'tinker', 'landtax', 'highpowerhate', 'affinity', 'academy', 'stompy', 'shardless', 'lanterns', 'depths', 'survival', 'landstill', 'moat', 'combo', 'kite', 'haste', 'fog', } def __init__(self, db: CardDatabase, document_id: str = values.DOCUMENT_ID): self._db = db self._printing_tree_parser = PrintingTreeParser(db) self._sheet_client = GoogleSheetClient(document_id) def _parse_groups(self, s: str) -> t.FrozenSet[str]: groups = [] if s: for group in s.split(','): group = group.rstrip().lstrip().replace('-', '') if not group in self._legal_groups: raise ConstrainedCubeablesFetchException( f'"{group}" not a legal group') groups.append(group) return frozenset(groups) def _fetch(self, start_column: int) -> t.List[ConstrainedNode]: exceptions = [] constrained_cubeables = [] for row in self._sheet_client.read_sheet( self.SHEET_NAME, start_column=start_column, start_row=4, end_column=start_column + 3, end_row=1000, ): if not row: print('Empty row in trapables input!') # TODO should probably be a runtime warning, but that requires logging... continue amount_cell, printings_cell, value_cell = row[:3] groups_cell = row[3] if len(row) > 3 else '' try: amount = int(amount_cell) except ValueError: amount = 1 try: node = self._printing_tree_parser.parse(printings_cell) except PrintingTreeParserException as e: exceptions.append(e) continue try: value = self._value_value_map[int(value_cell)] except (ValueError, KeyError) as e: exceptions.append(e) continue try: groups = self._parse_groups(groups_cell) except ConstrainedCubeablesFetchException as e: exceptions.append(e) continue for _ in range(amount): constrained_cubeables.append( ConstrainedNode( node=node, value=value, groups=groups, )) if exceptions: raise ConstrainedCubeablesFetchException(exceptions) return constrained_cubeables def fetch_garbage(self) -> t.List[ConstrainedNode]: return self._fetch(1) def fetch_garbage_lands(self) -> t.List[ConstrainedNode]: return self._fetch(5) def fetch_all(self) -> t.List[ConstrainedNode]: garbage = self.fetch_garbage() garbage.extend(self.fetch_garbage_lands()) return garbage
class CubeFetcher(object): def __init__(self, db: CardDatabase, document_id: str = values.DOCUMENT_ID, sheet_id: str = SHEET_ID): self._db = db self._printing_tree_parser = PrintingTreeParser(self._db) self._document_id = document_id self._sheet_id = sheet_id self._printing_matcher = re.compile( "\\s*([\\w\\-': ,/]+)\\|([A-Z0-9_]+)") self._ticket_matcher = re.compile("([\\w ]+):(.*)") self._id_matcher = re.compile('\\d+$') self._sheet_client = GoogleSheetClient(document_id) def _get_cells(self) -> t.List[t.List[str]]: return self._sheet_client.read_sheet( sheet_name=SHEET_NAME, start_column=1, start_row=1, end_column=50, end_row=300, major_dimension='COLUMNS', ) def _parse_trap_cell(self, cell: str, arg: t.Optional[str] = None) -> Trap: try: return Trap( node=self._printing_tree_parser.parse(cell), intention_type=(Trap.IntentionType.GARBAGE if arg is None or arg == '' else Trap.IntentionType( arg.split('-')[0])), ) except (PrintingTreeParserException, AttributeError) as e: raise CubeParseException(e) def _parse_printing(self, s: str, arg: str = None) -> Printing: m = self._printing_matcher.match(s) if not m: raise CubeParseException(f'Invalid printing "{s}"') if self._id_matcher.match(m.group(2)): try: return self._db.printings[int(m.group(2))] except KeyError: raise CubeParseException(f'Invalid printing id "{m.group(2)}"') try: cardboard = self._db.cardboards[m.group(1)] except KeyError: raise CubeParseException(f'Invalid cardboard "{m.group(1)}"') try: return cardboard.from_expansion(m.group(2)) except KeyError: raise CubeParseException( f'Invalid expansion "{m.group(2)}" for cardboard "{cardboard}"' ) def _parse_ticket(self, s: str, arg: str = None) -> Ticket: m = self._ticket_matcher.match(s) if not m: raise CubeParseException(f'Invalid ticket "{s}"') return Ticket([ self._parse_printing(sm.group()) for sm in self._printing_matcher.finditer(m.group(2)) ], m.group(1)) def _parse_purple(self, s: str, arg: str = None) -> Purple: return Purple(s) def _construct_cube(self, tsv: t.List[t.List[str]]) -> Cube: traps = [] printings = [] tickets = [] purples = [] exceptions = [] #type: t.List[t.Tuple[str, Exception]] def _parse_all_cells( cells: t.Iterable[str], arguments: t.Iterable[str], parser: t.Callable[[str, str], T], ) -> t.Iterable[T]: for _cell, arg in itertools.zip_longest(cells, arguments, fillvalue=''): if _cell: try: yield parser(_cell, arg) except CubeParseException as e: exceptions.append((_cell, e)) for column, args in itertools.zip_longest( tsv, tsv[1:], fillvalue=['' for _ in range(len(tsv[-1]))]): if column[0] == 'TRAPS': traps.extend( list( _parse_all_cells(column[2:], args[2:], self._parse_trap_cell))) if column[0] == 'GARBAGE_TRAPS': traps.extend( list( _parse_all_cells(column[2:], (), self._parse_trap_cell))) elif column[0] in ('W', 'U', 'B', 'R', 'G', 'HYBRID', 'GOLD', 'COLORLESS', 'LAND'): printings.extend( _parse_all_cells(column[2:], args[2:], self._parse_printing)) elif column[0] == 'TICKETS': tickets = list( _parse_all_cells(column[2:], args[2:], self._parse_ticket)) elif column[0] == 'PURPLES': purples = list( _parse_all_cells(column[2:], args[2:], self._parse_purple)) if exceptions: raise CubeParseException(exceptions) return Cube(itertools.chain(printings, traps, tickets, purples)) def fetch_cube(self) -> Cube: return self._construct_cube(self._get_cells())