コード例 #1
0
 def rename(self, g, newName):
     "Rename deck prefix to NAME if not exists. Updates children."
     # make sure target node doesn't already exist
     if newName in self.allNames():
         raise DeckRenameError(_("That deck already exists."))
     # make sure we're not nesting under a filtered deck
     for p in self.parentsByName(newName):
         if p['dyn']:
             raise DeckRenameError(
                 _("A filtered deck cannot have subdecks."))
     # ensure we have parents
     newName = self._ensureParents(newName)
     # rename children
     for grp in self.all():
         if grp['name'].startswith(g['name'] + "::"):
             grp['name'] = grp['name'].replace(g['name'] + "::",
                                               newName + "::", 1)
             self.save(grp)
     # adjust name
     g['name'] = newName
     # ensure we have parents again, as we may have renamed parent->child
     newName = self._ensureParents(newName)
     self.save(g)
     # renaming may have altered active did order
     self.maybeAddToActive()
コード例 #2
0
ファイル: csvfile.py プロジェクト: SzymonPajzert/anki_lib
 def foreignNotes(self):
     self.open()
     # process all lines
     log = []
     notes = []
     lineNum = 0
     ignored = 0
     if self.delimiter:
         reader = csv.reader(self.data,
                             delimiter=self.delimiter,
                             doublequote=True)
     else:
         reader = csv.reader(self.data, self.dialect, doublequote=True)
     try:
         for row in reader:
             if len(row) != self.numFields:
                 if row:
                     log.append(
                         _("'%(row)s' had %(num1)d fields, "
                           "expected %(num2)d") % {
                               "row": " ".join(row),
                               "num1": len(row),
                               "num2": self.numFields,
                           })
                     ignored += 1
                 continue
             note = self.noteFromFields(row)
             notes.append(note)
     except (csv.Error) as e:
         log.append(_("Aborted: %s") % str(e))
     self.log = log
     self.ignored = ignored
     self.fileobj.close()
     return notes
コード例 #3
0
ファイル: sched.py プロジェクト: SzymonPajzert/anki_lib
    def _nextDueMsg(self):
        line = []
        # the new line replacements are so we don't break translations
        # in a point release
        if self.revDue():
            line.append(
                _("""\
Today's review limit has been reached, but there are still cards
waiting to be reviewed. For optimum memory, consider increasing
the daily limit in the options.""").replace("\n", " "))
        if self.newDue():
            line.append(
                _("""\
There are more new cards available, but the daily limit has been
reached. You can increase the limit in the options, but please
bear in mind that the more new cards you introduce, the higher
your short-term review workload will become.""").replace("\n", " "))
        if self.haveBuried():
            if self.haveCustomStudy:
                now = " " + _(
                    "To see them now, click the Unbury button below.")
            else:
                now = ""
            line.append(
                _("""\
Some related or buried cards were delayed until a later session.""") + now)
        if self.haveCustomStudy and not self.col.decks.current()['dyn']:
            line.append(
                _("""\
To study outside of the normal schedule, click the Custom Study button below."""
                  ))
        return "<p>".join(line)
コード例 #4
0
ファイル: stats.py プロジェクト: SzymonPajzert/anki_lib
 def ivlGraph(self):
     (ivls, all, avg, max_), chunk = self._ivls()
     tot = 0
     totd = []
     if not ivls or not all:
         return ""
     for (grp, cnt) in ivls:
         tot += cnt
         totd.append((grp, tot/float(all)*100))
     if self.type == 0:
         ivlmax = 31
     elif self.type == 1:
         ivlmax = 52
     else:
         ivlmax = max(5, ivls[-1][0])
     txt = self._title(_("Intervals"),
                       _("Delays until reviews are shown again."))
     txt += self._graph(id="ivl", ylabel2=_("Percentage"), xunit=chunk, data=[
         dict(data=ivls, color=colIvl),
         dict(data=totd, color=colCum, yaxis=2,
          bars={'show': False}, lines=dict(show=True), stack=False)
         ], conf=dict(
             xaxis=dict(min=-0.5, max=ivlmax+0.5),
             yaxes=[dict(), dict(position="right", max=105)]))
     i = []
     self._line(i, _("Average interval"), fmtTimeSpan(avg*86400))
     self._line(i, _("Longest interval"), fmtTimeSpan(max_*86400))
     return txt + self._lineTbl(i)
