# Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html # pylint: skip-file from dataclasses import dataclass from typing import Dict, List, Union import ankirspy # pytype: disable=import-error import anki.backend_pb2 as pb import anki.buildinfo from .types import AllTemplateReqs assert ankirspy.buildhash() == anki.buildinfo.buildhash SchedTimingToday = pb.SchedTimingTodayOut class BackendException(Exception): def __str__(self) -> str: err: pb.BackendError = self.args[0] # pylint: disable=unsubscriptable-object kind = err.WhichOneof("value") if kind == "invalid_input": return f"invalid input: {err.invalid_input.info}" elif kind == "template_parse": return f"template parse: {err.template_parse.info}" else: return f"unhandled error: {err}" def proto_template_reqs_to_legacy( reqs: List[pb.TemplateRequirement], ) -> AllTemplateReqs: legacy_reqs = [] for (idx, req) in enumerate(reqs): kind = req.WhichOneof("value") # fixme: sorting is for the unit tests - should check if any # code depends on the order if kind == "any": legacy_reqs.append((idx, "any", sorted(req.any.ords))) elif kind == "all": legacy_reqs.append((idx, "all", sorted(req.all.ords))) else: l: List[int] = [] legacy_reqs.append((idx, "none", l)) return legacy_reqs @dataclass class TemplateReplacement: field_name: str filters: List[str] class RustBackend: def __init__(self, path: str): self._backend = ankirspy.Backend(path) def _run_command(self, input: pb.BackendInput) -> pb.BackendOutput: input_bytes = input.SerializeToString() output_bytes = self._backend.command(input_bytes) output = pb.BackendOutput() output.ParseFromString(output_bytes) kind = output.WhichOneof("value") if kind == "error": raise BackendException(output.error) else: return output def plus_one(self, num: int) -> int: input = pb.BackendInput(plus_one=pb.PlusOneIn(num=num)) output = self._run_command(input) return output.plus_one.num def template_requirements( self, template_fronts: List[str], field_map: Dict[str, int] ) -> AllTemplateReqs: input = pb.BackendInput( template_requirements=pb.TemplateRequirementsIn( template_front=template_fronts, field_names_to_ordinals=field_map ) ) output = self._run_command(input).template_requirements reqs: List[pb.TemplateRequirement] = output.requirements # type: ignore return proto_template_reqs_to_legacy(reqs) def sched_timing_today( self, created_secs: int, created_mins_west: int, now_secs: int, now_mins_west: int, rollover: int, ) -> SchedTimingToday: return self._run_command( pb.BackendInput( sched_timing_today=pb.SchedTimingTodayIn( created_secs=created_secs, created_mins_west=created_mins_west, now_secs=now_secs, now_mins_west=now_mins_west, rollover_hour=rollover, ) ) ).sched_timing_today def flatten_template( self, template: str, nonempty_fields: List[str] ) -> List[Union[str, TemplateReplacement]]: out = self._run_command( pb.BackendInput( flatten_template=pb.FlattenTemplateIn( template_text=template, nonempty_field_names=nonempty_fields ) ) ).flatten_template results: List[Union[str, TemplateReplacement]] = [] for node in out.nodes: if node.WhichOneof("value") == "text": results.append(node.text) else: results.append( TemplateReplacement( field_name=node.replacement.field_name, filters=list(node.replacement.filters), ) ) return results