示例#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
 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
    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
 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
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
    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
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
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
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
 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
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
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
 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
 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
    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
    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
 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
    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
    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
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
 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
 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
 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
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
 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
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()