コード例 #5
0
ファイル: stdmodels.py プロジェクト: SzymonPajzert/anki_lib
def addForwardReverse(col):
    mm = col.models
    m = addBasicModel(col)
    m['name'] = _("Basic (and reversed card)")
    t = mm.newTemplate(_("Card 2"))
    t['qfmt'] = "{{" + _("Back") + "}}"
    t['afmt'] = "{{FrontSide}}\n\n<hr id=answer>\n\n" + "{{" + _(
        "Front") + "}}"
    mm.addTemplate(m, t)
    return m
コード例 #6
0
ファイル: stats.py プロジェクト: SzymonPajzert/anki_lib
    def _dueInfo(self, tot, num):
        i = []
        self._line(i, _("Total"), ngettext("%d review", "%d reviews", tot) % tot)
        self._line(i, _("Average"), self._avgDay(
            tot, num, _("reviews")))
        tomorrow = self.col.db.scalar("""
select count() from cards where did in %s and queue in (2,3)
and due = ?""" % self._limit(), self.col.sched.today+1)
        tomorrow = ngettext("%d card", "%d cards", tomorrow) % tomorrow
        self._line(i, _("Due tomorrow"), tomorrow)
        return self._lineTbl(i)
コード例 #7
0
ファイル: latex.py プロジェクト: SzymonPajzert/anki_lib
def _errMsg(type, texpath):
    msg = (_("Error executing %s.") % type) + "<br>"
    msg += (_("Generated file: %s") % texpath) + "<br>"
    try:
        with open(namedtmp("latex_log.txt", rm=False)) as f:
            log = f.read()
        if not log:
            raise Exception()
        msg += "<small><pre>" + html.escape(log) + "</pre></small>"
    except:
        msg += _("Have you installed latex and dvipng/dvisvgm?")
    return msg
コード例 #8
0
ファイル: stdmodels.py プロジェクト: SzymonPajzert/anki_lib
def addForwardOptionalReverse(col):
    mm = col.models
    m = addBasicModel(col)
    m['name'] = _("Basic (optional reversed card)")
    av = _("Add Reverse")
    fm = mm.newField(av)
    mm.addField(m, fm)
    t = mm.newTemplate(_("Card 2"))
    t['qfmt'] = "{{#%s}}{{%s}}{{/%s}}" % (av, _("Back"), av)
    t['afmt'] = "{{FrontSide}}\n\n<hr id=answer>\n\n" + "{{" + _(
        "Front") + "}}"
    mm.addTemplate(m, t)
    return m
コード例 #9
0
ファイル: consts.py プロジェクト: SzymonPajzert/anki_lib
def dynOrderLabels():
    return {
        0: _("Oldest seen first"),
        1: _("Random"),
        2: _("Increasing intervals"),
        3: _("Decreasing intervals"),
        4: _("Most lapses"),
        5: _("Order added"),
        6: _("Order due"),
        7: _("Latest added first"),
        8: _("Relative overdueness"),
    }
コード例 #10
0
ファイル: stats.py プロジェクト: SzymonPajzert/anki_lib
 def _avgDay(self, tot, num, unit):
     vals = []
     try:
         vals.append(_("%(a)0.1f %(b)s/day") % dict(a=tot/float(num), b=unit))
         return ", ".join(vals)
     except ZeroDivisionError:
         return ""
コード例 #11
0
ファイル: exporting.py プロジェクト: SzymonPajzert/anki_lib
class AnkiCollectionPackageExporter(AnkiPackageExporter):

    key = _("Anki Collection Package")
    ext = ".colpkg"
    verbatim = True
    includeSched = None

    def __init__(self, col):
        AnkiPackageExporter.__init__(self, col)

    def doExport(self, z, path):
        # close our deck & write it into the zip file, and reopen
        self.count = self.col.cardCount()
        v2 = self.col.schedVer() != 1
        self.col.close()
        if not v2:
            z.write(self.col.path, "collection.anki_lib.")
        else:
            self._addDummyCollection(z)
            z.write(self.col.path, "collection.anki_lib.1")
        self.col.reopen()
        # copy all media
        if not self.includeMedia:
            return {}
        mdir = self.col.media.dir()
        return self._exportMedia(z, os.listdir(mdir), mdir)
