From 5a0e4cd17b4635d5365e43af5e1ea8a992adcbaf Mon Sep 17 00:00:00 2001 From: Al-Hassan Abdel-Raouf Date: Sun, 13 Sep 2020 02:45:22 +0200 Subject: [PATCH 1/5] add support to import Anki Decks --- drillsrs/cmd/import_.py | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/drillsrs/cmd/import_.py b/drillsrs/cmd/import_.py index d65da96..53a8478 100644 --- a/drillsrs/cmd/import_.py +++ b/drillsrs/cmd/import_.py @@ -1,14 +1,36 @@ import argparse import json import sys +import io from typing import IO, Any, Optional from dateutil.parser import parse as parse_date +from anki_export import ApkgReader +from lxml import etree from drillsrs import db, scheduler, util from drillsrs.cmd.command_base import CommandBase +def tojson(filepath): + with ApkgReader(filepath) as apkg: + temp = apkg.export() + temp = temp[list(temp)[0]] + x = {"name":temp[1][3], "description":None, "tags":[], "cards":[]} + counter = 1 + for s in temp[1:]: + card = {"active":False,"activation_date":None,"tags":[],"user_answers":[]} + card["id"] = counter + q = etree.HTML(s[0]) + card["question"] = etree.tostring(q, encoding='unicode', method='text') + a = etree.HTML(s[1]) + card["answers"] = [" "] if a is None else [etree.tostring(a, encoding='unicode', method='text')] + counter = counter + 1 + x["cards"].append(card) + return(json.dumps(x)) + + + def _import(handle: IO[Any]) -> None: with db.session_scope() as session: deck_obj = json.load(handle) @@ -70,11 +92,21 @@ def decorate_arg_parser(self, parser: argparse.ArgumentParser) -> None: nargs="?", help="path to import from; if omitted, standard input is used", ) + parser.add_argument( + "-a", + action="store_true", + help="Import Anki Deck", + ) def run(self, args: argparse.Namespace) -> None: path: Optional[str] = args.path + anki = args.a if path: - with open(path, "r") as handle: + if anki: + handle = io.StringIO(tojson(path)) _import(handle) + else: + with open(path, "r") as handle: + _import(handle) else: _import(sys.stdin) From 49044a6001bbdc023ddf427dc712cfba564742a0 Mon Sep 17 00:00:00 2001 From: Al-Hassan Abdel-Raouf Date: Sun, 13 Sep 2020 02:46:30 +0200 Subject: [PATCH 2/5] add needed packages for anki import --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 55e90dd..492c4e7 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ url="https://github.com/rr-/drill", packages=find_packages(), entry_points={"console_scripts": ["drill-srs = drillsrs.__main__:main"]}, - install_requires=["xdg", "python-dateutil", "sqlalchemy", "jinja2",], + install_requires=["xdg", "python-dateutil", "sqlalchemy", "jinja2", "lxml", "anki_export"], package_dir={"drillsrs": "drillsrs"}, package_data={"drillsrs": ["data/*.*"]}, classifiers=[ From 4605849bdf46e6e73d0952be8d8391f999dc8bd1 Mon Sep 17 00:00:00 2001 From: Al-Hassan Abdel-Raouf Date: Sun, 13 Sep 2020 02:53:58 +0200 Subject: [PATCH 3/5] small typo in setup file --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 492c4e7..e6df02f 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ url="https://github.com/rr-/drill", packages=find_packages(), entry_points={"console_scripts": ["drill-srs = drillsrs.__main__:main"]}, - install_requires=["xdg", "python-dateutil", "sqlalchemy", "jinja2", "lxml", "anki_export"], + install_requires=["xdg", "python-dateutil", "sqlalchemy", "jinja2", "lxml", "anki-export"], package_dir={"drillsrs": "drillsrs"}, package_data={"drillsrs": ["data/*.*"]}, classifiers=[ From 765ef0eabac6df96cdb0d5d822c19be4b12bd8f2 Mon Sep 17 00:00:00 2001 From: Al-Hassan Abdel-Raouf Date: Mon, 14 Sep 2020 02:19:05 +0200 Subject: [PATCH 4/5] fix formating and make anki packages optional --- drillsrs/cmd/import_.py | 40 +++++++++++++++++++++++++++------------- setup.py | 5 ++++- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/drillsrs/cmd/import_.py b/drillsrs/cmd/import_.py index 53a8478..ea183c6 100644 --- a/drillsrs/cmd/import_.py +++ b/drillsrs/cmd/import_.py @@ -5,30 +5,46 @@ from typing import IO, Any, Optional from dateutil.parser import parse as parse_date -from anki_export import ApkgReader -from lxml import etree from drillsrs import db, scheduler, util from drillsrs.cmd.command_base import CommandBase -def tojson(filepath): +def tojson(filepath: str) -> str: + try: + from anki_export import ApkgReader + except ImportError: + print("must install anki_export package") + exit() + try: + from lxml import etree + except ImportError: + print("must install lxml package") + exit() with ApkgReader(filepath) as apkg: temp = apkg.export() temp = temp[list(temp)[0]] - x = {"name":temp[1][3], "description":None, "tags":[], "cards":[]} + x = {"name": temp[1][3], "description": None, "tags": [], "cards": []} counter = 1 for s in temp[1:]: - card = {"active":False,"activation_date":None,"tags":[],"user_answers":[]} + card = { + "active": False, + "activation_date": None, + "tags": [], + "user_answers": [], + } card["id"] = counter q = etree.HTML(s[0]) - card["question"] = etree.tostring(q, encoding='unicode', method='text') + card["question"] = etree.tostring(q, encoding="unicode", method="text") a = etree.HTML(s[1]) - card["answers"] = [" "] if a is None else [etree.tostring(a, encoding='unicode', method='text')] + card["answers"] = ( + [" "] + if a is None + else [etree.tostring(a, encoding="unicode", method="text")] + ) counter = counter + 1 x["cards"].append(card) - return(json.dumps(x)) - + return json.dumps(x) def _import(handle: IO[Any]) -> None: @@ -70,9 +86,7 @@ def _import(handle: IO[Any]) -> None: card.user_answers.append(user_answer) if "activation_date" in card_obj: if card_obj["activation_date"]: - card.activation_date = parse_date( - card_obj["activation_date"] - ) + card.activation_date = parse_date(card_obj["activation_date"]) elif card.user_answers: card.activation_date = sorted( card.user_answers, key=lambda ua: ua.date @@ -100,7 +114,7 @@ def decorate_arg_parser(self, parser: argparse.ArgumentParser) -> None: def run(self, args: argparse.Namespace) -> None: path: Optional[str] = args.path - anki = args.a + anki: Optional[bool] = args.a if path: if anki: handle = io.StringIO(tojson(path)) diff --git a/setup.py b/setup.py index e6df02f..2a2d4f5 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,10 @@ url="https://github.com/rr-/drill", packages=find_packages(), entry_points={"console_scripts": ["drill-srs = drillsrs.__main__:main"]}, - install_requires=["xdg", "python-dateutil", "sqlalchemy", "jinja2", "lxml", "anki-export"], + install_requires=["xdg", "python-dateutil", "sqlalchemy", "jinja2"], + extras_require={ + "anki": ["lxml", "anki-export"], + }, package_dir={"drillsrs": "drillsrs"}, package_data={"drillsrs": ["data/*.*"]}, classifiers=[ From 5961cfc7a95e6ae4e0c98e489fea5181ab9d9d58 Mon Sep 17 00:00:00 2001 From: Al-Hassan Abdel-Raouf Date: Mon, 14 Sep 2020 11:57:03 +0200 Subject: [PATCH 5/5] changed anki import flag --- drillsrs/cmd/import_.py | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/drillsrs/cmd/import_.py b/drillsrs/cmd/import_.py index ea183c6..2f5762c 100644 --- a/drillsrs/cmd/import_.py +++ b/drillsrs/cmd/import_.py @@ -10,7 +10,7 @@ from drillsrs.cmd.command_base import CommandBase -def tojson(filepath: str) -> str: +def apkg_to_json(filepath: str) -> str: try: from anki_export import ApkgReader except ImportError: @@ -24,9 +24,8 @@ def tojson(filepath: str) -> str: with ApkgReader(filepath) as apkg: temp = apkg.export() temp = temp[list(temp)[0]] - x = {"name": temp[1][3], "description": None, "tags": [], "cards": []} - counter = 1 - for s in temp[1:]: + ret = {"name": temp[1][3], "description": None, "tags": [], "cards": []} + for counter, anki_card in enumerate(temp[1:], start=1): card = { "active": False, "activation_date": None, @@ -34,17 +33,16 @@ def tojson(filepath: str) -> str: "user_answers": [], } card["id"] = counter - q = etree.HTML(s[0]) - card["question"] = etree.tostring(q, encoding="unicode", method="text") - a = etree.HTML(s[1]) + question = etree.HTML(anki_card[0]) + card["question"] = etree.tostring(question, encoding="unicode", method="text") + answer = etree.HTML(anki_card[1]) card["answers"] = ( [" "] - if a is None - else [etree.tostring(a, encoding="unicode", method="text")] + if answer is None + else [etree.tostring(answer, encoding="unicode", method="text")] ) - counter = counter + 1 - x["cards"].append(card) - return json.dumps(x) + ret["cards"].append(card) + return json.dumps(ret) def _import(handle: IO[Any]) -> None: @@ -107,17 +105,17 @@ def decorate_arg_parser(self, parser: argparse.ArgumentParser) -> None: help="path to import from; if omitted, standard input is used", ) parser.add_argument( - "-a", + "--anki", action="store_true", - help="Import Anki Deck", + help="import anki deck", ) def run(self, args: argparse.Namespace) -> None: path: Optional[str] = args.path - anki: Optional[bool] = args.a + anki: Optional[bool] = args.anki if path: if anki: - handle = io.StringIO(tojson(path)) + handle = io.StringIO(apkg_to_json(path)) _import(handle) else: with open(path, "r") as handle: