py/gadict_srs_anki.py
changeset 677 e330e7f195b7
parent 676 e3266e096360
child 678 28e04408c0c0
equal deleted inserted replaced
676:e3266e096360 677:e330e7f195b7
   123 
   123 
   124 import anki
   124 import anki
   125 from anki.exporting import AnkiPackageExporter
   125 from anki.exporting import AnkiPackageExporter
   126 
   126 
   127 
   127 
   128 class AnkiDbBuilder:
   128 MODEL_CSS = """
   129 
       
   130     def __init__(self, tmpdir, name):
       
   131         self.tmpdir = tmpdir
       
   132         self.name = name
       
   133 
       
   134         self.collection = collection = anki.Collection(os.path.join(self.tmpdir, 'collection.anki2'))
       
   135 
       
   136         deck_id = collection.decks.id(self.name + "_deck")
       
   137         deck = collection.decks.get(deck_id)
       
   138 
       
   139         model = collection.models.new(self.name + "_model")
       
   140         model['did'] = deck_id
       
   141         model['css'] = """
       
   142 .card {
   129 .card {
   143   font-family: arial;
   130   font-family: arial;
   144   font-size: 20px;
   131   font-size: 20px;
   145   text-align: center;
   132   text-align: center;
   146   color: black;
   133   color: black;
   203   color: red;
   190   color: red;
   204   font-weight: bold;
   191   font-weight: bold;
   205 }
   192 }
   206 """
   193 """
   207 
   194 
   208         collection.models.addField(model, collection.models.newField('From'))
   195 def note_add_tags(note, tags):
   209         collection.models.addField(model, collection.models.newField('To'))
   196     if isinstance(tags, str):       note.tags = [tags]
   210 
   197     elif isinstance(tags, list):    note.tags = tags
   211         tmpl = collection.models.newTemplate('From -> To')
   198     elif tags is None:              pass
   212         tmpl['qfmt'] = '<div class="from">{{From}}</div>'
   199     else:                           raise Exception('Expecting string or list of tags...')
   213         tmpl['afmt'] = '{{FrontSide}}<hr id=answer><div class="to">{{To}}</div>'
   200 
   214         collection.models.addTemplate(model, tmpl)
   201 
   215 
   202 class AnkiDbBuilder:
   216         # Equivalent of:
   203 
   217         #   collection.models.add(model)
   204     def __init__(self, tmpdir, name):
   218         # without setting auto-generated ID. It is essential to keep model['id']
   205         self.tmpdir = tmpdir
   219         # unchanged between upgrades or notes will be skipped!!
   206         self.name = name
   220         model['id'] = int(hashlib.sha1(self.name).hexdigest(), 16) % (2**63)
   207 
       
   208         self.collection = collection = anki.Collection(os.path.join(self.tmpdir, 'collection.anki2'))
       
   209 
       
   210         deck_id = collection.decks.id(self.name)
       
   211         deck = collection.decks.get(deck_id)
       
   212 
       
   213         # It is essential to keep model['id'] unchanged between upgrades!!
       
   214         MODEL_ID = int(hashlib.sha1(self.name).hexdigest(), 16) % (2**63)
       
   215 
       
   216         ################################################################
       
   217         # Regular card model. SafeBack doesn't include examples to not spoil
       
   218         # word spelling.
       
   219         model = collection.models.new(self.name + "_frontback")
       
   220         model['did'] = deck_id
       
   221         model['css'] = MODEL_CSS
       
   222 
       
   223         collection.models.addField(model, collection.models.newField('Front'))
       
   224         collection.models.addField(model, collection.models.newField('Back'))
       
   225         collection.models.addField(model, collection.models.newField('SafeBack'))
       
   226 
       
   227         tmpl = collection.models.newTemplate('Front -> Back')
       
   228         tmpl['qfmt'] = '<div class="front">{{Front}}</div>'
       
   229         tmpl['afmt'] = '{{FrontSide}}<hr id=answer><div class="back">{{Back}}</div>'
       
   230         collection.models.addTemplate(model, tmpl)
       
   231 
       
   232         tmpl = collection.models.newTemplate('SafeBack -> Front')
       
   233         tmpl['qfmt'] = '<div class="safe-back">{{SafeBack}}</div>'
       
   234         tmpl['afmt'] = '{{FrontSide}}<hr id=answer><div class="front">{{Front}}</div>'
       
   235         collection.models.addTemplate(model, tmpl)
       
   236 
       
   237         # Equivalent of ``collection.models.add(model)`` without setting
       
   238         # auto-generated ID.
       
   239         # Increment +1 is for keeping model['id'] unique from previous v0.9 release.
       
   240         model['id'] = MODEL_ID + 1
   221         collection.models.update(model)
   241         collection.models.update(model)
   222         collection.models.setCurrent(model)
       
   223         collection.models.save(model)
   242         collection.models.save(model)
   224 
   243         self.model = model
   225     def guid(self, type_, headword):
   244         # collection.models.setCurrent(model)
       
   245 
       
   246         if not RICH_MODE:
       
   247             return
       
   248 
       
   249         ################################################################
       
   250         # Model for irregular verbs.
       
   251         model = collection.models.new(self.name + "_irrverb")
       
   252         model['did'] = deck_id
       
   253         model['css'] = MODEL_CSS
       
   254 
       
   255         collection.models.addField(model, collection.models.newField('V1'))
       
   256         collection.models.addField(model, collection.models.newField('V2'))
       
   257         collection.models.addField(model, collection.models.newField('V3'))
       
   258         collection.models.addField(model, collection.models.newField('V2alt'))
       
   259         collection.models.addField(model, collection.models.newField('V3alt'))
       
   260         collection.models.addField(model, collection.models.newField('Front'))
       
   261         collection.models.addField(model, collection.models.newField('Back'))
       
   262 
       
   263         question = u"<div class='ask'>Find irregular verb:</div>"
       
   264 
       
   265         tmpl = collection.models.newTemplate('V1 -> Back')
       
   266         tmpl['qfmt'] = question + '<div class="front">{{V1}}</div>'
       
   267         tmpl['afmt'] = '{{FrontSide}}<hr id=answer><div class="back">{{Front}}</div><div class="back">{{Back}}</div>'
       
   268         collection.models.addTemplate(model, tmpl)
       
   269 
       
   270         tmpl = collection.models.newTemplate('V2 -> Back')
       
   271         tmpl['qfmt'] = question + '<div class="front">{{V2}}</div>'
       
   272         tmpl['afmt'] = '{{FrontSide}}<hr id=answer><div class="back">{{Front}}</div><div class="back">{{Back}}</div>'
       
   273         collection.models.addTemplate(model, tmpl)
       
   274 
       
   275         tmpl = collection.models.newTemplate('V3 -> Back')
       
   276         tmpl['qfmt'] = question + '<div class="front">{{V3}}</div>'
       
   277         tmpl['afmt'] = '{{FrontSide}}<hr id=answer><div class="back">{{Front}}</div><div class="back">{{Back}}</div>'
       
   278         collection.models.addTemplate(model, tmpl)
       
   279 
       
   280         tmpl = collection.models.newTemplate('V2alt -> Back')
       
   281         tmpl['qfmt'] = question + '<div class="front">{{V2alt}}</div>'
       
   282         tmpl['afmt'] = '{{FrontSide}}<hr id=answer><div class="back">{{Front}}</div><div class="back">{{Back}}</div>'
       
   283         collection.models.addTemplate(model, tmpl)
       
   284 
       
   285         tmpl = collection.models.newTemplate('V3alt -> Back')
       
   286         tmpl['qfmt'] = question + '<div class="front">{{V3alt}}</div>'
       
   287         tmpl['afmt'] = '{{FrontSide}}<hr id=answer><div class="back">{{Front}}</div><div class="back">{{Back}}</div>'
       
   288         collection.models.addTemplate(model, tmpl)
       
   289 
       
   290         model['id'] = MODEL_ID + 2          # Keep model['id'] unique.
       
   291         collection.models.update(model)
       
   292         collection.models.save(model)
       
   293         self.model_irr = model
       
   294 
       
   295         ################################################################
       
   296         # Model for irregular plurals.
       
   297         model = collection.models.new(self.name + "_plural")
       
   298         model['did'] = deck_id
       
   299         model['css'] = MODEL_CSS
       
   300 
       
   301         collection.models.addField(model, collection.models.newField('Singular'))
       
   302         collection.models.addField(model, collection.models.newField('Plural'))
       
   303         collection.models.addField(model, collection.models.newField('Front'))
       
   304         collection.models.addField(model, collection.models.newField('Back'))
       
   305 
       
   306         question = u"<div class='ask'>Find singular/plural form:</div>"
       
   307 
       
   308         tmpl = collection.models.newTemplate('Singular -> Back')
       
   309         tmpl['qfmt'] = question + '<div class="front">{{Singular}}</div>'
       
   310         tmpl['afmt'] = '{{FrontSide}}<hr id=answer><div class="back">{{Front}}</div><div class="back">{{Back}}</div>'
       
   311         collection.models.addTemplate(model, tmpl)
       
   312 
       
   313         tmpl = collection.models.newTemplate('Plural -> Back')
       
   314         tmpl['qfmt'] = question + '<div class="front">{{Plural}}</div>'
       
   315         tmpl['afmt'] = '{{FrontSide}}<hr id=answer><div class="back">{{Front}}</div><div class="back">{{Back}}</div>'
       
   316         collection.models.addTemplate(model, tmpl)
       
   317 
       
   318         model['id'] = MODEL_ID + 3          # Keep model['id'] unique.
       
   319         collection.models.update(model)
       
   320         collection.models.save(model)
       
   321         self.model_pl = model
       
   322 
       
   323 
       
   324     def guid(self, nodetype, headword):
   226         """
   325         """
   227         :type_ used to generate different notes from same headword
   326         :nodetype  used to generate different notes from same headword
   228         """
   327         """
   229         h = hashlib.md5(":".join((self.name, type_, headword)))
   328         h = hashlib.md5(":".join((self.name, nodetype, headword)))
   230         return h.hexdigest()
   329         return h.hexdigest()
   231 
   330 
   232     def add_note(self, type_, id_, from_, to_, tags_ = None):
   331     def add_note(self, headword, front, back, safeback, tags = None):
   233         note = self.collection.newNote()
   332         note = anki.notes.Note(self.collection, self.model)
   234         note['From'] = from_
   333         note['Front'] = front
   235         # print(from_)
   334         note['Back'] = back
   236         note['To'] = to_
   335         note['SafeBack'] = safeback
   237         # print(to_)
   336         note_add_tags(note, tags)
   238         if isinstance(tags_, str):      note.tags = [tags_]
   337         note.guid = self.guid("front/back", headword)
   239         elif isinstance(tags_, list):   note.tags = tags_
   338         self.collection.addNote(note)
   240         elif tags_ is None:             pass
   339 
   241         else:                           raise Exception('Expect string or list of tags...')
   340     def add_note_irr(self, headword, v1, v2, v2alt, v3, v3alt, front, back, tags = None):
   242         note.guid = self.guid(type_, id_)
   341         note = anki.notes.Note(self.collection, self.model_irr)
       
   342         note['V1'] = v1
       
   343         note['V2'] = v2
       
   344         note['V3'] = v3
       
   345         note['V2alt'] = v2alt
       
   346         note['V3alt'] = v3alt
       
   347         note['Front'] = front
       
   348         note['Back'] = back
       
   349         note_add_tags(note, tags)
       
   350         note.guid = self.guid("irregular verb", headword)
       
   351         self.collection.addNote(note)
       
   352 
       
   353     def add_note_pl(self, headword, singular, plural, front, back, tags = None):
       
   354         note = anki.notes.Note(self.collection, self.model_pl)
       
   355         note['Singular'] = singular
       
   356         note['Plural'] = plural
       
   357         note['Front'] = front
       
   358         note['Back'] = back
       
   359         note_add_tags(note, tags)
       
   360         note.guid = self.guid("singular/plural noun", headword)
   243         self.collection.addNote(note)
   361         self.collection.addNote(note)
   244 
   362 
   245     def export(self, fname):
   363     def export(self, fname):
   246         export = AnkiPackageExporter(self.collection)
   364         export = AnkiPackageExporter(self.collection)
   247         export.exportInto(fname)
   365         export.exportInto(fname)
   315     builder = AnkiDbBuilder(TMPDIR, NAME)
   433     builder = AnkiDbBuilder(TMPDIR, NAME)
   316 
   434 
   317     for identity in FDEL or []:
   435     for identity in FDEL or []:
   318         identity = identity.strip()
   436         identity = identity.strip()
   319         warnmsg = "<div class='del'>Please delete this note ({})</div>".format(identity)
   437         warnmsg = "<div class='del'>Please delete this note ({})</div>".format(identity)
   320         builder.add_note("en->tr", identity, warnmsg, warnmsg+" en->tr", "del")
   438         builder.add_note(identity, warnmsg, warnmsg, warnmsg, "del")
   321         builder.add_note("tr->en", identity, warnmsg, warnmsg+" tr->en", "del")
   439         builder.add_note_irr(identity, warnmsg, warnmsg, warnmsg, warnmsg, warnmsg, warnmsg, warnmsg, "del")
   322         builder.add_note("irregular1", identity, warnmsg, warnmsg+" irregular1", "del")
   440         builder.add_note_pl(identity, warnmsg, warnmsg, warnmsg, warnmsg, "del")
   323         builder.add_note("irregular2", identity, warnmsg, warnmsg+" irregular2", "del")
       
   324         builder.add_note("irregular3", identity, warnmsg, warnmsg+" irregular3", "del")
       
   325         builder.add_note("singular", identity, warnmsg, warnmsg+" singular", "del")
       
   326         builder.add_note("plural", identity, warnmsg, warnmsg+" plural", "del")
       
   327 
   441 
   328     for (headwords, translations) in DOM[1:]:
   442     for (headwords, translations) in DOM[1:]:
   329         identity = headwords[0].headword
   443         identity = headwords[0].headword
   330         if 'rare' in headwords[0].attrs:
   444         if 'rare' in headwords[0].attrs:
   331             continue
   445             continue
   336         freqmsg = ""
   450         freqmsg = ""
   337         if len(freqtags) > 0:
   451         if len(freqtags) > 0:
   338             freqmsg = ",".join(freqtags)
   452             freqmsg = ",".join(freqtags)
   339             freqmsg = "<div class='freq'>{:s}</div>".format(freqmsg)
   453             freqmsg = "<div class='freq'>{:s}</div>".format(freqmsg)
   340         buf = []
   454         buf = []
   341         v1, v2, v3 = (None, None, None)
   455         v1, v2, v3, v2alt, v3alt = (None, None, None, None, None)
   342         singular, plural = (None, None)
   456         singular, plural = (None, None)
   343         for hw in headwords:
   457         for hw in headwords:
   344             buf.append("<div clsas='headword'>")
   458             buf.append("<div clsas='headword'>")
   345             buf.append("<span clsas='headword'>")
   459             buf.append("<span clsas='headword'>")
   346             buf.append(hw.headword)
   460             buf.append(hw.headword)
   356                 buf.append(", ".join(l))
   470                 buf.append(", ".join(l))
   357                 buf.append("</span>")
   471                 buf.append("</span>")
   358             if 'v1' in hw.attrs:
   472             if 'v1' in hw.attrs:
   359                 v1 = (hw.headword, hw.pron)
   473                 v1 = (hw.headword, hw.pron)
   360             if 'v2' in hw.attrs:
   474             if 'v2' in hw.attrs:
   361                 v2 = (hw.headword, hw.pron)
   475                 if v2:
       
   476                     v2alt = (hw.headword, hw.pron)
       
   477                 else:
       
   478                     v2 = (hw.headword, hw.pron)
   362             if 'v3' in hw.attrs:
   479             if 'v3' in hw.attrs:
   363                 v3 = (hw.headword, hw.pron)
   480                 if v3:
       
   481                     v3alt = (hw.headword, hw.pron)
       
   482                 else:
       
   483                     v3 = (hw.headword, hw.pron)
   364             if 's' in hw.attrs:
   484             if 's' in hw.attrs:
   365                 singular = (hw.headword, hw.pron)
   485                 singular = (hw.headword, hw.pron)
   366             if 'pl' in hw.attrs:
   486             if 'pl' in hw.attrs:
   367                 plural = (hw.headword, hw.pron)
   487                 plural = (hw.headword, hw.pron)
   368             buf.append("</div>")
   488             buf.append("</div>")
   373         direct_to = "".join(buf)
   493         direct_to = "".join(buf)
   374         buf = []
   494         buf = []
   375         for sense in translations:
   495         for sense in translations:
   376             write_sense(buf, sense, with_examples = False)
   496             write_sense(buf, sense, with_examples = False)
   377         reverse_from = "".join(buf)         # without examples!!
   497         reverse_from = "".join(buf)         # without examples!!
   378         builder.add_note("en->tr", identity, direct_from + freqmsg, direct_to)
   498         builder.add_note(identity, direct_from, direct_to, reverse_from)
   379         builder.add_note("tr->en", identity, reverse_from, direct_from + freqmsg)
       
   380         if v1 and v2 and v3 and RICH_MODE:
   499         if v1 and v2 and v3 and RICH_MODE:
   381             question = u"<div class='ask'>Find irregular verb:</div>"
       
   382             riddle1 = u"<span class='headword'>{}</span> <span class='pron'>[{}]</span> <span class='attrs'>v1</span>".format(v1[0], v1[1])
   500             riddle1 = u"<span class='headword'>{}</span> <span class='pron'>[{}]</span> <span class='attrs'>v1</span>".format(v1[0], v1[1])
   383             riddle2 = u"<span class='headword'>{}</span> <span class='pron'>[{}]</span> <span class='attrs'>v2</span>".format(v2[0], v2[1])
   501             riddle2 = u"<span class='headword'>{}</span> <span class='pron'>[{}]</span> <span class='attrs'>v2</span>".format(v2[0], v2[1])
   384             riddle3 = u"<span class='headword'>{}</span> <span class='pron'>[{}]</span> <span class='attrs'>v3</span>".format(v3[0], v3[1])
   502             riddle3 = u"<span class='headword'>{}</span> <span class='pron'>[{}]</span> <span class='attrs'>v3</span>".format(v3[0], v3[1])
   385             answer = direct_from + direct_to
   503             if v2alt:
   386             builder.add_note("irregular1", identity, question + riddle1 + freqmsg, answer)
   504                 riddle2alt = u"<span class='headword'>{}</span> <span class='pron'>[{}]</span> <span class='attrs'>v2</span>".format(v2alt[0], v2alt[1])
   387             builder.add_note("irregular2", identity, question + riddle2 + freqmsg, answer)
   505             else:
   388             builder.add_note("irregular3", identity, question + riddle3 + freqmsg, answer)
   506                 riddle2alt = ""
       
   507             if v3alt:
       
   508                 riddle3alt = u"<span class='headword'>{}</span> <span class='pron'>[{}]</span> <span class='attrs'>v2</span>".format(v3alt[0], v3alt[1])
       
   509             else:
       
   510                 riddle3alt = ""
       
   511             builder.add_note_irr(identity, riddle1 + freqmsg, riddle2 + freqmsg, riddle2alt, riddle3 + freqmsg, riddle3alt, direct_from, direct_to)
   389         if singular and plural and RICH_MODE:
   512         if singular and plural and RICH_MODE:
   390             question = u"<div class='ask'>Find plural:</div>"
       
   391             riddle_s = u"<span class='headword'>{}</span> <span class='pron'>[{}]</span> <span class='attrs'>s</span>".format(singular[0], singular[1])
   513             riddle_s = u"<span class='headword'>{}</span> <span class='pron'>[{}]</span> <span class='attrs'>s</span>".format(singular[0], singular[1])
   392             riddle_pl = u"<span class='headword'>{}</span> <span class='pron'>[{}]</span> <span class='attrs'>pl</span>".format(plural[0], plural[1])
   514             riddle_pl = u"<span class='headword'>{}</span> <span class='pron'>[{}]</span> <span class='attrs'>pl</span>".format(plural[0], plural[1])
   393             answer = direct_from + direct_to
   515             builder.add_note_pl(identity, riddle_s + freqmsg, riddle_pl + freqmsg, direct_from, direct_to)
   394             builder.add_note("singular", identity, question + riddle_s + freqmsg, answer)
       
   395             builder.add_note("plural", identity, question + riddle_pl + freqmsg, answer)
       
   396 
   516 
   397     builder.export(FONAME)
   517     builder.export(FONAME)
   398 finally:
   518 finally:
   399     builder.close()
   519     builder.close()
   400     shutil.rmtree(TMPDIR, ignore_errors=True)
   520     shutil.rmtree(TMPDIR, ignore_errors=True)