コード例 #12
0
ファイル: exporting.py プロジェクト: SzymonPajzert/anki_lib
class TextNoteExporter(Exporter):

    key = _("Notes in Plain Text")
    ext = ".txt"
    includeTags = True
    includeHTML = True

    def __init__(self, col):
        Exporter.__init__(self, col)
        self.includeID = False

    def doExport(self, file):
        cardIds = self.cardIds()
        data = []
        for id, flds, tags in self.col.db.execute("""
select guid, flds, tags from notes
where id in
(select nid from cards
where cards.id in %s)""" % ids2str(cardIds)):
            row = []
            # note id
            if self.includeID:
                row.append(str(id))
            # fields
            row.extend([self.processText(f) for f in splitFields(flds)])
            # tags
            if self.includeTags:
                row.append(tags.strip())
            data.append("\t".join(row))
        self.count = len(data)
        out = "\n".join(data)
        file.write(out.encode("utf-8"))
コード例 #13
0
ファイル: collection.py プロジェクト: SzymonPajzert/anki_lib
 def _renderQA(self, data, qfmt=None, afmt=None):
     "Returns hash of id, question, answer."
     # data is [cid, nid, mid, did, ord, tags, flds, cardFlags]
     # unpack fields and create dict
     flist = splitFields(data[6])
     fields = {}
     model = self.models.get(data[2])
     for (name, (idx, conf)) in list(self.models.fieldMap(model).items()):
         fields[name] = flist[idx]
     fields['Tags'] = data[5].strip()
     fields['Type'] = model['name']
     fields['Deck'] = self.decks.name(data[3])
     fields['Subdeck'] = fields['Deck'].split('::')[-1]
     fields['CardFlag'] = self._flagNameFromCardFlags(data[7])
     if model['type'] == MODEL_STD:
         template = model['tmpls'][data[4]]
     else:
         template = model['tmpls'][0]
     fields['Card'] = template['name']
     fields['c%d' % (data[4] + 1)] = "1"
     # render q & a
     d = dict(id=data[0])
     qfmt = qfmt or template['qfmt']
     afmt = afmt or template['afmt']
     for (type, format) in (("q", qfmt), ("a", afmt)):
         if type == "q":
             format = re.sub("{{(?!type:)(.*?)cloze:",
                             r"{{\1cq-%d:" % (data[4] + 1), format)
             format = format.replace("<%cloze:",
                                     "<%%cq:%d:" % (data[4] + 1))
         else:
             format = re.sub("{{(.*?)cloze:", r"{{\1ca-%d:" % (data[4] + 1),
                             format)
             format = format.replace("<%cloze:",
                                     "<%%ca:%d:" % (data[4] + 1))
             fields['FrontSide'] = stripSounds(d['q'])
         fields = runFilter("mungeFields", fields, model, data, self)
         html = anki_lib.template.render(format, fields)
         d[type] = runFilter("mungeQA", html, type, fields, model, data,
                             self)
         # empty cloze?
         if type == 'q' and model['type'] == MODEL_CLOZE:
             if not self.models._availClozeOrds(model, data[6], False):
                 d['q'] += ("<p>" + _(
                     "Please edit this note and add some cloze deletions. (%s)"
                 ) % ("<a href=%s#cloze>%s</a>" % (HELP_SITE, _("help"))))
     return d
コード例 #14
0
ファイル: collection.py プロジェクト: SzymonPajzert/anki_lib
 def markReview(self, card):
     old = []
     if self._undo:
         if self._undo[0] == 1:
             old = self._undo[2]
         self.clearUndo()
     wasLeech = card.note().hasTag("leech") or False
     self._undo = [1, _("Review"), old + [copy.copy(card)], wasLeech]
コード例 #15
0
ファイル: collection.py プロジェクト: SzymonPajzert/anki_lib
    def emptyCardReport(self, cids):
        rep = ""
        for ords, cnt, flds in self.db.all("""
select group_concat(ord+1), count(), flds from cards c, notes n
where c.nid = n.id and c.id in %s group by nid""" % ids2str(cids)):
            rep += _("Empty card numbers: %(c)s\nFields: %(f)s\n\n") % dict(
                c=ords, f=flds.replace("\x1f", " / "))
        return rep
