# HG changeset patch # User Oleksandr Gavenko # Date 1473956374 -10800 # Node ID 4a3188fc8951adbea1f2eebd081aab9de0b2ee43 # Parent 59714b9033bc319305ba7bda13da1851a96e33e6 Generating Packaged Anki Desk files for Anki space repetition software. diff -r 59714b9033bc -r 4a3188fc8951 Makefile --- 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 diff -r 59714b9033bc -r 4a3188fc8951 obsolete/exp_anki.py --- /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'] = '
{{en}}
' +tmpl['afmt'] = '{{FrontSide}}\n\n
\n\n{{tr}}' +collection.models.addTemplate(model, tmpl) +tmpl = collection.models.newTemplate('tr -> en') +tmpl['qfmt'] = '{{tr}}' +tmpl['afmt'] = '{{FrontSide}}\n\n
\n\n
{{en}}
' +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() diff -r 59714b9033bc -r 4a3188fc8951 py/gadict_srs_anki.py --- /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'] = '
{{From}}
' + tmpl['afmt'] = '{{FrontSide}}\n\n
\n\n
{{To}}
' + 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("
") + buf.append("") + buf.append(hw.headword) + buf.append("") + if hw.pron is not None: + buf.append(" [") + buf.append(hw.pron) + buf.append("]") + if len(hw.attrs) > 0: + l = [u"«"+x+u"»" for x in hw.attrs] + l.sort() + buf.append("") + buf.append(", ".join(l)) + buf.append("") + buf.append("
") + direct_from = "".join(buf) + buf = [] + for sense in translations: + buf.append("
") + if sense.pos: + buf.append("") + buf.append(sense.pos) + buf.append("") + if sense.ant_list and len(sense.ant_list) > 0: + buf.append("ant: ") + buf.append("; ".join(["{"+s+"}" for s in sense.ant_list])) + buf.append("") + if sense.syn_list and len(sense.syn_list) > 0: + buf.append("syn: ") + buf.append("; ".join(["{"+s+"}" for s in sense.syn_list])) + buf.append("") + if len(sense.tr_list) > 1: + for (lang, tr) in sense.tr_list: + buf.append("
") + buf.append(" ") + buf.append(lang) + buf.append("") + buf.append(" ") + buf.append(tr) + buf.append("") + buf.append("
") + else: + for (lang, tr) in sense.tr_list: + buf.append(" ") + buf.append(lang) + buf.append("") + buf.append(" ") + buf.append(tr) + buf.append("") + 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) diff -r 59714b9033bc -r 4a3188fc8951 www/CHANGES.rst --- 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 ================ diff -r 59714b9033bc -r 4a3188fc8951 www/INSTALL.rst --- 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!