Generating Packaged Anki Desk files for Anki space repetition software.
authorOleksandr Gavenko <gavenkoa@gmail.com>
Thu, 15 Sep 2016 19:19:34 +0300
changeset 555 4a3188fc8951
parent 554 59714b9033bc
child 556 78bf1097106a
Generating Packaged Anki Desk files for Anki space repetition software.
Makefile
obsolete/exp_anki.py
py/gadict_srs_anki.py
www/CHANGES.rst
www/INSTALL.rst
--- a/Makefile	Thu Sep 15 17:48:20 2016 +0300
+++ b/Makefile	Thu Sep 15 19:19:34 2016 +0300
@@ -143,6 +143,7 @@
 INDEX_FILES := $(C5_FILES:.c5=.index)
 
 SRS_TAB_FILES := $(patsubst %.gadict,dist/srs/%.tab.txt,$(GADICT_FILES))
+SRS_ANKI_FILES := $(patsubst %.gadict,dist/srs/%.apkg,$(GADICT_FILES))
 
 RST_TMPL_FILE = dist/misc/rst.tmpl
 RST_CSS_FILE = www/tmpl/rst.css
@@ -394,7 +395,10 @@
 	mkdir -p $@
 
 .PHONY: srs
-srs: $(SRS_TAB_FILES)
+srs: $(SRS_ANKI_FILES) $(SRS_TAB_FILES)
+
+dist/srs/%.apkg: %.gadict py/gadict.py py/gadict_srs_anki.py $(MAKEFILE_LIST) | dist/srs/
+	PYTHONPATH=/usr/share/anki: python -B py/gadict_srs_anki.py $< $@
 
 dist/srs/gadict_en-ru+uk.tab.txt: gadict_en-ru+uk.gadict py/gadict.py py/gadict_srs_tab.py $(MAKEFILE_LIST) | dist/srs/
 	python3 -B py/gadict_srs_tab.py  $< $@ ru,uk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/obsolete/exp_anki.py	Thu Sep 15 19:19:34 2016 +0300
