def build_card(self, deck: Deck, card_data: CardData, block: CardBlock) -> None: card = deck.make_card() card.set_count(card_data.count) if 'name' in card_data.data: card.name = card_data.data['name'] if 'description' in card_data.data: card.name = card_data.data['description'] exec = Executor(self.ctx, card, None) exec.env['card'] = card_data.data exec.env['deck'] = deck.data try: for renderer in block.renderers: exec.execute(renderer) except ValidateError as ve: raise ValidateError( f"while rendering card {json.dumps(card_data.data, ensure_ascii=False)}:\n{ve}" ) from ve if not card.get_front(): raise ValidateError( f"no front face for card {json.dumps(card_data.data, ensure_ascii=False)}" ) if not card.get_back(): raise ValidateError( f"no back face for card {json.dumps(card_data.data, ensure_ascii=False)}" )
def parse_scheme(self, elt: Element, scheme: ElementScheme): params: Dict[str, Any] = dict() for key, value in elt.attrib.items(): if key not in scheme.attrs: raise ValidateError(f"unexpected attribute '{encode(key)}'") try: params[key] = scheme.attrs[key](value) except ValidateError as ve: raise ValidateError(f"in attribute '{encode(key)}': {ve}") from ve for key in scheme.required: if key not in params: raise ValidateError(f"missing required attribute '{key}'") return params
def parse_bool(value): if value == "true": return True elif value == "false": return False else: raise ValidateError("expected 'true' or 'false'")
def parse_style(self, style_elt: Element): params = self.parse_scheme(style_elt, style_scheme) name = params['name'] if name in self.styles: raise ValidateError(f"duplicate style '{name}'") self.styles[name] = params for elt in style_elt: raise self.unexpected_elt(elt)
def parse_case(self, case_elt: Element) -> StmtCase: params = self.parse_scheme(case_elt, case_scheme) case = StmtCase(self.getloc(case_elt)) for elt in case_elt: if elt.tag == "when": case.whens.append(self.process_element(elt, self.parse_when_block)) elif elt.tag == "default": if case.kelse is not None: raise ValidateError("duplicate <else> element")
def parse_valign(value): if value == "top": return VAlign.Top elif value == "center": return VAlign.Center elif value == "bottom": return VAlign.Bottom else: raise ValidateError( "invalid value (expected 'top', 'center' or 'bottom')")
def add_card(self, block: CardBlock, data: Dict[str, str]): card_data = CardData() for key, value in data.items(): if key == "count": try: card_data.count = int(value) except ValueError: raise ValidateError(f"invalid 'count' value of {encode(value)}") card_data.data[key] = value block.cards.append(card_data)
def to_number(val): if isinstance(val, int): return val if isinstance(val, float): return val try: return int(val) except ValueError: try: return float(val) except ValueError: raise ValidateError("expected integer")
def parse_halign(value): if value == "left": return HAlign.Left elif value == "center": return HAlign.Center elif value == "right": return HAlign.Right elif value == "justify": return HAlign.Justify else: raise ValidateError( "invalid value (expected 'left', 'center', 'right' or 'justify')")
def compute(self, expr: Expr) -> Any: if expr is None: return None if isinstance(expr, ExprLit): return expr.s elif isinstance(expr, ExprConcat): return ''.join(map(self.eval, expr.pieces)) elif isinstance(expr, ExprField): lhs = self.compute(expr.obj) if isinstance(lhs, dict): return lhs.get(expr.field, None) else: raise ValidateError( f"cannot read property '{encode(expr.field)}' of non-object" ) elif isinstance(expr, ExprID): if expr.s in self.env: return self.env[expr.s] else: raise ValidateError( f"variable '{encode(expr.s)}' doesn't exist") elif isinstance(expr, ExprCall): func = expr.func if func not in funcs: raise ValidateError(f"unknown function '{func}'") func_data = funcs[func] args = [] for arg in expr.args: args.append(self.compute(arg)) params = func_data['args'] if len(args) != len(params): raise ValidateError( f"invalid number of arguments for function '{func}'") converted_args = [] for param, arg in zip(params, args): converted_args.append(param(arg)) return func_data['call'](*converted_args) else: raise ValidateError("invalid expression")
def resolve_style(self, name: str) -> TextStyle: if name in self.resolved_styles: return self.resolved_styles[name] if name not in self.styles: raise ValidateError(f"text style '{encode(name)}' is not defined") params = self.styles[name] self.resolved_styles[name] = None parent_name = params.get('parent') parent = None if parent_name is not None: parent = self.resolve_style(parent_name) style = TextStyle(name, parent, params) self.resolved_styles[name] = style return style
def parse_google_sheet(self, google_elt: Element, block: CardBlock): params = self.parse_scheme(google_elt, google_scheme) try: def worker(process: TaskProcess): data = download_google_sheet_as_dictionary(params['key'], params['sheet']) return data @asyncify def run(): data = yield run_threaded(f"Downloading {params['key']}/{params['sheet']}", worker) for row in data: self.add_card(block, row) self.pending_tasks.append(run()) except BaseException as err: raise ValidateError(f"failed to download google spreadsheet: {err}")
def instantiate_deck(self, db: Deckbuilder, template: DeckTemplate): try: deck = db.make_deck(template.name, (template.width, template.height)) deck.scale = template.scale if template.back_default: deck.set_default_back( self.build_face(deck, template.back_default)) if template.face_hidden: deck.set_hidden_face( self.build_face(deck, template.face_hidden)) for card_block in template.card_blocks: for card in card_block.cards: self.build_card(deck, card, card_block) except ValidateError as ve: raise ValidateError( f"while building deck '{encode(template.name)}': {ve}") from ve
def parse_deck(self, deck_elt: Element): params = self.parse_scheme(deck_elt, deck_scheme) name = params['name'] if name in self.decks: raise ValidateError(f"duplicate deck '{name}'") deck = DeckTemplate(name, params['width'], params['height']) if 'scale' in params: deck.scale = params['scale'] self.decks[name] = deck for elt in deck_elt: if elt.tag == "cards": self.process_element(elt, self.parse_cards, deck) elif elt.tag == "back-default": deck.back_default = self.process_element(elt, self.parse_template) elif elt.tag == "face-hidden": deck.face_hidden = self.process_element(elt, self.parse_template) else: raise self.unexpected_elt(elt)
def parse_image_set(self, imageset_elt: Element, block: CardBlock): params = self.parse_scheme(imageset_elt, imageset_scheme) try: path = params['path'] def worker(process: TaskProcess): data = [] base_path = self.resolve_path(path) for root, dirs, files in os.walk(base_path): for file in files: if file.endswith(".png") or file.endswith(".jpg") or file.endswith(".jpeg"): data.append({ "path": os.path.abspath(os.path.join(root, file)), "filename": file, "name": os.path.splitext(file)[0] }) return data @asyncify def run(): data = yield run_threaded(f"Collecting images in {path}", worker) for row in data: self.add_card(block, row) self.pending_tasks.append(run()) except BaseException as err: raise ValidateError(f"failed to load image set: {err}")
def parse_float(value): try: return float(value) except ValueError: raise ValidateError("expected number")
def parse_int(value): try: return int(value) except ValueError: raise ValidateError("expected integer")
def parse_name(value): if not re_style_name.match(value): raise ValidateError( "expected name (allowed characters are a-z, A-Z, 0-9, and '_', starts with a letter)" ) return value
def execute(self, stmt: Stmt): self.fuel -= 1 if self.fuel <= 0: raise ValidateError("evaluation took too many steps") try: if isinstance(stmt, StmtSequence): for child in stmt.stmts: self.execute(child) elif isinstance(stmt, StmtDrawRect): self.get_face().draw_rect( ( validators.parse_int(self.eval(stmt.x)), validators.parse_int(self.eval(stmt.y)), validators.parse_int(self.eval(stmt.width)), validators.parse_int(self.eval(stmt.height)), ), self.eval_nullable(validators.parse_color, stmt.color, None), self.eval_nullable(validators.parse_color, stmt.line_color, None), self.eval_nullable(validators.parse_int, stmt.line_width, 1)) elif isinstance(stmt, StmtDrawText): self.get_face().draw_text( ( validators.parse_int(self.eval(stmt.x)), validators.parse_int(self.eval(stmt.y)), validators.parse_int(self.eval(stmt.width)), validators.parse_int(self.eval(stmt.height)), ), self.ctx.resolve_style(self.eval(stmt.style)), textparser.TextParser(self.ctx, self.eval(stmt.text)).parse()) elif isinstance(stmt, StmtDrawImage): self.get_face().draw_image( (validators.parse_int(self.eval( stmt.x)), validators.parse_int(self.eval(stmt.y))), self.ctx.resolve_path(self.eval(stmt.src)), (self.eval_nullable(validators.parse_float, stmt.align_x, 0), self.eval_nullable(validators.parse_float, stmt.align_y, 0))) elif isinstance(stmt, StmtFace): if self.face is not None: raise ValidateError("face already selected") if not self.card: raise ValidateError("no active card") self.face = self.card.front if not self.face: self.face = self.card.deck.make_face() self.card.set_front(self.face) self.execute(stmt.stmt) self.face = None elif isinstance(stmt, StmtForEach): var = stmt.var old_val = self.env.get(var, None) container = to_list(self.compute(stmt.in_expr)) for elt in container: self.env[var] = elt self.execute(stmt.body) self.env[var] = old_val elif isinstance(stmt, StmtSetName): card = self.get_card() card.name = self.eval(stmt.value) elif isinstance(stmt, StmtSetDescription): card = self.get_card() card.description = self.eval(stmt.value) elif isinstance(stmt, StmtSetVar): var = stmt.var value = self.compute(stmt.value) self.env[var] = value elif isinstance(stmt, StmtIf): if to_number(self.compute(stmt.condition)): self.execute(stmt.body) elif isinstance(stmt, StmtWhile): while to_number(self.compute(stmt.condition)): self.execute(stmt.body) elif isinstance(stmt, StmtCase): for when in stmt.whens: if to_number(self.compute(when.condition)): self.execute(when.body) break else: if stmt.kelse is not None: self.execute(stmt.kelse) elif isinstance(stmt, StmtFor): var = stmt.var old_val = self.env.get(var, None) kfrom = to_number(self.compute(stmt.kfrom)) to = to_number(self.compute(stmt.kto)) step = 1 if stmt.step: step = to_number(self.compute(stmt.step)) value = kfrom if step == 0: raise ValidateError("step is 0") while True: if step > 0: if value > to: break elif step < 0: if value < to: break self.env[var] = value self.execute(stmt.body) value += step self.env[var] = old_val else: raise ValidateError("invalid statement") except ValidateError as ve: raise ValidateError( f"in line {stmt.location[0]}, col {stmt.location[1]}:\n{ve}" ) from ve
def to_list(val): if not isinstance(val, list): raise ValidateError("expected list") return val
def get_face(self) -> CardFaceTemplate: if self.face is None: raise ValidateError("no card face selected") return self.face
def parse_inline(self, inline_elt: Element): params = self.parse_scheme(inline_elt, inline_scheme) name = params['name'] if name in self.inlines: raise ValidateError(f"duplicate inline '{name}'") self.inlines[name] = InlineSymbol(params['name'], params['src'], params.get('offset-y', 0))
def resolve_inline(self, name: str) -> 'InlineSymbol': if name in self.inlines: return self.inlines[name] raise ValidateError(f"inline symbol '{encode(name)}' is not defined")
def get_card(self) -> CardTemplate: if self.card is None: raise ValidateError("no card selected") return self.card
def process_element(self, elt: Element, func: Callable[..., T], *args: Any, **kwargs: Any) -> T: try: return func(elt, *args, **kwargs) except ValidateError as ve: loc = self.getloc(elt) raise ValidateError(f"in <{encode(elt.tag)}> at line {loc[0]}, col {loc[1]}:\n{ve}") from ve
def resolve_style(self, name: str) -> 'TextStyle': if name in self.styles: return self.styles[name] raise ValidateError(f"text style '{encode(name)}' is not defined")
def unexpected_elt(self, elt: Element) -> NoReturn: loc = self.getloc(elt) raise ValidateError(f"unexpected child <{elt.tag}> at line {loc[0]}, col {loc[1]}")
def to_int(val): try: return int(val) except ValueError: raise ValidateError("expected integer")
def parse_color(value): if not re_color.match(value): raise ValidateError("invalid color (expected #RRGGBB or #AARRGGBB)") return value
def error(self, msg: str) -> NoReturn: raise ValidateError(f"at position {self.pos}: {msg}")