コード例 #16
0
ファイル: stats.py プロジェクト: SzymonPajzert/anki_lib
    def introductionGraph(self):
        start, days, chunk = self.get_start_end_chunk()
        data = self._added(days, chunk)
        if not data:
            return ""
        conf = dict(
            xaxis=dict(tickDecimals=0, max=0.5),
            yaxes=[dict(min=0), dict(position="right", min=0)])
        if days is not None:
            # pylint: disable=invalid-unary-operand-type
            conf['xaxis']['min'] = -days+0.5
        def plot(id, data, ylabel, ylabel2):
            return self._graph(
                id, data=data, conf=conf, xunit=chunk, ylabel=ylabel, ylabel2=ylabel2)
        # graph
        repdata, repsum = self._splitRepData(data, ((1, colLearn, ""),))
        txt = self._title(
            _("Added"), _("The number of new cards you have added."))
        txt += plot("intro", repdata, ylabel=_("Cards"), ylabel2=_("Cumulative Cards"))
        # total and per day average
        tot = sum([i[1] for i in data])
        period = self._periodDays()
        if not period:
            # base off date of earliest added card
            period = self._deckAge('add')
        i = []
        self._line(i, _("Total"), ngettext("%d card", "%d cards", tot) % tot)
        self._line(i, _("Average"), self._avgDay(tot, period, _("cards")))
        txt += self._lineTbl(i)

        return txt
コード例 #17
0
ファイル: sched.py プロジェクト: SzymonPajzert/anki_lib
 def nextIvlStr(self, card, ease, short=False):
     "Return the next interval for CARD as a string."
     ivl = self.nextIvl(card, ease)
     if not ivl:
         return _("(end)")
     s = fmtTimeSpan(ivl, short=short)
     if ivl < self.col.conf['collapseTime']:
         s = "<" + s
     return s
コード例 #18
0
    def logger(self, text, level=1):
        "Wrapper for Anki logger"

        dLevels = {0: '', 1: 'Info', 2: 'Verbose', 3: 'Debug'}
        if level <= self.META.loggerLevel:
            #self.deck.updateProgress(_(text))

            if self.META.logToStdOutput:
                print(self.__class__.__name__ + " - " +
                      dLevels[level].ljust(9) + ' -\t' + _(text))
コード例 #19
0
ファイル: stats.py プロジェクト: SzymonPajzert/anki_lib
    def todayStats(self):
        b = self._title(_("Today"))
        # studied today
        lim = self._revlogLimit()
        if lim:
            lim = " and " + lim
        cards, thetime, failed, lrn, rev, relrn, filt = self.col.db.first("""
select count(), sum(time)/1000,
sum(case when ease = 1 then 1 else 0 end), /* failed */
sum(case when type = 0 then 1 else 0 end), /* learning */
sum(case when type = 1 then 1 else 0 end), /* review */
sum(case when type = 2 then 1 else 0 end), /* relearn */
sum(case when type = 3 then 1 else 0 end) /* filter */
from revlog where id > ? """+lim, (self.col.sched.dayCutoff-86400)*1000)
        cards = cards or 0
        thetime = thetime or 0
        failed = failed or 0
        lrn = lrn or 0
        rev = rev or 0
        relrn = relrn or 0
        filt = filt or 0
        # studied
        def bold(s):
            return "<b>"+str(s)+"</b>"
        msgp1 = ngettext("<!--studied-->%d card", "<!--studied-->%d cards", cards) % cards
        if cards:
            b += _("Studied %(a)s %(b)s today (%(secs).1fs/card)") % dict(
                a=bold(msgp1), b=bold(fmtTimeSpan(thetime, unit=1, inTime=True)),
                secs=thetime/cards
            )
            # again/pass count
            b += "<br>" + _("Again count: %s") % bold(failed)
            if cards:
                b += " " + _("(%s correct)") % bold(
                    "%0.1f%%" %((1-failed/float(cards))*100))
            # type breakdown
            b += "<br>"
            b += (_("Learn: %(a)s, Review: %(b)s, Relearn: %(c)s, Filtered: %(d)s")
                  % dict(a=bold(lrn), b=bold(rev), c=bold(relrn), d=bold(filt)))
            # mature today
            mcnt, msum = self.col.db.first("""
    select count(), sum(case when ease = 1 then 0 else 1 end) from revlog
    where lastIvl >= 21 and id > ?"""+lim, (self.col.sched.dayCutoff-86400)*1000)
            b += "<br>"
            if mcnt:
                b += _("Correct answers on mature cards: %(a)d/%(b)d (%(c).1f%%)") % dict(
                    a=msum, b=mcnt, c=(msum / float(mcnt) * 100))
            else:
                b += _("No mature cards were studied today.")
        else:
            b += _("No cards have been studied today.")
        return b
コード例 #20
0
ファイル: exporting.py プロジェクト: SzymonPajzert/anki_lib
    def _addDummyCollection(self, zip):
        path = namedtmp("dummy.anki_lib.")
        c = Collection(path)
        n = c.newNote()
        n[_('Front')] = "This file requires a newer version of Anki."
        c.addNote(n)
        c.save()
        c.close()

        zip.write(path, "collection.anki_lib.")
        os.unlink(path)
