Пример #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()