@@ -0,0 +1,89 @@
+FONAME = "test.apkg"
+
+# Looks like anki libs change working directory to media directory of current deck
+# Therefore absolute path should be stored before creating temporary deck
+FONAME = os.path.abspath(FONAME)
+FBASENAME, _ = os.path.splitext(os.path.basename(FONAME))
+TMPDIR = tempfile.mkdtemp(dir = os.path.dirname(FONAME))
+
+import anki
+from anki.exporting import AnkiPackageExporter
+
+collection = anki.Collection(os.path.join(TMPDIR, 'collection.anki2'))
+
+deck_id = collection.decks.id(FBASENAME + "_deck")
+deck = collection.decks.get(deck_id)
+# deck = collection.decks.confForDid(deck_id)
+# collection.decks.update(deck)
+# print(dir(deck))
+# print(type(deck))
+# print(deck)
+
+model = collection.models.new(FBASENAME + "_model")
+model['tags'].append(FBASENAME + "_tag")
+model['did'] = deck_id
+model['css'] = """
+.card {
+  font-family: arial;
+  font-size: 20px;
+  text-align: center;
+  color: black;
+  background-color: white;
+}
+.from {
+  font-style: italic;
+}
+"""
+
+collection.models.addField(model, collection.models.newField('en'))
+collection.models.addField(model, collection.models.newField('tr'))
+
+tmpl = collection.models.newTemplate('en -> tr')
+tmpl['qfmt'] = '<div class="from">{{en}}</div>'
+tmpl['afmt'] = '{{FrontSide}}\n\n<hr id=answer>\n\n{{tr}}'
+collection.models.addTemplate(model, tmpl)
+tmpl = collection.models.newTemplate('tr -> en')
+tmpl['qfmt'] = '{{tr}}'
+tmpl['afmt'] = '{{FrontSide}}\n\n<hr id=answer>\n\n<div class="from">{{en}}</div>'
+collection.models.addTemplate(model, tmpl)
+
+
+print(dir(model))
+print(type(model))
+print(model)
+# Equivalent of:
+# collection.models.add(model)
+# without setting auto-generated ID:
+model['id'] = 12345678  # essential for upgrade detection
+collection.models.update(model)
+collection.models.setCurrent(model)
+collection.models.save(model)
+
+# collection.decks.select(deck_id)
+
+note = anki.notes.Note(collection, model)
+print(dir(note))
+print(type(note))
+print(note._fmap)
+print(note)
+
+
+note['en'] = "hello"
+note['tr'] = u"[heləʊ]\nint. привет"
+note.guid = "xxx1"
+collection.addNote(note)
+print(dir(note))
+
+note = collection.newNote()
+note['en'] = "bye"
+note['tr'] = u"[baɪ]\nint. пока"
+note.guid = "xxx2"
+collection.addNote(note)
+
+# model.add(deck)
+# model.save()
+
+export = AnkiPackageExporter(collection)
+export.exportInto(FONAME)
+
+cleanup()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/py/gadict_srs_anki.py	Thu Sep 15 19:19:34 2016 +0300
@@ -0,0 +1,208 @@
+# -*- coding: utf-8 -*-
+
+import os
+import io
+import sys
+import codecs
+import tempfile
+import shutil
+import signal
+
+import gadict
+
+
+FINAME = None
+FONAME = None
+if len(sys.argv) >= 2:
+    FINAME = sys.argv[1]
+if len(sys.argv) >= 3:
+    FONAME = sys.argv[2]
+LANGS = None
+if len(sys.argv) >= 4:
+    LANGS = set(sys.argv[3].split(","))
+
+FIN = io.open(FINAME, mode='r', buffering=1, encoding="utf-8")
+
+PARSER = gadict.Parser()
+try:
+    DOM = PARSER.parse(FIN)
+except gadict.ParseException as ex:
+    sys.stdout.write("{:s}{:s}\n".format(FINAME, repr(ex)))
+    if __debug__:
+        import traceback
+        traceback.print_exc()
+    exit(1)
+finally:
+    FIN.close()
+
+def cleanup(collection, tmpdir):
+    if collection:
+        collection.close()
+    if tmpdir:
+        shutil.rmtree(tmpdir, ignore_errors=True)
+
+# signal.signal(signal.SIGINT, lambda signal, frame: cleanup())
+
+
+if FONAME is None:
+    raise Exception('Missing output file name')
+# Looks like anki libs change working directory to media directory of current deck
+# Therefore absolute path should be stored before creating temporary deck
+FONAME = os.path.abspath(FONAME)
+FBASENAME, _ = os.path.splitext(os.path.basename(FONAME))
+TMPDIR = tempfile.mkdtemp(dir = os.path.dirname(FONAME))
+
+import hashlib
+
+import anki
+from anki.exporting import AnkiPackageExporter
+
+
+class AnkiDbBuilder:
+
+    def __init__(self, tmpdir, name):
+        self.tmpdir = tmpdir
+        self.name = name
+
+        self.collection = collection = anki.Collection(os.path.join(self.tmpdir, 'collection.anki2'))
+
+        deck_id = collection.decks.id(self.name + "_deck")
+        deck = collection.decks.get(deck_id)
+
+        model = collection.models.new(self.name + "_model")
+        model['tags'].append(self.name + "_tag")
+        model['did'] = deck_id
+        model['css'] = """
+.card {
+  font-family: arial;
+  font-size: 20px;
+  text-align: center;
+  color: black;
+  background-color: white;
+}
+span.headword {
+  font-style: italic;
+}
+.pron {
+  color: magenta;
+}
+.pos {
+  color: green;
+  font-style: italic;
+}
+.lang {
+  color: red;
+  font-style: italic;
+}
+.ant {
+  color: red;
+}
+.syn {
+  color: blue;
+}
+"""
+
+        collection.models.addField(model, collection.models.newField('From'))
+        collection.models.addField(model, collection.models.newField('To'))
+
+        tmpl = collection.models.newTemplate('From -> To')
+        tmpl['qfmt'] = '<div class="from">{{From}}</div>'
+        tmpl['afmt'] = '{{FrontSide}}\n\n<hr id=answer>\n\n<div class="to">{{To}}</div>'
+        collection.models.addTemplate(model, tmpl)
+
+        # Equivalent of:
+        #   collection.models.add(model)
+        # without setting auto-generated ID. It is essential to keep model['id']
+        # unchanged between upgrades or notes will be skipped!!
+        model['id'] = int(hashlib.sha1(self.name).hexdigest(), 16) % (2**63)
+        collection.models.update(model)
+        collection.models.setCurrent(model)
+        collection.models.save(model)
+
+    def guid(self, type_, headword):
+        """
+        :type_ used to generate different notes from same headword
+        """
+        h = hashlib.md5(":".join((self.name, type_, headword)))
+        return h.hexdigest()
+
+    def add_note(self, type_, id_, from_, to_):
+        note = self.collection.newNote()
+        note['From'] = from_
+        # print(from_)
+        note['To'] = to_
+        # print(to_)
+        note.guid = self.guid(type_, id_)
+        self.collection.addNote(note)
+
+    def export(self, fname):
+        export = AnkiPackageExporter(self.collection)
+        export.exportInto(fname)
+
+    def close(self):
+        self.collection.close()
+
+try:
+    builder = AnkiDbBuilder(TMPDIR, FBASENAME)
+
+    for (headwords, translations) in DOM[1:]:
+        identity = headwords[0].headword
+        buf = []
+        for hw in headwords:
+            buf.append("<div clsas='headword'>")
+            buf.append("<span clsas='headword'>")
+            buf.append(hw.headword)
+            buf.append("</span>")
+            if hw.pron is not None:
+                buf.append(" <span class='pron'>[")
+                buf.append(hw.pron)
+                buf.append("]</span>")
+            if len(hw.attrs) > 0:
+                l = [u"«"+x+u"»" for x in hw.attrs]
+                l.sort()
+                buf.append("<span class='attrs'>")
+                buf.append(", ".join(l))
+                buf.append("</span>")
+            buf.append("</div>")
+        direct_from = "".join(buf)
+        buf = []
+        for sense in translations:
+            buf.append("<div class='sense'>")
+            if sense.pos:
+                buf.append("<span class='pos'>")
+                buf.append(sense.pos)
+                buf.append("</span>")
+            if sense.ant_list and len(sense.ant_list) > 0:
+                buf.append("<span class='ant'>ant: ")
+                buf.append("; ".join(["{"+s+"}" for s in sense.ant_list]))
+                buf.append("</span>")
+            if sense.syn_list and len(sense.syn_list) > 0:
+                buf.append("<span class='syn'>syn: ")
+                buf.append("; ".join(["{"+s+"}" for s in sense.syn_list]))
+                buf.append("</span>")
+            if len(sense.tr_list) > 1:
+                for (lang, tr) in sense.tr_list:
+                    buf.append("<div class='sense'>")
+                    buf.append(" <span class='lang'>")
+                    buf.append(lang)
+                    buf.append("</span>")
+                    buf.append(" <span class='tr'>")
+                    buf.append(tr)
+                    buf.append("</span>")
+                    buf.append("</div>")
+            else:
+                for (lang, tr) in sense.tr_list:
+                    buf.append(" <span class='lang'>")
+                    buf.append(lang)
+                    buf.append("</span>")
+                    buf.append(" <span class='tr'>")
+                    buf.append(tr)
+                    buf.append("</span>")
+        direct_to = "".join(buf)
+        builder.add_note("en->tr", identity, direct_from, direct_to)
+        builder.add_note("tr->en", identity, direct_to, direct_from)
+
+    builder.export(FONAME)
+finally:
+    builder.close()
+    shutil.rmtree(TMPDIR, ignore_errors=True)
--- a/www/CHANGES.rst	Thu Sep 15 17:48:20 2016 +0300
+++ b/www/CHANGES.rst	Thu Sep 15 19:19:34 2016 +0300
@@ -19,6 +19,11 @@
        0.3      800          n/a
    ======= ======== ============
 
+v0.9, 2016-09-xx
+================
+
+ * Generating Packaged Anki Desk files for Anki space repetition software.
+
 v0.8, 2016-09-11
 ================
 
--- a/www/INSTALL.rst	Thu Sep 15 17:48:20 2016 +0300
+++ b/www/INSTALL.rst	Thu Sep 15 19:19:34 2016 +0300
@@ -23,6 +23,10 @@
 * Documentation in plain text in ``/doc/txt`` directory (with extension
   ``.rst``) and in HTML in ``/doc/html`` directory (with extension ``.html``).
 
+* Import files in Packaged Anki Desk format for Anki and AnkiDroid software in
+  ``/srs`` directory (files with extension ``.apkg``). Do not forget to enable
+  random order of cards review!
+
 * Import files in TAB format for SRS software in ``/srs`` directory (files with
   extension ``.tab.txt``). Anki, Mnemosyne, Anymemo and many other SRS packages
   able to import TAB files. Don't forget to enable HTML markup on import!