コード例 #21
0
def _getColVars(db):
    import anki_lib.collection
    import anki_lib.decks
    g = copy.deepcopy(anki_lib.decks.defaultDeck)
    g['id'] = 1
    g['name'] = _("Default")
    g['conf'] = 1
    g['mod'] = intTime()
    gc = copy.deepcopy(anki_lib.decks.defaultConf)
    gc['id'] = 1
    return g, gc, anki_lib.collection.defaultConf.copy()
コード例 #22
0
ファイル: stdmodels.py プロジェクト: SzymonPajzert/anki_lib
def addBasicTypingModel(col):
    mm = col.models
    m = mm.new(_("Basic (type in the answer)"))
    fm = mm.newField(_("Front"))
    mm.addField(m, fm)
    fm = mm.newField(_("Back"))
    mm.addField(m, fm)
    t = mm.newTemplate(_("Card 1"))
    t['qfmt'] = "{{" + _("Front") + "}}\n\n{{type:" + _("Back") + "}}"
    t['afmt'] = "{{" + _("Front") + "}}\n\n<hr id=answer>\n\n{{type:" + _(
        "Back") + "}}"
    mm.addTemplate(m, t)
    mm.add(m)
    return m
コード例 #23
0
ファイル: stats.py プロジェクト: SzymonPajzert/anki_lib
 def dueGraph(self):
     start, end, chunk = self.get_start_end_chunk()
     d = self._due(start, end, chunk)
     yng = []
     mtr = []
     tot = 0
     totd = []
     for day in d:
         yng.append((day[0], day[1]))
         mtr.append((day[0], day[2]))
         tot += day[1]+day[2]
         totd.append((day[0], tot))
     data = [
         dict(data=mtr, color=colMature, label=_("Mature")),
         dict(data=yng, color=colYoung, label=_("Young")),
     ]
     if len(totd) > 1:
         data.append(
             dict(data=totd, color=colCum, label=_("Cumulative"), yaxis=2,
                  bars={'show': False}, lines=dict(show=True), stack=False))
     txt = self._title(
         _("Forecast"),
         _("The number of reviews due in the future."))
     xaxis = dict(tickDecimals=0, min=-0.5)
     if end is not None:
         xaxis['max'] = end-0.5
     txt += self._graph(
         id="due", data=data, xunit=chunk, ylabel2=_("Cumulative Cards"),
         conf=dict(
             xaxis=xaxis, yaxes=[
                 dict(min=0), dict(min=0, tickDecimals=0, position="right")]
         ),
     )
     txt += self._dueInfo(tot, len(totd)*chunk)
     return txt
コード例 #24
0
ファイル: stats.py プロジェクト: SzymonPajzert/anki_lib
 def easeGraph(self):
     # 3 + 4 + 4 + spaces on sides and middle = 15
     # yng starts at 1+3+1 = 5
     # mtr starts at 5+4+1 = 10
     d = {'lrn':[], 'yng':[], 'mtr':[]}
     types = ("lrn", "yng", "mtr")
     eases = self._eases()
     for (type, ease, cnt) in eases:
         if type == 1:
             ease += 5
         elif type == 2:
             ease += 10
         n = types[type]
         d[n].append((ease, cnt))
     ticks = [[1,1],[2,2],[3,3], # [4,4]
              [6,1],[7,2],[8,3],[9,4],
              [11, 1],[12,2],[13,3],[14,4]]
     if self.col.schedVer() != 1:
         ticks.insert(3, [4,4])
     txt = self._title(_("Answer Buttons"),
                       _("The number of times you have pressed each button."))
     txt += self._graph(id="ease", data=[
         dict(data=d['lrn'], color=colLearn, label=_("Learning")),
         dict(data=d['yng'], color=colYoung, label=_("Young")),
         dict(data=d['mtr'], color=colMature, label=_("Mature")),
         ], type="bars", conf=dict(
             xaxis=dict(ticks=ticks, min=0, max=15)),
         ylabel=_("Answers"))
     txt += self._easeInfo(eases)
     return txt
コード例 #25
0
def shortTimeFmt(type):
    return {
    "years": _("%sy"),
    "months": _("%smo"),
    "days": _("%sd"),
    "hours": _("%sh"),
    "minutes": _("%sm"),
    "seconds": _("%ss"),
    }[type]
コード例 #26
0
ファイル: sound.py プロジェクト: SzymonPajzert/anki_lib
 def postprocess(self, encode=True):
     self.encode = encode
     for c in processingChain:
         #print c
         if not self.encode and c[0] == 'lame':
             continue
         try:
             cmd, env = _packagedCmd(c)
             ret = retryWait(subprocess.Popen(cmd, startupinfo=si, env=env))
         except:
             ret = True
         finally:
             self.cleanup()
         if ret:
             raise Exception(_("Error running %s") % " ".join(cmd))
コード例 #27
0
ファイル: stdmodels.py プロジェクト: SzymonPajzert/anki_lib
def addClozeModel(col):
    mm = col.models
    m = mm.new(_("Cloze"))
    m['type'] = MODEL_CLOZE
    txt = _("Text")
    fm = mm.newField(txt)
    mm.addField(m, fm)
    fm = mm.newField(_("Extra"))
    mm.addField(m, fm)
    t = mm.newTemplate(_("Cloze"))
    fmt = "{{cloze:%s}}" % txt
    m['css'] += """
.cloze {
 font-weight: bold;
 color: blue;
}
.nightMode .cloze {
 color: lightblue;
}"""
    t['qfmt'] = fmt
    t['afmt'] = fmt + "<br>\n{{%s}}" % _("Extra")
    mm.addTemplate(m, t)
    mm.add(m)
    return m
コード例 #28
0
def _upgradeClozeModel(col, m):
    m['type'] = MODEL_CLOZE
    # convert first template
    t = m['tmpls'][0]
    for type in 'qfmt', 'afmt':
        t[type] = re.sub("{{cloze:1:(.+?)}}", r"{{cloze:\1}}", t[type])
    t['name'] = _("Cloze")
    # delete non-cloze cards for the model
    rem = []
    for t in m['tmpls'][1:]:
        if "{{cloze:" not in t['qfmt']:
            rem.append(t)
    for r in rem:
        col.models.remTemplate(m, r)
    del m['tmpls'][1:]
    col.models._updateTemplOrds(m)
    col.models.save(m)
コード例 #29
0
ファイル: stats.py プロジェクト: SzymonPajzert/anki_lib
 def footer(self):
     b = "<br><br><font size=1>"
     b += _("Generated on %s") % time.asctime(time.localtime(time.time()))
     b += "<br>"
     if self.wholeCollection:
         deck = _("whole collection")
     else:
         deck = self.col.decks.current()['name']
     b += _("Scope: %s") % deck
     b += "<br>"
     b += _("Period: %s") % [
         _("1 month"),
         _("1 year"),
         _("deck life")
         ][self.type]
     return b
コード例 #30
0
ファイル: latex.py プロジェクト: SzymonPajzert/anki_lib
def _buildImg(col, latex, fname, model):
    # add header/footer
    latex = (model["latexPre"] + "\n" + latex + "\n" + model["latexPost"])
    # it's only really secure if run in a jail, but these are the most common
    tmplatex = latex.replace("\\includegraphics", "")
    for bad in ("\\write18", "\\readline", "\\input", "\\include", "\\catcode",
                "\\openout", "\\write", "\\loop", "\\def", "\\shipout"):
        # don't mind if the sequence is only part of a command
        bad_re = "\\" + bad + "[^a-zA-Z]"
        if re.search(bad_re, tmplatex):
            return _("""\
For security reasons, '%s' is not allowed on cards. You can still use \
it by placing the command in a different package, and importing that \
package in the LaTeX header instead.""") % bad

    # commands to use?
    if model.get("latexsvg", False):
        latexCmds = svgCommands
        ext = "svg"
    else:
        latexCmds = pngCommands
        ext = "png"

    # write into a temp file
    log = open(namedtmp("latex_log.txt"), "w")
    texpath = namedtmp("tmp.tex")
    texfile = open(texpath, "w", encoding="utf8")
    texfile.write(latex)
    texfile.close()
    mdir = col.media.dir()
    oldcwd = os.getcwd()
    png = namedtmp("tmp.%s" % ext)
    try:
        # generate png
        os.chdir(tmpdir())
        for latexCmd in latexCmds:
            if call(latexCmd, stdout=log, stderr=log):
                return _errMsg(latexCmd[0], texpath)
        # add to media
        shutil.copyfile(png, os.path.join(mdir, fname))
        return
    finally:
        os.chdir(oldcwd)
        log.close()