start port to python 3
unit tests pass and main screens of GUI load
This commit is contained in:
parent
1dce3eaaff
commit
15b349e3a8
@ -5,30 +5,12 @@
|
||||
import sys
|
||||
import os
|
||||
import platform
|
||||
import json
|
||||
|
||||
if sys.version_info[0] > 2:
|
||||
raise Exception("Anki should be run with Python 2")
|
||||
elif sys.version_info[1] < 6:
|
||||
raise Exception("Anki requires Python 2.6+")
|
||||
elif sys.getfilesystemencoding().lower() in ("ascii", "ansi_x3.4-1968"):
|
||||
raise Exception("Anki requires a UTF-8 locale.")
|
||||
|
||||
try:
|
||||
import simplejson as json
|
||||
except:
|
||||
import json as json
|
||||
if json.__version__ < "1.7.3":
|
||||
raise Exception("SimpleJSON must be 1.7.3 or later.")
|
||||
|
||||
# add path to bundled third party libs
|
||||
ext = os.path.realpath(os.path.join(
|
||||
os.path.dirname(__file__), "../thirdparty"))
|
||||
sys.path.insert(0, ext)
|
||||
arch = platform.architecture()
|
||||
if arch[1] == "ELF":
|
||||
# add arch-dependent libs
|
||||
sys.path.insert(0, os.path.join(ext, "py2.%d-%s" % (
|
||||
sys.version_info[1], arch[0][0:2])))
|
||||
if sys.version_info[0] < 3:
|
||||
raise Exception("Anki should be run with Python 3")
|
||||
elif sys.version_info[1] < 4:
|
||||
raise Exception("Anki requires Python 3.4+")
|
||||
|
||||
version="2.0.36" # build scripts grep this line, so preserve formatting
|
||||
from anki.storage import Collection
|
||||
|
15
anki/anki
15
anki/anki
@ -1,15 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os, sys
|
||||
# system-wide install
|
||||
sys.path.insert(0, "/usr/share/anki")
|
||||
sys.path.insert(0, "/usr/share/anki/libanki")
|
||||
# running from extracted folder
|
||||
base = os.path.dirname(os.path.realpath(__file__))
|
||||
sys.path.insert(0, base)
|
||||
sys.path.insert(0, os.path.join(base, "libanki"))
|
||||
# or git
|
||||
sys.path.insert(0, os.path.join(base, "..", "libanki"))
|
||||
# start
|
||||
import aqt
|
||||
aqt.run()
|
@ -351,7 +351,7 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
|
||||
ts += 1
|
||||
# note any cards that need removing
|
||||
if nid in have:
|
||||
for ord, id in have[nid].items():
|
||||
for ord, id in list(have[nid].items()):
|
||||
if ord not in avail:
|
||||
rem.append(id)
|
||||
# bulk update
|
||||
@ -383,7 +383,7 @@ insert into cards values (?,?,?,?,?,?,0,0,?,0,0,0,0,0,0,0,0,"")""",
|
||||
card.nid = note.id
|
||||
card.ord = template['ord']
|
||||
# Use template did (deck override) if valid, otherwise model did
|
||||
if template['did'] and unicode(template['did']) in self.decks.decks:
|
||||
if template['did'] and str(template['did']) in self.decks.decks:
|
||||
card.did = template['did']
|
||||
else:
|
||||
card.did = note.model()['did']
|
||||
@ -500,7 +500,7 @@ where c.nid = n.id and c.id in %s group by nid""" % ids2str(cids)):
|
||||
flist = splitFields(data[6])
|
||||
fields = {}
|
||||
model = self.models.get(data[2])
|
||||
for (name, (idx, conf)) in self.models.fieldMap(model).items():
|
||||
for (name, (idx, conf)) in list(self.models.fieldMap(model).items()):
|
||||
fields[name] = flist[idx]
|
||||
fields['Tags'] = data[5].strip()
|
||||
fields['Type'] = model['name']
|
||||
@ -820,16 +820,16 @@ and queue = 0""", intTime(), self.usn())
|
||||
if not self._debugLog:
|
||||
return
|
||||
def customRepr(x):
|
||||
if isinstance(x, basestring):
|
||||
if isinstance(x, str):
|
||||
return x
|
||||
return pprint.pformat(x)
|
||||
path, num, fn, y = traceback.extract_stack(
|
||||
limit=2+kwargs.get("stack", 0))[0]
|
||||
buf = u"[%s] %s:%s(): %s" % (intTime(), os.path.basename(path), fn,
|
||||
buf = "[%s] %s:%s(): %s" % (intTime(), os.path.basename(path), fn,
|
||||
", ".join([customRepr(x) for x in args]))
|
||||
self._logHnd.write(buf.encode("utf8") + "\n")
|
||||
self._logHnd.write(buf + "\n")
|
||||
if os.environ.get("ANKIDEV"):
|
||||
print buf
|
||||
print(buf)
|
||||
|
||||
def _openLog(self):
|
||||
if not self._debugLog:
|
||||
@ -840,7 +840,7 @@ and queue = 0""", intTime(), self.usn())
|
||||
if os.path.exists(lpath2):
|
||||
os.unlink(lpath2)
|
||||
os.rename(lpath, lpath2)
|
||||
self._logHnd = open(lpath, "ab")
|
||||
self._logHnd = open(lpath, "a")
|
||||
|
||||
def _closeLog(self):
|
||||
self._logHnd = None
|
||||
|
17
anki/db.py
17
anki/db.py
@ -18,10 +18,7 @@ Error = sqlite.Error
|
||||
|
||||
class DB(object):
|
||||
def __init__(self, path, timeout=0):
|
||||
encpath = path
|
||||
if isinstance(encpath, unicode):
|
||||
encpath = path.encode("utf-8")
|
||||
self._db = sqlite.connect(encpath, timeout=timeout)
|
||||
self._db = sqlite.connect(path, timeout=timeout)
|
||||
self._path = path
|
||||
self.echo = os.environ.get("DBECHO")
|
||||
self.mod = False
|
||||
@ -41,9 +38,9 @@ class DB(object):
|
||||
res = self._db.execute(sql, a)
|
||||
if self.echo:
|
||||
#print a, ka
|
||||
print sql, "%0.3fms" % ((time.time() - t)*1000)
|
||||
print(sql, "%0.3fms" % ((time.time() - t)*1000))
|
||||
if self.echo == "2":
|
||||
print a, ka
|
||||
print(a, ka)
|
||||
return res
|
||||
|
||||
def executemany(self, sql, l):
|
||||
@ -51,20 +48,20 @@ class DB(object):
|
||||
t = time.time()
|
||||
self._db.executemany(sql, l)
|
||||
if self.echo:
|
||||
print sql, "%0.3fms" % ((time.time() - t)*1000)
|
||||
print(sql, "%0.3fms" % ((time.time() - t)*1000))
|
||||
if self.echo == "2":
|
||||
print l
|
||||
print(l)
|
||||
|
||||
def commit(self):
|
||||
t = time.time()
|
||||
self._db.commit()
|
||||
if self.echo:
|
||||
print "commit %0.3fms" % ((time.time() - t)*1000)
|
||||
print("commit %0.3fms" % ((time.time() - t)*1000))
|
||||
|
||||
def executescript(self, sql):
|
||||
self.mod = True
|
||||
if self.echo:
|
||||
print sql
|
||||
print(sql)
|
||||
self._db.executescript(sql)
|
||||
|
||||
def rollback(self):
|
||||
|
@ -95,7 +95,7 @@ class DeckManager(object):
|
||||
self.dconf = json.loads(dconf)
|
||||
# set limits to within bounds
|
||||
found = False
|
||||
for c in self.dconf.values():
|
||||
for c in list(self.dconf.values()):
|
||||
for t in ('rev', 'new'):
|
||||
pd = 'perDay'
|
||||
if c[t][pd] > 999999:
|
||||
@ -125,7 +125,7 @@ class DeckManager(object):
|
||||
def id(self, name, create=True, type=defaultDeck):
|
||||
"Add a deck with NAME. Reuse deck if already exists. Return id as int."
|
||||
name = name.replace('"', '')
|
||||
for id, g in self.decks.items():
|
||||
for id, g in list(self.decks.items()):
|
||||
if g['name'].lower() == name.lower():
|
||||
return int(id)
|
||||
if not create:
|
||||
@ -185,22 +185,22 @@ class DeckManager(object):
|
||||
del self.decks[str(did)]
|
||||
# ensure we have an active deck
|
||||
if did in self.active():
|
||||
self.select(int(self.decks.keys()[0]))
|
||||
self.select(int(list(self.decks.keys())[0]))
|
||||
self.save()
|
||||
|
||||
def allNames(self, dyn=True):
|
||||
"An unsorted list of all deck names."
|
||||
if dyn:
|
||||
return [x['name'] for x in self.decks.values()]
|
||||
return [x['name'] for x in list(self.decks.values())]
|
||||
else:
|
||||
return [x['name'] for x in self.decks.values() if not x['dyn']]
|
||||
return [x['name'] for x in list(self.decks.values()) if not x['dyn']]
|
||||
|
||||
def all(self):
|
||||
"A list of all decks."
|
||||
return self.decks.values()
|
||||
return list(self.decks.values())
|
||||
|
||||
def allIds(self):
|
||||
return self.decks.keys()
|
||||
return list(self.decks.keys())
|
||||
|
||||
def collapse(self, did):
|
||||
deck = self.get(did)
|
||||
@ -225,7 +225,7 @@ class DeckManager(object):
|
||||
|
||||
def byName(self, name):
|
||||
"Get deck with NAME."
|
||||
for m in self.decks.values():
|
||||
for m in list(self.decks.values()):
|
||||
if m['name'] == name:
|
||||
return m
|
||||
|
||||
@ -319,7 +319,7 @@ class DeckManager(object):
|
||||
|
||||
def allConf(self):
|
||||
"A list of all deck config."
|
||||
return self.dconf.values()
|
||||
return list(self.dconf.values())
|
||||
|
||||
def confForDid(self, did):
|
||||
deck = self.get(did, default=False)
|
||||
@ -370,7 +370,7 @@ class DeckManager(object):
|
||||
|
||||
def didsForConf(self, conf):
|
||||
dids = []
|
||||
for deck in self.decks.values():
|
||||
for deck in list(self.decks.values()):
|
||||
if 'conf' in deck and deck['conf'] == conf['id']:
|
||||
dids.append(deck['id'])
|
||||
return dids
|
||||
@ -421,7 +421,7 @@ class DeckManager(object):
|
||||
ids2str(dids))
|
||||
|
||||
def recoverOrphans(self):
|
||||
dids = self.decks.keys()
|
||||
dids = list(self.decks.keys())
|
||||
mod = self.col.db.mod
|
||||
self.col.db.execute("update cards set did = 1 where did not in "+
|
||||
ids2str(dids))
|
||||
|
@ -137,7 +137,7 @@ class AnkiExporter(Exporter):
|
||||
"insert into cards values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||
data)
|
||||
# notes
|
||||
strnids = ids2str(nids.keys())
|
||||
strnids = ids2str(list(nids.keys()))
|
||||
notedata = []
|
||||
for row in self.src.db.all(
|
||||
"select * from notes where id in "+strnids):
|
||||
@ -209,7 +209,7 @@ class AnkiExporter(Exporter):
|
||||
if self._modelHasMedia(m, fname):
|
||||
media[fname] = True
|
||||
break
|
||||
self.mediaFiles = media.keys()
|
||||
self.mediaFiles = list(media.keys())
|
||||
self.dst.crt = self.src.crt
|
||||
# todo: tags?
|
||||
self.count = self.dst.cardCount()
|
||||
|
40
anki/find.py
40
anki/find.py
@ -92,7 +92,7 @@ select distinct(n.id) from cards c, notes n where c.nid=n.id and """+preds
|
||||
else:
|
||||
inQuote = c
|
||||
# separator (space and ideographic space)
|
||||
elif c in (" ", u'\u3000'):
|
||||
elif c in (" ", '\u3000'):
|
||||
if inQuote:
|
||||
token += c
|
||||
elif token:
|
||||
@ -239,7 +239,8 @@ select distinct(n.id) from cards c, notes n where c.nid=n.id and """+preds
|
||||
# Commands
|
||||
######################################################################
|
||||
|
||||
def _findTag(self, (val, args)):
|
||||
def _findTag(self, args):
|
||||
(val, args) = args
|
||||
if val == "none":
|
||||
return 'n.tags = ""'
|
||||
val = val.replace("*", "%")
|
||||
@ -250,7 +251,8 @@ select distinct(n.id) from cards c, notes n where c.nid=n.id and """+preds
|
||||
args.append(val)
|
||||
return "n.tags like ?"
|
||||
|
||||
def _findCardState(self, (val, args)):
|
||||
def _findCardState(self, args):
|
||||
(val, args) = args
|
||||
if val in ("review", "new", "learn"):
|
||||
if val == "review":
|
||||
n = 2
|
||||
@ -269,8 +271,9 @@ select distinct(n.id) from cards c, notes n where c.nid=n.id and """+preds
|
||||
(c.queue = 1 and c.due <= %d)""" % (
|
||||
self.col.sched.today, self.col.sched.dayCutoff)
|
||||
|
||||
def _findRated(self, (val, args)):
|
||||
def _findRated(self, args):
|
||||
# days(:optional_ease)
|
||||
(val, args) = args
|
||||
r = val.split(":")
|
||||
try:
|
||||
days = int(r[0])
|
||||
@ -287,7 +290,8 @@ select distinct(n.id) from cards c, notes n where c.nid=n.id and """+preds
|
||||
return ("c.id in (select cid from revlog where id>%d %s)" %
|
||||
(cutoff, ease))
|
||||
|
||||
def _findAdded(self, (val, args)):
|
||||
def _findAdded(self, args):
|
||||
(val, args) = args
|
||||
try:
|
||||
days = int(val)
|
||||
except ValueError:
|
||||
@ -295,8 +299,9 @@ select distinct(n.id) from cards c, notes n where c.nid=n.id and """+preds
|
||||
cutoff = (self.col.sched.dayCutoff - 86400*days)*1000
|
||||
return "c.id > %d" % cutoff
|
||||
|
||||
def _findProp(self, (val, args)):
|
||||
def _findProp(self, args):
|
||||
# extract
|
||||
(val, args) = args
|
||||
m = re.match("(^.+?)(<=|>=|!=|=|<|>)(.+?$)", val)
|
||||
if not m:
|
||||
return
|
||||
@ -331,22 +336,26 @@ select distinct(n.id) from cards c, notes n where c.nid=n.id and """+preds
|
||||
args.append("%"+val+"%")
|
||||
return "(n.sfld like ? escape '\\' or n.flds like ? escape '\\')"
|
||||
|
||||
def _findNids(self, (val, args)):
|
||||
def _findNids(self, args):
|
||||
(val, args) = args
|
||||
if re.search("[^0-9,]", val):
|
||||
return
|
||||
return "n.id in (%s)" % val
|
||||
|
||||
def _findCids(self, (val, args)):
|
||||
def _findCids(self, args):
|
||||
(val, args) = args
|
||||
if re.search("[^0-9,]", val):
|
||||
return
|
||||
return "c.id in (%s)" % val
|
||||
|
||||
def _findMid(self, (val, args)):
|
||||
def _findMid(self, args):
|
||||
(val, args) = args
|
||||
if re.search("[^0-9]", val):
|
||||
return
|
||||
return "n.mid = %s" % val
|
||||
|
||||
def _findModel(self, (val, args)):
|
||||
def _findModel(self, args):
|
||||
(val, args) = args
|
||||
ids = []
|
||||
val = val.lower()
|
||||
for m in self.col.models.all():
|
||||
@ -354,8 +363,9 @@ select distinct(n.id) from cards c, notes n where c.nid=n.id and """+preds
|
||||
ids.append(m['id'])
|
||||
return "n.mid in %s" % ids2str(ids)
|
||||
|
||||
def _findDeck(self, (val, args)):
|
||||
def _findDeck(self, args):
|
||||
# if searching for all decks, skip
|
||||
(val, args) = args
|
||||
if val == "*":
|
||||
return "skip"
|
||||
# deck types
|
||||
@ -386,8 +396,9 @@ select distinct(n.id) from cards c, notes n where c.nid=n.id and """+preds
|
||||
sids = ids2str(ids)
|
||||
return "c.did in %s or c.odid in %s" % (sids, sids)
|
||||
|
||||
def _findTemplate(self, (val, args)):
|
||||
def _findTemplate(self, args):
|
||||
# were we given an ordinal number?
|
||||
(val, args) = args
|
||||
try:
|
||||
num = int(val) - 1
|
||||
except:
|
||||
@ -427,7 +438,7 @@ select distinct(n.id) from cards c, notes n where c.nid=n.id and """+preds
|
||||
for (id,mid,flds) in self.col.db.execute("""
|
||||
select id, mid, flds from notes
|
||||
where mid in %s and flds like ? escape '\\'""" % (
|
||||
ids2str(mods.keys())),
|
||||
ids2str(list(mods.keys()))),
|
||||
"%"+val+"%"):
|
||||
flds = splitFields(flds)
|
||||
ord = mods[str(mid)][1]
|
||||
@ -441,8 +452,9 @@ where mid in %s and flds like ? escape '\\'""" % (
|
||||
return "0"
|
||||
return "n.id in %s" % ids2str(nids)
|
||||
|
||||
def _findDupes(self, (val, args)):
|
||||
def _findDupes(self, args):
|
||||
# caller must call stripHTMLMedia on passed val
|
||||
(val, args) = args
|
||||
try:
|
||||
mid, val = val.split(",", 1)
|
||||
except OSError:
|
||||
|
@ -404,7 +404,7 @@ insert or ignore into revlog values (?,?,?,?,?,?,?,?,?)""", revlog)
|
||||
######################################################################
|
||||
|
||||
def _postImport(self):
|
||||
for did in self._decks.values():
|
||||
for did in list(self._decks.values()):
|
||||
self.col.sched.maybeRandomizeDeck(did)
|
||||
# make sure new position is correct
|
||||
self.dst.conf['nextPos'] = self.dst.db.scalar(
|
||||
|
@ -19,12 +19,12 @@ class AnkiPackageImporter(Anki2Importer):
|
||||
# we need the media dict in advance, and we'll need a map of fname ->
|
||||
# number to use during the import
|
||||
self.nameToNum = {}
|
||||
for k, v in json.loads(z.read("media")).items():
|
||||
for k, v in list(json.loads(z.read("media").decode("utf8")).items()):
|
||||
self.nameToNum[v] = k
|
||||
# run anki2 importer
|
||||
Anki2Importer.run(self)
|
||||
# import static media
|
||||
for file, c in self.nameToNum.items():
|
||||
for file, c in list(self.nameToNum.items()):
|
||||
if not file.startswith("_") and not file.startswith("latex-"):
|
||||
continue
|
||||
path = os.path.join(self.col.media.dir(),
|
||||
|
@ -35,13 +35,12 @@ class TextImporter(NoteImporter):
|
||||
reader = csv.reader(self.data, self.dialect, doublequote=True)
|
||||
try:
|
||||
for row in reader:
|
||||
row = [unicode(x, "utf-8") for x in row]
|
||||
if len(row) != self.numFields:
|
||||
if row:
|
||||
log.append(_(
|
||||
"'%(row)s' had %(num1)d fields, "
|
||||
"expected %(num2)d") % {
|
||||
"row": u" ".join(row),
|
||||
"row": " ".join(row),
|
||||
"num1": len(row),
|
||||
"num2": self.numFields,
|
||||
})
|
||||
@ -49,7 +48,7 @@ class TextImporter(NoteImporter):
|
||||
continue
|
||||
note = self.noteFromFields(row)
|
||||
notes.append(note)
|
||||
except (csv.Error), e:
|
||||
except (csv.Error) as e:
|
||||
log.append(_("Aborted: %s") % str(e))
|
||||
self.log = log
|
||||
self.ignored = ignored
|
||||
@ -68,16 +67,14 @@ class TextImporter(NoteImporter):
|
||||
|
||||
def openFile(self):
|
||||
self.dialect = None
|
||||
self.fileobj = open(self.file, "rbU")
|
||||
self.fileobj = open(self.file, "r", encoding='utf-8-sig')
|
||||
self.data = self.fileobj.read()
|
||||
if self.data.startswith(codecs.BOM_UTF8):
|
||||
self.data = self.data[len(codecs.BOM_UTF8):]
|
||||
def sub(s):
|
||||
return re.sub("^\#.*$", "__comment", s)
|
||||
self.data = [sub(x)+"\n" for x in self.data.split("\n") if sub(x) != "__comment"]
|
||||
if self.data:
|
||||
if self.data[0].startswith("tags:"):
|
||||
tags = unicode(self.data[0][5:], "utf8").strip()
|
||||
tags = str(self.data[0][5:], "utf8").strip()
|
||||
self.tagsToAdd = tags.split(" ")
|
||||
del self.data[0]
|
||||
self.updateDelimiter()
|
||||
@ -117,7 +114,7 @@ class TextImporter(NoteImporter):
|
||||
reader = csv.reader(self.data, delimiter=self.delimiter, doublequote=True)
|
||||
try:
|
||||
while True:
|
||||
row = reader.next()
|
||||
row = next(reader)
|
||||
if row:
|
||||
self.numFields = len(row)
|
||||
break
|
||||
|
@ -158,7 +158,7 @@ acq_reps+ret_reps, lapses, card_type_id from cards"""):
|
||||
|
||||
def _addCloze(self, notes):
|
||||
data = []
|
||||
notes = notes.values()
|
||||
notes = list(notes.values())
|
||||
for orig in notes:
|
||||
# create a foreign note object
|
||||
n = ForeignNote()
|
||||
|
@ -222,7 +222,7 @@ content in the text file to the correct fields."""))
|
||||
if not self.processFields(n):
|
||||
return
|
||||
# note id for card updates later
|
||||
for ord, c in n.cards.items():
|
||||
for ord, c in list(n.cards.items()):
|
||||
self._cards.append((id, ord, c))
|
||||
self.col.tags.register(n.tags)
|
||||
return [id, guid64(), self.model['id'],
|
||||
|
@ -10,7 +10,6 @@ from anki.lang import _
|
||||
from anki.lang import ngettext
|
||||
|
||||
from xml.dom import minidom
|
||||
from types import DictType, InstanceType
|
||||
from string import capwords
|
||||
import re, unicodedata, time
|
||||
|
||||
@ -27,9 +26,9 @@ class SmartDict(dict):
|
||||
|
||||
def __init__(self, *a, **kw):
|
||||
if a:
|
||||
if type(a[0]) is DictType:
|
||||
if isinstance(type(a[0]), dict):
|
||||
kw.update(a[0])
|
||||
elif type(a[0]) is InstanceType:
|
||||
elif isinstance(type(a[0]), object):
|
||||
kw.update(a[0].__dict__)
|
||||
elif hasattr(a[0], '__class__') and a[0].__class__.__name__=='SmartDict':
|
||||
kw.update(a[0].__dict__)
|
||||
@ -121,26 +120,26 @@ class SupermemoXmlImporter(NoteImporter):
|
||||
|
||||
def _fudgeText(self, text):
|
||||
"Replace sm syntax to Anki syntax"
|
||||
text = text.replace("\n\r", u"<br>")
|
||||
text = text.replace("\n", u"<br>")
|
||||
text = text.replace("\n\r", "<br>")
|
||||
text = text.replace("\n", "<br>")
|
||||
return text
|
||||
|
||||
def _unicode2ascii(self,str):
|
||||
"Remove diacritic punctuation from strings (titles)"
|
||||
return u"".join([ c for c in unicodedata.normalize('NFKD', str) if not unicodedata.combining(c)])
|
||||
return "".join([ c for c in unicodedata.normalize('NFKD', str) if not unicodedata.combining(c)])
|
||||
|
||||
def _decode_htmlescapes(self,s):
|
||||
"""Unescape HTML code."""
|
||||
#In case of bad formated html you can import MinimalSoup etc.. see btflsoup source code
|
||||
from BeautifulSoup import BeautifulStoneSoup as btflsoup
|
||||
from bs4 import BeautifulSoup as btflsoup
|
||||
|
||||
#my sm2004 also ecaped & char in escaped sequences.
|
||||
s = re.sub(u'&',u'&',s)
|
||||
s = re.sub('&','&',s)
|
||||
#unescaped solitary chars < or > that were ok for minidom confuse btfl soup
|
||||
#s = re.sub(u'>',u'>',s)
|
||||
#s = re.sub(u'<',u'<',s)
|
||||
|
||||
return unicode(btflsoup(s, selfClosingTags=['br','hr','img','wbr'], convertEntities=btflsoup.HTML_ENTITIES))
|
||||
return str(btflsoup(s, "html.parser"))
|
||||
|
||||
def _afactor2efactor(self, af):
|
||||
# Adapted from <http://www.supermemo.com/beta/xml/xml-core.htm>
|
||||
@ -173,9 +172,9 @@ class SupermemoXmlImporter(NoteImporter):
|
||||
|
||||
# Migrating content / time consuming part
|
||||
# addItemToCards is called for each sm element
|
||||
self.logger(u'Parsing started.')
|
||||
self.logger('Parsing started.')
|
||||
self.parse()
|
||||
self.logger(u'Parsing done.')
|
||||
self.logger('Parsing done.')
|
||||
|
||||
# Return imported cards
|
||||
self.total = len(self.notes)
|
||||
@ -201,7 +200,7 @@ class SupermemoXmlImporter(NoteImporter):
|
||||
# pre-process scheduling data
|
||||
# convert learning data
|
||||
if (not self.META.resetLearningData
|
||||
and item.Interval >= 1
|
||||
and int(item.Interval) >= 1
|
||||
and getattr(item, "LastRepetition", None)):
|
||||
# migration of LearningData algorithm
|
||||
tLastrep = time.mktime(time.strptime(item.LastRepetition, '%d.%m.%Y'))
|
||||
@ -221,7 +220,7 @@ class SupermemoXmlImporter(NoteImporter):
|
||||
# you can deceide if you are going to tag all toppics or just that containing some pattern
|
||||
tTaggTitle = False
|
||||
for pattern in self.META.pathsToBeTagged:
|
||||
if item.lTitle != None and pattern.lower() in u" ".join(item.lTitle).lower():
|
||||
if item.lTitle != None and pattern.lower() in " ".join(item.lTitle).lower():
|
||||
tTaggTitle = True
|
||||
break
|
||||
if tTaggTitle or self.META.tagAllTopics:
|
||||
@ -236,26 +235,26 @@ class SupermemoXmlImporter(NoteImporter):
|
||||
tmp = list(set([ re.sub('(\W)',' ', i ) for i in tmp ]))
|
||||
tmp = list(set([ re.sub( '^[0-9 ]+$','',i) for i in tmp ]))
|
||||
tmp = list(set([ capwords(i).replace(' ','') for i in tmp ]))
|
||||
tags = [ j[0].lower() + j[1:] for j in tmp if j.strip() <> '']
|
||||
tags = [ j[0].lower() + j[1:] for j in tmp if j.strip() != '']
|
||||
|
||||
note.tags += tags
|
||||
|
||||
if self.META.tagMemorizedItems and item.Interval >0:
|
||||
if self.META.tagMemorizedItems and int(item.Interval) >0:
|
||||
note.tags.append("Memorized")
|
||||
|
||||
self.logger(u'Element tags\t- ' + `note.tags`, level=3)
|
||||
self.logger('Element tags\t- ' + repr(note.tags), level=3)
|
||||
|
||||
self.notes.append(note)
|
||||
|
||||
def logger(self,text,level=1):
|
||||
"Wrapper for Anki logger"
|
||||
|
||||
dLevels={0:'',1:u'Info',2:u'Verbose',3:u'Debug'}
|
||||
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__+ u" - " + dLevels[level].ljust(9) +u' -\t'+ _(text)
|
||||
print(self.__class__.__name__+ " - " + dLevels[level].ljust(9) +' -\t'+ _(text))
|
||||
|
||||
|
||||
# OPEN AND LOAD
|
||||
@ -266,9 +265,9 @@ class SupermemoXmlImporter(NoteImporter):
|
||||
return sys.stdin
|
||||
|
||||
# try to open with urllib (if source is http, ftp, or file URL)
|
||||
import urllib
|
||||
import urllib.request, urllib.parse, urllib.error
|
||||
try:
|
||||
return urllib.urlopen(source)
|
||||
return urllib.request.urlopen(source)
|
||||
except (IOError, OSError):
|
||||
pass
|
||||
|
||||
@ -279,24 +278,24 @@ class SupermemoXmlImporter(NoteImporter):
|
||||
pass
|
||||
|
||||
# treat source as string
|
||||
import StringIO
|
||||
return StringIO.StringIO(str(source))
|
||||
import io
|
||||
return io.StringIO(str(source))
|
||||
|
||||
def loadSource(self, source):
|
||||
"""Load source file and parse with xml.dom.minidom"""
|
||||
self.source = source
|
||||
self.logger(u'Load started...')
|
||||
self.logger('Load started...')
|
||||
sock = open(self.source)
|
||||
self.xmldoc = minidom.parse(sock).documentElement
|
||||
sock.close()
|
||||
self.logger(u'Load done.')
|
||||
self.logger('Load done.')
|
||||
|
||||
|
||||
# PARSE
|
||||
def parse(self, node=None):
|
||||
"Parse method - parses document elements"
|
||||
|
||||
if node==None and self.xmldoc<>None:
|
||||
if node==None and self.xmldoc!=None:
|
||||
node = self.xmldoc
|
||||
|
||||
_method = "parse_%s" % node.__class__.__name__
|
||||
@ -304,7 +303,7 @@ class SupermemoXmlImporter(NoteImporter):
|
||||
parseMethod = getattr(self, _method)
|
||||
parseMethod(node)
|
||||
else:
|
||||
self.logger(u'No handler for method %s' % _method, level=3)
|
||||
self.logger('No handler for method %s' % _method, level=3)
|
||||
|
||||
def parse_Document(self, node):
|
||||
"Parse XML document"
|
||||
@ -319,7 +318,7 @@ class SupermemoXmlImporter(NoteImporter):
|
||||
handlerMethod = getattr(self, _method)
|
||||
handlerMethod(node)
|
||||
else:
|
||||
self.logger(u'No handler for method %s' % _method, level=3)
|
||||
self.logger('No handler for method %s' % _method, level=3)
|
||||
#print traceback.print_exc()
|
||||
|
||||
def parse_Text(self, node):
|
||||
@ -353,7 +352,7 @@ class SupermemoXmlImporter(NoteImporter):
|
||||
for child in node.childNodes: self.parse(child)
|
||||
|
||||
#strip all saved strings, just for sure
|
||||
for key in self.cntElm[-1].keys():
|
||||
for key in list(self.cntElm[-1].keys()):
|
||||
if hasattr(self.cntElm[-1][key], 'strip'):
|
||||
self.cntElm[-1][key]=self.cntElm[-1][key].strip()
|
||||
|
||||
@ -367,18 +366,18 @@ class SupermemoXmlImporter(NoteImporter):
|
||||
|
||||
# migrate only memorized otherway skip/continue
|
||||
if self.META.onlyMemorizedItems and not(int(smel.Interval) > 0):
|
||||
self.logger(u'Element skiped \t- not memorized ...', level=3)
|
||||
self.logger('Element skiped \t- not memorized ...', level=3)
|
||||
else:
|
||||
#import sm element data to Anki
|
||||
self.addItemToCards(smel)
|
||||
self.logger(u"Import element \t- " + smel['Question'], level=3)
|
||||
self.logger("Import element \t- " + smel['Question'], level=3)
|
||||
|
||||
#print element
|
||||
self.logger('-'*45, level=3)
|
||||
for key in smel.keys():
|
||||
for key in list(smel.keys()):
|
||||
self.logger('\t%s %s' % ((key+':').ljust(15),smel[key]), level=3 )
|
||||
else:
|
||||
self.logger(u'Element skiped \t- no valid Q and A ...', level=3)
|
||||
self.logger('Element skiped \t- no valid Q and A ...', level=3)
|
||||
|
||||
|
||||
else:
|
||||
@ -389,7 +388,7 @@ class SupermemoXmlImporter(NoteImporter):
|
||||
if smel.Title != None:
|
||||
# remove topic from title list
|
||||
t = self.cntMeta['title'].pop()
|
||||
self.logger(u'End of topic \t- %s' % (t), level=2)
|
||||
self.logger('End of topic \t- %s' % (t), level=2)
|
||||
|
||||
def do_Content(self, node):
|
||||
"Process SM element Content"
|
||||
@ -422,7 +421,7 @@ class SupermemoXmlImporter(NoteImporter):
|
||||
self.cntElm[-1][node.tagName] = t
|
||||
self.cntMeta['title'].append(t)
|
||||
self.cntElm[-1]['lTitle'] = self.cntMeta['title']
|
||||
self.logger(u'Start of topic \t- ' + u" / ".join(self.cntMeta['title']), level=2)
|
||||
self.logger('Start of topic \t- ' + " / ".join(self.cntMeta['title']), level=2)
|
||||
|
||||
|
||||
def do_Type(self, node):
|
||||
|
88
anki/lang.py
88
anki/lang.py
@ -7,48 +7,48 @@ import gettext
|
||||
import threading
|
||||
|
||||
langs = [
|
||||
(u"Afrikaans", "af"),
|
||||
(u"Bahasa Melayu", "ms"),
|
||||
(u"Dansk", "da"),
|
||||
(u"Deutsch", "de"),
|
||||
(u"Eesti", "et"),
|
||||
(u"English", "en"),
|
||||
(u"Español", "es"),
|
||||
(u"Esperanto", "eo"),
|
||||
(u"Français", "fr"),
|
||||
(u"Galego", "gl"),
|
||||
(u"Italiano", "it"),
|
||||
(u"Lenga d'òc", "oc"),
|
||||
(u"Magyar", "hu"),
|
||||
(u"Nederlands","nl"),
|
||||
(u"Norsk","nb"),
|
||||
(u"Occitan","oc"),
|
||||
(u"Plattdüütsch", "nds"),
|
||||
(u"Polski", "pl"),
|
||||
(u"Português Brasileiro", "pt_BR"),
|
||||
(u"Português", "pt"),
|
||||
(u"Româneşte", "ro"),
|
||||
(u"Slovenščina", "sl"),
|
||||
(u"Suomi", "fi"),
|
||||
(u"Svenska", "sv"),
|
||||
(u"Tiếng Việt", "vi"),
|
||||
(u"Türkçe", "tr"),
|
||||
(u"Čeština", "cs"),
|
||||
(u"Ελληνικά", "el"),
|
||||
(u"босански", "bs"),
|
||||
(u"Български", "bg"),
|
||||
(u"Монгол хэл","mn"),
|
||||
(u"русский язык", "ru"),
|
||||
(u"Српски", "sr"),
|
||||
(u"українська мова", "uk"),
|
||||
(u"עִבְרִית", "he"),
|
||||
(u"العربية", "ar"),
|
||||
(u"فارسی", "fa"),
|
||||
(u"ภาษาไทย", "th"),
|
||||
(u"日本語", "ja"),
|
||||
(u"简体中文", "zh_CN"),
|
||||
(u"繁體中文", "zh_TW"),
|
||||
(u"한국어", "ko"),
|
||||
("Afrikaans", "af"),
|
||||
("Bahasa Melayu", "ms"),
|
||||
("Dansk", "da"),
|
||||
("Deutsch", "de"),
|
||||
("Eesti", "et"),
|
||||
("English", "en"),
|
||||
("Español", "es"),
|
||||
("Esperanto", "eo"),
|
||||
("Français", "fr"),
|
||||
("Galego", "gl"),
|
||||
("Italiano", "it"),
|
||||
("Lenga d'òc", "oc"),
|
||||
("Magyar", "hu"),
|
||||
("Nederlands","nl"),
|
||||
("Norsk","nb"),
|
||||
("Occitan","oc"),
|
||||
("Plattdüütsch", "nds"),
|
||||
("Polski", "pl"),
|
||||
("Português Brasileiro", "pt_BR"),
|
||||
("Português", "pt"),
|
||||
("Româneşte", "ro"),
|
||||
("Slovenščina", "sl"),
|
||||
("Suomi", "fi"),
|
||||
("Svenska", "sv"),
|
||||
("Tiếng Việt", "vi"),
|
||||
("Türkçe", "tr"),
|
||||
("Čeština", "cs"),
|
||||
("Ελληνικά", "el"),
|
||||
("босански", "bs"),
|
||||
("Български", "bg"),
|
||||
("Монгол хэл","mn"),
|
||||
("русский язык", "ru"),
|
||||
("Српски", "sr"),
|
||||
("українська мова", "uk"),
|
||||
("עִבְרִית", "he"),
|
||||
("العربية", "ar"),
|
||||
("فارسی", "fa"),
|
||||
("ภาษาไทย", "th"),
|
||||
("日本語", "ja"),
|
||||
("简体中文", "zh_CN"),
|
||||
("繁體中文", "zh_TW"),
|
||||
("한국어", "ko"),
|
||||
]
|
||||
|
||||
threadLocal = threading.local()
|
||||
@ -65,10 +65,10 @@ def localTranslation():
|
||||
return currentTranslation
|
||||
|
||||
def _(str):
|
||||
return localTranslation().ugettext(str)
|
||||
return localTranslation().gettext(str)
|
||||
|
||||
def ngettext(single, plural, n):
|
||||
return localTranslation().ungettext(single, plural, n)
|
||||
return localTranslation().ngettext(single, plural, n)
|
||||
|
||||
def langDir():
|
||||
dir = os.path.join(os.path.dirname(
|
||||
|
@ -56,7 +56,7 @@ def _imgLink(col, latex, model):
|
||||
if os.path.exists(fname):
|
||||
return link
|
||||
elif not build:
|
||||
return u"[latex]%s[/latex]" % latex
|
||||
return "[latex]%s[/latex]" % latex
|
||||
else:
|
||||
err = _buildImg(col, txt, fname, model)
|
||||
if err:
|
||||
@ -71,11 +71,10 @@ def _latexFromHtml(col, latex):
|
||||
return latex
|
||||
|
||||
def _buildImg(col, latex, fname, model):
|
||||
# add header/footer & convert to utf8
|
||||
# add header/footer
|
||||
latex = (model["latexPre"] + "\n" +
|
||||
latex + "\n" +
|
||||
model["latexPost"])
|
||||
latex = latex.encode("utf8")
|
||||
# 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",
|
||||
@ -91,7 +90,7 @@ package in the LaTeX header instead.""") % bad
|
||||
# write into a temp file
|
||||
log = open(namedtmp("latex_log.txt"), "w")
|
||||
texpath = namedtmp("tmp.tex")
|
||||
texfile = file(texpath, "w")
|
||||
texfile = open(texpath, "w")
|
||||
texfile.write(latex)
|
||||
texfile.close()
|
||||
mdir = col.media.dir()
|
||||
|
@ -4,11 +4,11 @@
|
||||
|
||||
import re
|
||||
import traceback
|
||||
import urllib
|
||||
import urllib.request, urllib.parse, urllib.error
|
||||
import unicodedata
|
||||
import sys
|
||||
import zipfile
|
||||
from cStringIO import StringIO
|
||||
from io import StringIO
|
||||
|
||||
from anki.utils import checksum, isWin, isMac, json
|
||||
from anki.db import DB
|
||||
@ -33,9 +33,6 @@ class MediaManager(object):
|
||||
return
|
||||
# media directory
|
||||
self._dir = re.sub("(?i)\.(anki2)$", ".media", self.col.path)
|
||||
# convert dir to unicode if it's not already
|
||||
if isinstance(self._dir, str):
|
||||
self._dir = unicode(self._dir, sys.getfilesystemencoding())
|
||||
if not os.path.exists(self._dir):
|
||||
os.makedirs(self._dir)
|
||||
try:
|
||||
@ -92,7 +89,7 @@ create table meta (dirMod int, lastUsn int); insert into meta values (0, 0);
|
||||
insert into meta select dirMod, usn from old.meta
|
||||
""")
|
||||
self.db.commit()
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
# if we couldn't import the old db for some reason, just start
|
||||
# anew
|
||||
self.col.log("failed to import old media db:"+traceback.format_exc())
|
||||
@ -223,16 +220,15 @@ create table meta (dirMod int, lastUsn int); insert into meta values (0, 0);
|
||||
|
||||
def escapeImages(self, string, unescape=False):
|
||||
if unescape:
|
||||
fn = urllib.unquote
|
||||
fn = urllib.parse.unquote
|
||||
else:
|
||||
fn = urllib.quote
|
||||
fn = urllib.parse.quote
|
||||
def repl(match):
|
||||
tag = match.group(0)
|
||||
fname = match.group("fname")
|
||||
if re.match("(https?|ftp)://", fname):
|
||||
return tag
|
||||
return tag.replace(
|
||||
fname, unicode(fn(fname.encode("utf-8")), "utf8"))
|
||||
return tag.replace(fname, fn(fname))
|
||||
for reg in self.imgRegexps:
|
||||
string = re.sub(reg, repl, string)
|
||||
return string
|
||||
@ -271,9 +267,6 @@ create table meta (dirMod int, lastUsn int); insert into meta values (0, 0);
|
||||
if file.startswith("_"):
|
||||
# leading _ says to ignore file
|
||||
continue
|
||||
if not isinstance(file, unicode):
|
||||
invalid.append(unicode(file, sys.getfilesystemencoding(), "replace"))
|
||||
continue
|
||||
nfcFile = unicodedata.normalize("NFC", file)
|
||||
# we enforce NFC fs encoding on non-macs; on macs we'll have gotten
|
||||
# NFD so we use the above variable for comparing references
|
||||
@ -322,9 +315,6 @@ create table meta (dirMod int, lastUsn int); insert into meta values (0, 0);
|
||||
return re.sub(self._illegalCharReg, "", str)
|
||||
|
||||
def hasIllegal(self, str):
|
||||
# a file that couldn't be decoded to unicode is considered invalid
|
||||
if not isinstance(str, unicode):
|
||||
return True
|
||||
return not not re.search(self._illegalCharReg, str)
|
||||
|
||||
# Tracking changes
|
||||
@ -413,7 +403,7 @@ create table meta (dirMod int, lastUsn int); insert into meta values (0, 0);
|
||||
# mark as used
|
||||
self.cache[f][2] = True
|
||||
# look for any entries in the cache that no longer exist on disk
|
||||
for (k, v) in self.cache.items():
|
||||
for (k, v) in list(self.cache.items()):
|
||||
if not v[2]:
|
||||
removed.append(k)
|
||||
return added, removed
|
||||
@ -510,8 +500,6 @@ create table meta (dirMod int, lastUsn int); insert into meta values (0, 0);
|
||||
data = z.read(i)
|
||||
csum = checksum(data)
|
||||
name = meta[i.filename]
|
||||
if not isinstance(name, unicode):
|
||||
name = unicode(name, "utf8")
|
||||
# normalize name for platform
|
||||
if isMac:
|
||||
name = unicodedata.normalize("NFD", name)
|
||||
|
@ -108,7 +108,7 @@ class ModelManager(object):
|
||||
m = self.get(self.col.decks.current().get('mid'))
|
||||
if not forDeck or not m:
|
||||
m = self.get(self.col.conf['curModel'])
|
||||
return m or self.models.values()[0]
|
||||
return m or list(self.models.values())[0]
|
||||
|
||||
def setCurrent(self, m):
|
||||
self.col.conf['curModel'] = m['id']
|
||||
@ -122,14 +122,14 @@ class ModelManager(object):
|
||||
|
||||
def all(self):
|
||||
"Get all models."
|
||||
return self.models.values()
|
||||
return list(self.models.values())
|
||||
|
||||
def allNames(self):
|
||||
return [m['name'] for m in self.all()]
|
||||
|
||||
def byName(self, name):
|
||||
"Get model with NAME."
|
||||
for m in self.models.values():
|
||||
for m in list(self.models.values()):
|
||||
if m['name'] == name:
|
||||
return m
|
||||
|
||||
@ -158,7 +158,7 @@ select id from cards where nid in (select id from notes where mid = ?)""",
|
||||
self.save()
|
||||
# GUI should ensure last model is not deleted
|
||||
if current:
|
||||
self.setCurrent(self.models.values()[0])
|
||||
self.setCurrent(list(self.models.values())[0])
|
||||
|
||||
def add(self, m):
|
||||
self._setID(m)
|
||||
@ -191,7 +191,7 @@ select id from cards where nid in (select id from notes where mid = ?)""",
|
||||
return str(id) in self.models
|
||||
|
||||
def ids(self):
|
||||
return self.models.keys()
|
||||
return list(self.models.keys())
|
||||
|
||||
# Tools
|
||||
##################################################
|
||||
@ -429,7 +429,7 @@ select id from notes where mid = ?)""" % " ".join(map),
|
||||
"select id, flds from notes where id in "+ids2str(nids)):
|
||||
newflds = {}
|
||||
flds = splitFields(flds)
|
||||
for old, new in map.items():
|
||||
for old, new in list(map.items()):
|
||||
newflds[new] = flds[old]
|
||||
flds = []
|
||||
for c in range(nfields):
|
||||
|
@ -79,7 +79,7 @@ insert or replace into notes values (?,?,?,?,?,?,?,?,?,?,?)""",
|
||||
##################################################
|
||||
|
||||
def keys(self):
|
||||
return self._fmap.keys()
|
||||
return list(self._fmap.keys())
|
||||
|
||||
def values(self):
|
||||
return self.fields
|
||||
@ -101,7 +101,7 @@ insert or replace into notes values (?,?,?,?,?,?,?,?,?,?,?)""",
|
||||
self.fields[self._fieldOrd(key)] = value
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self._fmap.keys()
|
||||
return key in list(self._fmap.keys())
|
||||
|
||||
# Tags
|
||||
##################################################
|
||||
|
@ -2,7 +2,6 @@
|
||||
# Copyright: Damien Elmes <anki@ichi2.net>
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
from __future__ import division
|
||||
import time
|
||||
import random
|
||||
import itertools
|
||||
|
@ -24,8 +24,8 @@ def hasSound(text):
|
||||
|
||||
##########################################################################
|
||||
|
||||
processingSrc = u"rec.wav"
|
||||
processingDst = u"rec.mp3"
|
||||
processingSrc = "rec.wav"
|
||||
processingDst = "rec.mp3"
|
||||
processingChain = []
|
||||
recFiles = []
|
||||
|
||||
@ -229,7 +229,7 @@ class _Recorder(object):
|
||||
if ret:
|
||||
raise Exception(_(
|
||||
"Error running %s") %
|
||||
u" ".join(c))
|
||||
" ".join(c))
|
||||
|
||||
class PyAudioThreadedRecorder(threading.Thread):
|
||||
|
||||
@ -258,7 +258,7 @@ class PyAudioThreadedRecorder(threading.Thread):
|
||||
while not self.finish:
|
||||
try:
|
||||
data = stream.read(chunk)
|
||||
except IOError, e:
|
||||
except IOError as e:
|
||||
if e[1] == pyaudio.paInputOverflowed:
|
||||
data = None
|
||||
else:
|
||||
@ -295,7 +295,7 @@ class PyAudioRecorder(_Recorder):
|
||||
|
||||
def file(self):
|
||||
if self.encode:
|
||||
tgt = u"rec%d.mp3" % time.time()
|
||||
tgt = "rec%d.mp3" % time.time()
|
||||
os.rename(processingDst, tgt)
|
||||
return tgt
|
||||
else:
|
||||
|
@ -2,7 +2,6 @@
|
||||
# Copyright: Damien Elmes <anki@ichi2.net>
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
from __future__ import division
|
||||
import time
|
||||
import datetime
|
||||
import json
|
||||
@ -112,7 +111,7 @@ class CollectionStats(object):
|
||||
def report(self, type=0):
|
||||
# 0=days, 1=weeks, 2=months
|
||||
self.type = type
|
||||
from statsbg import bg
|
||||
from .statsbg import bg
|
||||
txt = self.css % bg
|
||||
txt += self.todayStats()
|
||||
txt += self.dueGraph()
|
||||
@ -160,7 +159,7 @@ from revlog where id > ? """+lim, (self.col.sched.dayCutoff-86400)*1000)
|
||||
filt = filt or 0
|
||||
# studied
|
||||
def bold(s):
|
||||
return "<b>"+unicode(s)+"</b>"
|
||||
return "<b>"+str(s)+"</b>"
|
||||
msgp1 = ngettext("<!--studied-->%d card", "<!--studied-->%d cards", cards) % cards
|
||||
b += _("Studied %(a)s in %(b)s today.") % dict(
|
||||
a=bold(msgp1), b=bold(fmtTimeSpan(thetime, unit=1)))
|
||||
|
32
anki/sync.py
32
anki/sync.py
@ -2,19 +2,19 @@
|
||||
# Copyright: Damien Elmes <anki@ichi2.net>
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
import urllib
|
||||
import urllib.request, urllib.parse, urllib.error
|
||||
import sys
|
||||
import gzip
|
||||
import random
|
||||
from cStringIO import StringIO
|
||||
from io import StringIO
|
||||
|
||||
import httplib2
|
||||
from anki.db import DB
|
||||
from anki.utils import ids2str, intTime, json, isWin, isMac, platDesc, checksum
|
||||
from anki.consts import *
|
||||
from hooks import runHook
|
||||
from .hooks import runHook
|
||||
import anki
|
||||
from lang import ngettext
|
||||
from .lang import ngettext
|
||||
|
||||
# syncing vars
|
||||
HTTP_TIMEOUT = 90
|
||||
@ -64,17 +64,19 @@ def _setupProxy():
|
||||
# platform-specific fetch
|
||||
url = None
|
||||
if isWin:
|
||||
r = urllib.getproxies_registry()
|
||||
if 'https' in r:
|
||||
url = r['https']
|
||||
elif 'http' in r:
|
||||
url = r['http']
|
||||
print("fixme: win proxy support")
|
||||
# r = urllib.getproxies_registry()
|
||||
# if 'https' in r:
|
||||
# url = r['https']
|
||||
# elif 'http' in r:
|
||||
# url = r['http']
|
||||
elif isMac:
|
||||
r = urllib.getproxies_macosx_sysconf()
|
||||
if 'https' in r:
|
||||
url = r['https']
|
||||
elif 'http' in r:
|
||||
url = r['http']
|
||||
print("fixme: mac proxy support")
|
||||
# r = urllib.getproxies_macosx_sysconf()
|
||||
# if 'https' in r:
|
||||
# url = r['https']
|
||||
# elif 'http' in r:
|
||||
# url = r['http']
|
||||
if url:
|
||||
p = _proxy_info_from_url(url, _proxyMethod(url))
|
||||
if p:
|
||||
@ -556,7 +558,7 @@ class HttpSyncer(object):
|
||||
buf = StringIO()
|
||||
# post vars
|
||||
self.postVars['c'] = 1 if comp else 0
|
||||
for (key, value) in self.postVars.items():
|
||||
for (key, value) in list(self.postVars.items()):
|
||||
buf.write(bdry + "\r\n")
|
||||
buf.write(
|
||||
'Content-Disposition: form-data; name="%s"\r\n\r\n%s\r\n' %
|
||||
|
12
anki/tags.py
12
anki/tags.py
@ -47,7 +47,7 @@ class TagManager(object):
|
||||
runHook("newTag")
|
||||
|
||||
def all(self):
|
||||
return self.tags.keys()
|
||||
return list(self.tags.keys())
|
||||
|
||||
def registerNotes(self, nids=None):
|
||||
"Add any missing tags from notes to the tags list."
|
||||
@ -62,7 +62,7 @@ class TagManager(object):
|
||||
" ".join(self.col.db.list("select distinct tags from notes"+lim)))))
|
||||
|
||||
def allItems(self):
|
||||
return self.tags.items()
|
||||
return list(self.tags.items())
|
||||
|
||||
def save(self):
|
||||
self.changed = True
|
||||
@ -122,13 +122,13 @@ class TagManager(object):
|
||||
|
||||
def split(self, tags):
|
||||
"Parse a string and return a list of tags."
|
||||
return [t for t in tags.replace(u'\u3000', ' ').split(" ") if t]
|
||||
return [t for t in tags.replace('\u3000', ' ').split(" ") if t]
|
||||
|
||||
def join(self, tags):
|
||||
"Join tags into a single string, with leading and trailing spaces."
|
||||
if not tags:
|
||||
return u""
|
||||
return u" %s " % u" ".join(tags)
|
||||
return ""
|
||||
return " %s " % " ".join(tags)
|
||||
|
||||
def addToStr(self, addtags, tags):
|
||||
"Add tags if they don't exist, and canonify."
|
||||
@ -174,6 +174,6 @@ class TagManager(object):
|
||||
##########################################################################
|
||||
|
||||
def beforeUpload(self):
|
||||
for k in self.tags.keys():
|
||||
for k in list(self.tags.keys()):
|
||||
self.tags[k] = 0
|
||||
self.save()
|
||||
|
@ -1,78 +0,0 @@
|
||||
========
|
||||
Pystache
|
||||
========
|
||||
|
||||
Inspired by ctemplate_ and et_, Mustache_ is a
|
||||
framework-agnostic way to render logic-free views.
|
||||
|
||||
As ctemplates says, "It emphasizes separating logic from presentation:
|
||||
it is impossible to embed application logic in this template language."
|
||||
|
||||
Pystache is a Python implementation of Mustache. Pystache requires
|
||||
Python 2.6.
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
||||
The different Mustache tags are documented at `mustache(5)`_.
|
||||
|
||||
Install It
|
||||
==========
|
||||
|
||||
::
|
||||
|
||||
pip install pystache
|
||||
|
||||
|
||||
Use It
|
||||
======
|
||||
|
||||
::
|
||||
|
||||
>>> import pystache
|
||||
>>> pystache.render('Hi {{person}}!', {'person': 'Mom'})
|
||||
'Hi Mom!'
|
||||
|
||||
You can also create dedicated view classes to hold your view logic.
|
||||
|
||||
Here's your simple.py::
|
||||
|
||||
import pystache
|
||||
class Simple(pystache.View):
|
||||
def thing(self):
|
||||
return "pizza"
|
||||
|
||||
Then your template, simple.mustache::
|
||||
|
||||
Hi {{thing}}!
|
||||
|
||||
Pull it together::
|
||||
|
||||
>>> Simple().render()
|
||||
'Hi pizza!'
|
||||
|
||||
|
||||
Test It
|
||||
=======
|
||||
|
||||
nose_ works great! ::
|
||||
|
||||
pip install nose
|
||||
cd pystache
|
||||
nosetests
|
||||
|
||||
|
||||
Author
|
||||
======
|
||||
|
||||
::
|
||||
|
||||
context = { 'author': 'Chris Wanstrath', 'email': 'chris@ozmm.org' }
|
||||
pystache.render("{{author}} :: {{email}}", context)
|
||||
|
||||
|
||||
.. _ctemplate: http://code.google.com/p/google-ctemplate/
|
||||
.. _et: http://www.ivan.fomichev.name/2008/05/erlang-template-engine-prototype.html
|
||||
.. _Mustache: http://defunkt.github.com/mustache/
|
||||
.. _mustache(5): http://defunkt.github.com/mustache/mustache.5.html
|
||||
.. _nose: http://somethingaboutorange.com/mrl/projects/nose/0.11.1/testing.html
|
@ -99,7 +99,7 @@ class Template(object):
|
||||
replacer = ''
|
||||
# if it and isinstance(it, collections.Callable):
|
||||
# replacer = it(inner)
|
||||
if isinstance(it, basestring):
|
||||
if isinstance(it, str):
|
||||
it = stripHTMLMedia(it).strip()
|
||||
if it and not hasattr(it, '__iter__'):
|
||||
if section[2] != '^':
|
||||
@ -133,7 +133,7 @@ class Template(object):
|
||||
replacement = func(self, tag_name, context)
|
||||
template = template.replace(tag, replacement)
|
||||
except (SyntaxError, KeyError):
|
||||
return u"{{invalid template}}"
|
||||
return "{{invalid template}}"
|
||||
|
||||
return template
|
||||
|
||||
|
@ -53,7 +53,7 @@ class View(object):
|
||||
|
||||
name = self.get_template_name() + '.' + self.template_extension
|
||||
|
||||
if isinstance(self.template_path, basestring):
|
||||
if isinstance(self.template_path, str):
|
||||
self.template_file = os.path.join(self.template_path, name)
|
||||
return self._load_template()
|
||||
|
||||
@ -70,7 +70,7 @@ class View(object):
|
||||
try:
|
||||
template = f.read()
|
||||
if self.template_encoding:
|
||||
template = unicode(template, self.template_encoding)
|
||||
template = str(template, self.template_encoding)
|
||||
finally:
|
||||
f.close()
|
||||
return template
|
||||
|
@ -2,13 +2,12 @@
|
||||
# Copyright: Damien Elmes <anki@ichi2.net>
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
from __future__ import division
|
||||
import re
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
import math
|
||||
import htmlentitydefs
|
||||
import html.entities
|
||||
import subprocess
|
||||
import tempfile
|
||||
import shutil
|
||||
@ -18,28 +17,10 @@ import locale
|
||||
from hashlib import sha1
|
||||
import platform
|
||||
import traceback
|
||||
import json
|
||||
|
||||
from anki.lang import _, ngettext
|
||||
|
||||
|
||||
if sys.version_info[1] < 5:
|
||||
def format_string(a, b):
|
||||
return a % b
|
||||
locale.format_string = format_string
|
||||
|
||||
try:
|
||||
import simplejson as json
|
||||
# make sure simplejson's loads() always returns unicode
|
||||
# we don't try to support .load()
|
||||
origLoads = json.loads
|
||||
def loads(s, *args, **kwargs):
|
||||
if not isinstance(s, unicode):
|
||||
s = unicode(s, "utf8")
|
||||
return origLoads(s, *args, **kwargs)
|
||||
json.loads = loads
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
# Time handling
|
||||
##############################################################################
|
||||
|
||||
@ -182,15 +163,15 @@ def entsToTxt(html):
|
||||
# character reference
|
||||
try:
|
||||
if text[:3] == "&#x":
|
||||
return unichr(int(text[3:-1], 16))
|
||||
return chr(int(text[3:-1], 16))
|
||||
else:
|
||||
return unichr(int(text[2:-1]))
|
||||
return chr(int(text[2:-1]))
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
# named entity
|
||||
try:
|
||||
text = unichr(htmlentitydefs.name2codepoint[text[1:-1]])
|
||||
text = chr(html.entities.name2codepoint[text[1:-1]])
|
||||
except KeyError:
|
||||
pass
|
||||
return text # leave as is
|
||||
@ -222,8 +203,7 @@ def maxID(db):
|
||||
"Return the first safe ID to use."
|
||||
now = intTime(1000)
|
||||
for tbl in "cards", "notes":
|
||||
now = max(now, db.scalar(
|
||||
"select max(id) from %s" % tbl))
|
||||
now = max(now, db.scalar("select max(id) from %s" % tbl) or 0)
|
||||
return now + 1
|
||||
|
||||
# used in ankiweb
|
||||
@ -271,7 +251,7 @@ def splitFields(string):
|
||||
##############################################################################
|
||||
|
||||
def checksum(data):
|
||||
if isinstance(data, unicode):
|
||||
if isinstance(data, str):
|
||||
data = data.encode("utf-8")
|
||||
return sha1(data).hexdigest()
|
||||
|
||||
@ -292,8 +272,7 @@ def tmpdir():
|
||||
shutil.rmtree(_tmpdir)
|
||||
import atexit
|
||||
atexit.register(cleanup)
|
||||
_tmpdir = unicode(os.path.join(tempfile.gettempdir(), "anki_temp"), \
|
||||
sys.getfilesystemencoding())
|
||||
_tmpdir = os.path.join(tempfile.gettempdir(), "anki_temp")
|
||||
if not os.path.exists(_tmpdir):
|
||||
os.mkdir(_tmpdir)
|
||||
return _tmpdir
|
||||
|
@ -5,7 +5,7 @@ import os
|
||||
import sys
|
||||
import optparse
|
||||
import tempfile
|
||||
import __builtin__
|
||||
import builtins
|
||||
import locale
|
||||
import gettext
|
||||
|
||||
@ -29,16 +29,16 @@ moduleDir = os.path.split(os.path.dirname(os.path.abspath(__file__)))[0]
|
||||
|
||||
try:
|
||||
import aqt.forms
|
||||
except ImportError, e:
|
||||
except ImportError as e:
|
||||
if "forms" in str(e):
|
||||
print "If you're running from git, did you run build_ui.sh?"
|
||||
print
|
||||
print("If you're running from git, did you run build_ui.sh?")
|
||||
print()
|
||||
raise
|
||||
|
||||
from anki.utils import checksum
|
||||
|
||||
# Dialog manager - manages modeless windows
|
||||
##########################################################################
|
||||
##########################################################################emacs
|
||||
|
||||
class DialogManager(object):
|
||||
|
||||
@ -67,7 +67,7 @@ class DialogManager(object):
|
||||
|
||||
def closeAll(self):
|
||||
"True if all closed successfully."
|
||||
for (n, (creator, instance)) in self._dialogs.items():
|
||||
for (n, (creator, instance)) in list(self._dialogs.items()):
|
||||
if instance:
|
||||
if not instance.canClose():
|
||||
return False
|
||||
@ -98,8 +98,8 @@ def setupLang(pm, app, force=None):
|
||||
# gettext
|
||||
_gtrans = gettext.translation(
|
||||
'anki', dir, languages=[lang], fallback=True)
|
||||
__builtin__.__dict__['_'] = _gtrans.ugettext
|
||||
__builtin__.__dict__['ngettext'] = _gtrans.ungettext
|
||||
builtins.__dict__['_'] = _gtrans.gettext
|
||||
builtins.__dict__['ngettext'] = _gtrans.ngettext
|
||||
anki.lang.setLang(lang, local=False)
|
||||
if lang in ("he","ar","fa"):
|
||||
app.setLayoutDirection(Qt.RightToLeft)
|
||||
@ -133,7 +133,7 @@ class AnkiApp(QApplication):
|
||||
if args and args[0]:
|
||||
buf = os.path.abspath(args[0])
|
||||
if self.sendMsg(buf):
|
||||
print "Already running; reusing existing instance."
|
||||
print("Already running; reusing existing instance.")
|
||||
return True
|
||||
else:
|
||||
# send failed, so we're the first instance or the
|
||||
@ -163,7 +163,7 @@ class AnkiApp(QApplication):
|
||||
sys.stderr.write(sock.errorString())
|
||||
return
|
||||
buf = sock.readAll()
|
||||
buf = unicode(buf, sys.getfilesystemencoding(), "ignore")
|
||||
buf = str(buf, sys.getfilesystemencoding(), "ignore")
|
||||
self.emit(SIGNAL("appMsg"), buf)
|
||||
sock.disconnectFromServer()
|
||||
|
||||
@ -192,7 +192,7 @@ def parseArgs(argv):
|
||||
def run():
|
||||
try:
|
||||
_run()
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
QMessageBox.critical(None, "Startup Error",
|
||||
"Please notify support of this error:\n\n"+
|
||||
traceback.format_exc())
|
||||
@ -202,8 +202,8 @@ def _run():
|
||||
|
||||
# parse args
|
||||
opts, args = parseArgs(sys.argv)
|
||||
opts.base = unicode(opts.base or "", sys.getfilesystemencoding())
|
||||
opts.profile = unicode(opts.profile or "", sys.getfilesystemencoding())
|
||||
opts.base = opts.base or ""
|
||||
opts.profile = opts.profile or ""
|
||||
|
||||
# on osx we'll need to add the qt plugins to the search path
|
||||
if isMac and getattr(sys, 'frozen', None):
|
||||
|
@ -27,7 +27,7 @@ system. It's free and open source.")
|
||||
abouttext += (_("<a href='%s'>Visit website</a>") % aqt.appWebsite) + \
|
||||
"</span>"
|
||||
abouttext += '<p>' + _("Written by Damien Elmes, with patches, translation,\
|
||||
testing and design from:<p>%(cont)s") % {'cont': u"""Aaron Harsh, Ádám Szegi,
|
||||
testing and design from:<p>%(cont)s") % {'cont': """Aaron Harsh, Ádám Szegi,
|
||||
Alex Fraser, Andreas Klauer, Andrew Wright, Bernhard Ibertsberger, C. van Rooyen, Charlene Barina,
|
||||
Christian Krause, Christian Rusche, David Smith, Dave Druelinger, Dotan Cohen,
|
||||
Emilio Wuerges, Emmanuel Jarri, Frank Harper, Gregor Skumavc, H. Mijail,
|
||||
|
@ -69,7 +69,7 @@ class AddCards(QDialog):
|
||||
self.connect(self.helpButton, SIGNAL("clicked()"), self.helpRequested)
|
||||
# history
|
||||
b = bb.addButton(
|
||||
_("History")+ u" "+downArrow(), ar)
|
||||
_("History")+ " "+downArrow(), ar)
|
||||
if isMac:
|
||||
sc = "Ctrl+Shift+H"
|
||||
else:
|
||||
@ -90,8 +90,8 @@ class AddCards(QDialog):
|
||||
oldNote = self.editor.note
|
||||
note = self.setupNewNote(set=False)
|
||||
if oldNote:
|
||||
oldFields = oldNote.keys()
|
||||
newFields = note.keys()
|
||||
oldFields = list(oldNote.keys())
|
||||
newFields = list(note.keys())
|
||||
for n, f in enumerate(note.model()['flds']):
|
||||
fieldName = f['name']
|
||||
try:
|
||||
|
@ -3,7 +3,7 @@
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
import sys, os, traceback
|
||||
from cStringIO import StringIO
|
||||
from io import StringIO
|
||||
import zipfile
|
||||
from aqt.qt import *
|
||||
from aqt.utils import showInfo, openFolder, isWin, openLink, \
|
||||
@ -73,7 +73,7 @@ class AddonManager(object):
|
||||
frm = aqt.forms.editaddon.Ui_Dialog()
|
||||
frm.setupUi(d)
|
||||
d.setWindowTitle(os.path.basename(path))
|
||||
frm.text.setPlainText(unicode(open(path).read(), "utf8"))
|
||||
frm.text.setPlainText(open(path).read())
|
||||
d.connect(frm.buttonBox, SIGNAL("accepted()"),
|
||||
lambda: self.onAcceptEdit(path, frm))
|
||||
d.exec_()
|
||||
|
@ -278,10 +278,10 @@ class DataModel(QAbstractTableModel):
|
||||
return a
|
||||
|
||||
def formatQA(self, txt):
|
||||
s = txt.replace("<br>", u" ")
|
||||
s = s.replace("<br />", u" ")
|
||||
s = s.replace("<div>", u" ")
|
||||
s = s.replace("\n", u" ")
|
||||
s = txt.replace("<br>", " ")
|
||||
s = s.replace("<br />", " ")
|
||||
s = s.replace("<div>", " ")
|
||||
s = s.replace("\n", " ")
|
||||
s = re.sub("\[sound:[^]]+\]", "", s)
|
||||
s = re.sub("\[\[type:[^]]+\]\]", "", s)
|
||||
s = stripHTMLMedia(s)
|
||||
@ -518,7 +518,7 @@ class Browser(QMainWindow):
|
||||
|
||||
def onSearch(self, reset=True):
|
||||
"Careful: if reset is true, the current note is saved."
|
||||
txt = unicode(self.form.searchEdit.lineEdit().text()).strip()
|
||||
txt = str(self.form.searchEdit.lineEdit().text()).strip()
|
||||
prompt = _("<type here to search; hit enter to show current deck>")
|
||||
sh = self.mw.pm.profile['searchHistory']
|
||||
# update search history
|
||||
@ -788,12 +788,12 @@ by clicking on one on the left."""))
|
||||
if self.mw.app.keyboardModifiers() & Qt.AltModifier:
|
||||
txt = "-"+txt
|
||||
if self.mw.app.keyboardModifiers() & Qt.ControlModifier:
|
||||
cur = unicode(self.form.searchEdit.lineEdit().text())
|
||||
cur = str(self.form.searchEdit.lineEdit().text())
|
||||
if cur and cur != \
|
||||
_("<type here to search; hit enter to show current deck>"):
|
||||
txt = cur + " " + txt
|
||||
elif self.mw.app.keyboardModifiers() & Qt.ShiftModifier:
|
||||
cur = unicode(self.form.searchEdit.lineEdit().text())
|
||||
cur = str(self.form.searchEdit.lineEdit().text())
|
||||
if cur:
|
||||
txt = cur + " or " + txt
|
||||
self.form.searchEdit.lineEdit().setText(txt)
|
||||
@ -1365,8 +1365,8 @@ update cards set usn=?, mod=?, did=? where id in """ + scids,
|
||||
self.model.beginReset()
|
||||
try:
|
||||
changed = self.col.findReplace(sf,
|
||||
unicode(frm.find.text()),
|
||||
unicode(frm.replace.text()),
|
||||
str(frm.find.text()),
|
||||
str(frm.replace.text()),
|
||||
frm.re.isChecked(),
|
||||
field,
|
||||
frm.ignoreCase.isChecked())
|
||||
@ -1679,7 +1679,7 @@ class ChangeModel(QDialog):
|
||||
# check maps
|
||||
fmap = self.getFieldMap()
|
||||
cmap = self.getTemplateMap()
|
||||
if any(True for c in cmap.values() if c is None):
|
||||
if any(True for c in list(cmap.values()) if c is None):
|
||||
if not askUser(_("""\
|
||||
Any cards mapped to nothing will be deleted. \
|
||||
If a note has no remaining cards, it will be lost. \
|
||||
@ -1786,7 +1786,7 @@ class FavouritesLineEdit(QLineEdit):
|
||||
self.mw = mw
|
||||
self.browser = browser
|
||||
# add conf if missing
|
||||
if not self.mw.col.conf.has_key('savedFilters'):
|
||||
if 'savedFilters' not in self.mw.col.conf:
|
||||
self.mw.col.conf['savedFilters'] = {}
|
||||
self.button = QToolButton(self)
|
||||
self.button.setStyleSheet('border: 0px;')
|
||||
@ -1818,8 +1818,8 @@ class FavouritesLineEdit(QLineEdit):
|
||||
def updateButton(self, reset=True):
|
||||
# If search text is a saved query, switch to the delete button.
|
||||
# Otherwise show save button.
|
||||
txt = unicode(self.text()).strip()
|
||||
for key, value in self.mw.col.conf['savedFilters'].items():
|
||||
txt = str(self.text()).strip()
|
||||
for key, value in list(self.mw.col.conf['savedFilters'].items()):
|
||||
if txt == value:
|
||||
self.doSave = False
|
||||
self.name = key
|
||||
@ -1835,7 +1835,7 @@ class FavouritesLineEdit(QLineEdit):
|
||||
self.deleteClicked()
|
||||
|
||||
def saveClicked(self):
|
||||
txt = unicode(self.text()).strip()
|
||||
txt = str(self.text()).strip()
|
||||
dlg = QInputDialog(self)
|
||||
dlg.setInputMode(QInputDialog.TextInput)
|
||||
dlg.setLabelText(_("The current search terms will be added as a new "
|
||||
|
@ -32,7 +32,7 @@ class CardLayout(QDialog):
|
||||
if addMode:
|
||||
# save it to DB temporarily
|
||||
self.emptyFields = []
|
||||
for name, val in note.items():
|
||||
for name, val in list(note.items()):
|
||||
if val.strip():
|
||||
continue
|
||||
self.emptyFields.append(name)
|
||||
@ -90,10 +90,10 @@ class CardLayout(QDialog):
|
||||
# template area
|
||||
tform = aqt.forms.template.Ui_Form()
|
||||
tform.setupUi(left)
|
||||
tform.label1.setText(u" →")
|
||||
tform.label2.setText(u" →")
|
||||
tform.labelc1.setText(u" ↗")
|
||||
tform.labelc2.setText(u" ↘")
|
||||
tform.label1.setText(" →")
|
||||
tform.label2.setText(" →")
|
||||
tform.labelc1.setText(" ↗")
|
||||
tform.labelc2.setText(" ↘")
|
||||
if self.style().objectName() == "gtk+":
|
||||
# gtk+ requires margins in inner layout
|
||||
tform.tlayout1.setContentsMargins(0, 11, 0, 0)
|
||||
@ -167,7 +167,7 @@ Please create a new card type first."""))
|
||||
flip.setAutoDefault(False)
|
||||
l.addWidget(flip)
|
||||
c(flip, SIGNAL("clicked()"), self.onFlip)
|
||||
more = QPushButton(_("More") + u" "+downArrow())
|
||||
more = QPushButton(_("More") + " "+downArrow())
|
||||
more.setAutoDefault(False)
|
||||
l.addWidget(more)
|
||||
c(more, SIGNAL("clicked()"), lambda: self.onMore(more))
|
||||
@ -251,7 +251,7 @@ Please create a new card type first."""))
|
||||
txt = txt.replace("<hr id=answer>", "")
|
||||
hadHR = origLen != len(txt)
|
||||
def answerRepl(match):
|
||||
res = self.mw.reviewer.correct(u"exomple", u"an example")
|
||||
res = self.mw.reviewer.correct("exomple", "an example")
|
||||
if hadHR:
|
||||
res = "<hr id=answer>" + res
|
||||
return res
|
||||
|
@ -63,7 +63,7 @@ class DeckBrowser(object):
|
||||
|
||||
def _keyHandler(self, evt):
|
||||
# currently does nothing
|
||||
key = unicode(evt.text())
|
||||
key = str(evt.text())
|
||||
|
||||
def _selDeck(self, did):
|
||||
self.scrollPos = self.web.page().mainFrame().scrollPosition()
|
||||
@ -287,7 +287,7 @@ where id > ?""", (self.mw.col.sched.dayCutoff-86400)*1000)
|
||||
return
|
||||
try:
|
||||
self.mw.col.decks.rename(deck, newName)
|
||||
except DeckRenameError, e:
|
||||
except DeckRenameError as e:
|
||||
return showWarning(e.description)
|
||||
self.show()
|
||||
|
||||
@ -304,7 +304,7 @@ where id > ?""", (self.mw.col.sched.dayCutoff-86400)*1000)
|
||||
def _dragDeckOnto(self, draggedDeckDid, ontoDeckDid):
|
||||
try:
|
||||
self.mw.col.decks.renameForDragAndDrop(draggedDeckDid, ontoDeckDid)
|
||||
except DeckRenameError, e:
|
||||
except DeckRenameError as e:
|
||||
return showWarning(e.description)
|
||||
|
||||
self.show()
|
||||
|
@ -42,7 +42,7 @@ class DeckConf(QDialog):
|
||||
def setupCombos(self):
|
||||
import anki.consts as cs
|
||||
f = self.form
|
||||
f.newOrder.addItems(cs.newCardOrderLabels().values())
|
||||
f.newOrder.addItems(list(cs.newCardOrderLabels().values()))
|
||||
self.connect(f.newOrder, SIGNAL("currentIndexChanged(int)"),
|
||||
self.onNewOrderChanged)
|
||||
|
||||
@ -230,7 +230,7 @@ class DeckConf(QDialog):
|
||||
##################################################
|
||||
|
||||
def updateList(self, conf, key, w, minSize=1):
|
||||
items = unicode(w.text()).split(" ")
|
||||
items = str(w.text()).split(" ")
|
||||
ret = []
|
||||
for i in items:
|
||||
if not i:
|
||||
|
@ -65,12 +65,12 @@ class Downloader(QThread):
|
||||
try:
|
||||
resp, cont = con.request(
|
||||
aqt.appShared + "download/%d" % self.code)
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
exc = traceback.format_exc()
|
||||
try:
|
||||
self.error = unicode(e[0], "utf8", "ignore")
|
||||
self.error = str(e[0])
|
||||
except:
|
||||
self.error = unicode(exc, "utf8", "ignore")
|
||||
self.error = str(exc)
|
||||
return
|
||||
finally:
|
||||
remHook("httpRecv", recvEvent)
|
||||
|
@ -38,7 +38,7 @@ class DeckConf(QDialog):
|
||||
|
||||
def setupOrder(self):
|
||||
import anki.consts as cs
|
||||
self.form.order.addItems(cs.dynOrderLabels().values())
|
||||
self.form.order.addItems(list(cs.dynOrderLabels().values()))
|
||||
|
||||
def loadConf(self):
|
||||
f = self.form
|
||||
@ -94,7 +94,7 @@ it?""")):
|
||||
return " ".join([str(x) for x in l])
|
||||
|
||||
def userToList(self, w, minSize=1):
|
||||
items = unicode(w.text()).split(" ")
|
||||
items = str(w.text()).split(" ")
|
||||
ret = []
|
||||
for i in items:
|
||||
if not i:
|
||||
|
@ -3,9 +3,9 @@
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
import re
|
||||
import os
|
||||
import urllib2
|
||||
import urllib.request, urllib.error, urllib.parse
|
||||
import ctypes
|
||||
import urllib
|
||||
import urllib.request, urllib.parse, urllib.error
|
||||
|
||||
from anki.lang import _
|
||||
from aqt.qt import *
|
||||
@ -18,7 +18,7 @@ from aqt.utils import shortcut, showInfo, showWarning, getBase, getFile, \
|
||||
openHelp, tooltip, downArrow
|
||||
import aqt
|
||||
import anki.js
|
||||
from BeautifulSoup import BeautifulSoup
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
pics = ("jpg", "jpeg", "png", "tif", "tiff", "gif", "svg", "webp")
|
||||
audio = ("wav", "mp3", "ogg", "flac", "mp4", "swf", "mov", "mpeg", "mkv", "m4a", "3gp", "spx", "oga")
|
||||
@ -407,7 +407,7 @@ class Editor(object):
|
||||
runHook("setupEditorButtons", self)
|
||||
|
||||
def enableButtons(self, val=True):
|
||||
for b in self._buttons.values():
|
||||
for b in list(self._buttons.values()):
|
||||
b.setEnabled(val)
|
||||
|
||||
def disableButtons(self):
|
||||
@ -491,7 +491,7 @@ class Editor(object):
|
||||
elif str.startswith("dupes"):
|
||||
self.showDupes()
|
||||
else:
|
||||
print str
|
||||
print(str)
|
||||
|
||||
def mungeHTML(self, txt):
|
||||
if txt == "<br>":
|
||||
@ -536,7 +536,7 @@ class Editor(object):
|
||||
# will be loaded when page is ready
|
||||
return
|
||||
data = []
|
||||
for fld, val in self.note.items():
|
||||
for fld, val in list(self.note.items()):
|
||||
data.append((fld, self.mw.col.media.escapeImages(val)))
|
||||
self.web.eval("setFields(%s, %d);" % (
|
||||
json.dumps(data), field))
|
||||
@ -614,7 +614,7 @@ class Editor(object):
|
||||
html = form.textEdit.toPlainText()
|
||||
# filter html through beautifulsoup so we can strip out things like a
|
||||
# leading </div>
|
||||
html = unicode(BeautifulSoup(html))
|
||||
html = str(BeautifulSoup(html, "html.parser"))
|
||||
self.note.fields[self.currentField] = html
|
||||
self.loadNote()
|
||||
# focus field so it's saved
|
||||
@ -702,7 +702,7 @@ to a cloze type first, via Edit>Change Note Type."""))
|
||||
return
|
||||
# find the highest existing cloze
|
||||
highest = 0
|
||||
for name, val in self.note.items():
|
||||
for name, val in list(self.note.items()):
|
||||
m = re.findall("\{\{c(\d+)::", val)
|
||||
if m:
|
||||
highest = max(highest, sorted([int(x) for x in m])[-1])
|
||||
@ -785,7 +785,7 @@ to a cloze type first, via Edit>Change Note Type."""))
|
||||
def onRecSound(self):
|
||||
try:
|
||||
file = getAudio(self.widget)
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
showWarning(_(
|
||||
"Couldn't record audio. Have you installed lame and sox?") +
|
||||
"\n\n" + repr(str(e)))
|
||||
@ -804,7 +804,7 @@ to a cloze type first, via Edit>Change Note Type."""))
|
||||
def fnameToLink(self, fname):
|
||||
ext = fname.split(".")[-1].lower()
|
||||
if ext in pics:
|
||||
name = urllib.quote(fname.encode("utf8"))
|
||||
name = urllib.parse.quote(fname.encode("utf8"))
|
||||
return '<img src="%s">' % name
|
||||
else:
|
||||
anki.sound.play(fname)
|
||||
@ -837,22 +837,22 @@ to a cloze type first, via Edit>Change Note Type."""))
|
||||
self.mw.progress.start(
|
||||
immediate=True, parent=self.parentWindow)
|
||||
try:
|
||||
req = urllib2.Request(url, None, {
|
||||
req = urllib.request.Request(url, None, {
|
||||
'User-Agent': 'Mozilla/5.0 (compatible; Anki)'})
|
||||
filecontents = urllib2.urlopen(req).read()
|
||||
except urllib2.URLError, e:
|
||||
filecontents = urllib.request.urlopen(req).read()
|
||||
except urllib.error.URLError as e:
|
||||
showWarning(_("An error occurred while opening %s") % e)
|
||||
return
|
||||
finally:
|
||||
self.mw.progress.finish()
|
||||
path = unicode(urllib2.unquote(url.encode("utf8")), "utf8")
|
||||
path = urllib.parse.unquote(url)
|
||||
return self.mw.col.media.writeData(path, filecontents)
|
||||
|
||||
# HTML filtering
|
||||
######################################################################
|
||||
|
||||
def _filterHTML(self, html, localize=False):
|
||||
doc = BeautifulSoup(html)
|
||||
doc = BeautifulSoup(html, "html.parser")
|
||||
# remove implicit regular font style from outermost element
|
||||
if doc.span:
|
||||
try:
|
||||
@ -919,7 +919,7 @@ to a cloze type first, via Edit>Change Note Type."""))
|
||||
for elem in "html", "head", "body", "meta":
|
||||
for tag in doc(elem):
|
||||
tag.replaceWithChildren()
|
||||
html = unicode(doc)
|
||||
html = str(doc)
|
||||
return html
|
||||
|
||||
# Advanced menu
|
||||
@ -1136,7 +1136,7 @@ class EditorWebView(AnkiWebView):
|
||||
# be URL-encoded, and shouldn't be a file:// url unless they're browsing
|
||||
# locally, which we don't support
|
||||
def _processText(self, mime):
|
||||
txt = unicode(mime.text())
|
||||
txt = str(mime.text())
|
||||
html = None
|
||||
# if the user is pasting an image or sound link, convert it to local
|
||||
if self.editor.isURL(txt):
|
||||
|
@ -21,11 +21,8 @@ class ErrorHandler(QObject):
|
||||
sys.stderr = self
|
||||
|
||||
def write(self, data):
|
||||
# make sure we have unicode
|
||||
if not isinstance(data, unicode):
|
||||
data = unicode(data, "utf8", "replace")
|
||||
# dump to stdout
|
||||
sys.stdout.write(data.encode("utf-8"))
|
||||
sys.stdout.write(data)
|
||||
# save in buffer
|
||||
self.pool += data
|
||||
# and update timer
|
||||
|
@ -88,7 +88,7 @@ class ExportDialog(QDialog):
|
||||
deck_name = self.decks[self.frm.deck.currentIndex()]
|
||||
deck_name = re.sub('[\\\\/?<>:*|"^]', '_', deck_name)
|
||||
filename = os.path.join(aqt.mw.pm.base,
|
||||
u'{0}{1}'.format(deck_name, self.exporter.ext))
|
||||
'{0}{1}'.format(deck_name, self.exporter.ext))
|
||||
while 1:
|
||||
file = getSaveFile(self, _("Export"), "export",
|
||||
self.exporter.key, self.exporter.ext,
|
||||
@ -104,8 +104,8 @@ class ExportDialog(QDialog):
|
||||
try:
|
||||
f = open(file, "wb")
|
||||
f.close()
|
||||
except (OSError, IOError), e:
|
||||
showWarning(_("Couldn't save file: %s") % unicode(e))
|
||||
except (OSError, IOError) as e:
|
||||
showWarning(_("Couldn't save file: %s") % str(e))
|
||||
else:
|
||||
os.unlink(file)
|
||||
exportedMedia = lambda cnt: self.mw.progress.update(
|
||||
|
@ -138,7 +138,7 @@ you can enter it here. Use \\t to represent tab."""),
|
||||
elif d == ":":
|
||||
d = _("Colon")
|
||||
else:
|
||||
d = `d`
|
||||
d = repr(d)
|
||||
txt = _("Fields separated by: %s") % d
|
||||
self.frm.autoDetect.setText(txt)
|
||||
|
||||
@ -164,7 +164,7 @@ you can enter it here. Use \\t to represent tab."""),
|
||||
except UnicodeDecodeError:
|
||||
showUnicodeWarning()
|
||||
return
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
msg = _("Import failed.\n")
|
||||
err = repr(str(e))
|
||||
if "1-character string" in err:
|
||||
@ -172,7 +172,7 @@ you can enter it here. Use \\t to represent tab."""),
|
||||
elif "invalidTempFolder" in err:
|
||||
msg += self.mw.errorHandler.tempFolderMsg()
|
||||
else:
|
||||
msg += unicode(traceback.format_exc(), "ascii", "replace")
|
||||
msg += str(traceback.format_exc(), "ascii", "replace")
|
||||
showText(msg)
|
||||
return
|
||||
finally:
|
||||
@ -268,7 +268,7 @@ def onImport(mw):
|
||||
filter=filt)
|
||||
if not file:
|
||||
return
|
||||
file = unicode(file)
|
||||
file = str(file)
|
||||
importFile(mw, file)
|
||||
|
||||
def importFile(mw, file):
|
||||
@ -295,7 +295,7 @@ def importFile(mw, file):
|
||||
except UnicodeDecodeError:
|
||||
showUnicodeWarning()
|
||||
return
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
msg = repr(str(e))
|
||||
if msg == "'unknownFormat'":
|
||||
if file.endswith(".anki2"):
|
||||
@ -306,7 +306,7 @@ backup, please see the 'Backups' section of the user manual."""))
|
||||
showWarning(_("Unknown file format."))
|
||||
else:
|
||||
msg = _("Import failed. Debugging info:\n")
|
||||
msg += unicode(traceback.format_exc(), "ascii", "replace")
|
||||
msg += str(traceback.format_exc())
|
||||
showText(msg)
|
||||
return
|
||||
finally:
|
||||
@ -329,7 +329,7 @@ backup, please see the 'Backups' section of the user manual."""))
|
||||
importer.run()
|
||||
except zipfile.BadZipfile:
|
||||
showWarning(invalidZipMsg())
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
err = repr(str(e))
|
||||
if "invalidFile" in err:
|
||||
msg = _("""\
|
||||
@ -342,7 +342,7 @@ Invalid file. Please restore from backup.""")
|
||||
Unable to import from a read-only file."""))
|
||||
else:
|
||||
msg = _("Import failed.\n")
|
||||
msg += unicode(traceback.format_exc(), "ascii", "replace")
|
||||
msg += str(traceback.format_exc())
|
||||
showText(msg)
|
||||
else:
|
||||
log = "\n".join(importer.log)
|
||||
|
13
aqt/main.py
13
aqt/main.py
@ -56,7 +56,7 @@ class AnkiQt(QMainWindow):
|
||||
"syncing and add-on loading."))
|
||||
# were we given a file to import?
|
||||
if args and args[0]:
|
||||
self.onAppMsg(unicode(args[0], sys.getfilesystemencoding(), "ignore"))
|
||||
self.onAppMsg(args[0])
|
||||
# Load profile in a timer so we can let the window finish init and not
|
||||
# close on profile load error.
|
||||
if isMac and qtmajor >= 5:
|
||||
@ -272,7 +272,7 @@ see the manual for how to restore from an automatic backup.
|
||||
Debug info:
|
||||
""")+traceback.format_exc())
|
||||
self.unloadProfile()
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
# the custom exception handler won't catch this if we immediately
|
||||
# unload, so we have to manually handle it
|
||||
if "invalidTempFolder" in repr(str(e)):
|
||||
@ -627,7 +627,7 @@ title="%s">%s</button>''' % (
|
||||
# run standard handler
|
||||
QMainWindow.keyPressEvent(self, evt)
|
||||
# check global keys
|
||||
key = unicode(evt.text())
|
||||
key = str(evt.text())
|
||||
if key == "d":
|
||||
self.moveToState("deckBrowser")
|
||||
elif key == "s":
|
||||
@ -1060,7 +1060,7 @@ will be lost. Continue?"""))
|
||||
pp = pprint.pprint
|
||||
self._captureOutput(True)
|
||||
try:
|
||||
exec text
|
||||
exec(text)
|
||||
except:
|
||||
self._output += traceback.format_exc()
|
||||
self._captureOutput(False)
|
||||
@ -1109,7 +1109,7 @@ will be lost. Continue?"""))
|
||||
return
|
||||
tgt = tgt or self
|
||||
for action in tgt.findChildren(QAction):
|
||||
txt = unicode(action.text())
|
||||
txt = str(action.text())
|
||||
m = re.match("^(.+)\(&.+\)(.+)?", txt)
|
||||
if m:
|
||||
action.setText(m.group(1) + (m.group(2) or ""))
|
||||
@ -1157,7 +1157,4 @@ Please ensure a profile is open and Anki is not busy, then try again."""),
|
||||
if buf == "raise":
|
||||
return
|
||||
# import
|
||||
if not isinstance(buf, unicode):
|
||||
buf = unicode(buf, "utf8", "ignore")
|
||||
|
||||
self.handleImport(buf)
|
||||
|
@ -7,6 +7,7 @@ from aqt.utils import showInfo, askUser, getText, maybeHideClose, openHelp
|
||||
import aqt.modelchooser, aqt.clayout
|
||||
from anki import stdmodels
|
||||
from aqt.utils import saveGeom, restoreGeom
|
||||
import collections
|
||||
|
||||
class Models(QDialog):
|
||||
def __init__(self, mw, parent=None, fromMain=False):
|
||||
@ -118,8 +119,8 @@ class Models(QDialog):
|
||||
restoreGeom(d, "modelopts")
|
||||
d.exec_()
|
||||
saveGeom(d, "modelopts")
|
||||
self.model['latexPre'] = unicode(frm.latexHeader.toPlainText())
|
||||
self.model['latexPost'] = unicode(frm.latexFooter.toPlainText())
|
||||
self.model['latexPre'] = str(frm.latexHeader.toPlainText())
|
||||
self.model['latexPost'] = str(frm.latexFooter.toPlainText())
|
||||
|
||||
def saveModel(self):
|
||||
self.mm.save(self.model)
|
||||
@ -127,7 +128,7 @@ class Models(QDialog):
|
||||
def _tmpNote(self):
|
||||
self.mm.setCurrent(self.model)
|
||||
n = self.col.newNote(forDeck=False)
|
||||
for name in n.keys():
|
||||
for name in list(n.keys()):
|
||||
n[name] = "("+name+")"
|
||||
try:
|
||||
if "{{cloze:Text}}" in self.model['tmpls'][0]['qfmt']:
|
||||
@ -171,7 +172,7 @@ class AddModel(QDialog):
|
||||
# standard models
|
||||
self.models = []
|
||||
for (name, func) in stdmodels.models:
|
||||
if callable(name):
|
||||
if isinstance(name, collections.Callable):
|
||||
name = name()
|
||||
item = QListWidgetItem(_("Add: %s") % name)
|
||||
self.dialog.models.addItem(item)
|
||||
|
@ -38,7 +38,7 @@ class Overview(object):
|
||||
if self.mw.state == "overview":
|
||||
tooltip(_("No cards are due yet."))
|
||||
elif url == "anki":
|
||||
print "anki menu"
|
||||
print("anki menu")
|
||||
elif url == "opts":
|
||||
self.mw.onDeckConf()
|
||||
elif url == "cram":
|
||||
@ -64,7 +64,7 @@ class Overview(object):
|
||||
|
||||
def _keyHandler(self, evt):
|
||||
cram = self.mw.col.decks.current()['dyn']
|
||||
key = unicode(evt.text())
|
||||
key = str(evt.text())
|
||||
if key == "o":
|
||||
self.mw.onDeckConf()
|
||||
if key == "r" and cram:
|
||||
|
@ -80,7 +80,7 @@ class Preferences(QDialog):
|
||||
f.timeLimit.setValue(qc['timeLim']/60.0)
|
||||
f.showEstimates.setChecked(qc['estTimes'])
|
||||
f.showProgress.setChecked(qc['dueCounts'])
|
||||
f.newSpread.addItems(c.newCardSchedulingLabels().values())
|
||||
f.newSpread.addItems(list(c.newCardSchedulingLabels().values()))
|
||||
f.newSpread.setCurrentIndex(qc['newSpread'])
|
||||
f.useCurrent.setCurrentIndex(int(not qc.get("addToCur", True)))
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import os
|
||||
import random
|
||||
import cPickle
|
||||
import pickle
|
||||
import locale
|
||||
import re
|
||||
|
||||
@ -21,7 +21,6 @@ from aqt import appHelpSite
|
||||
import aqt.forms
|
||||
from send2trash import send2trash
|
||||
|
||||
|
||||
metaConf = dict(
|
||||
ver=0,
|
||||
updates=True,
|
||||
@ -103,12 +102,13 @@ a flash drive.""" % self.base)
|
||||
|
||||
def profiles(self):
|
||||
return sorted(x for x in
|
||||
self.db.list("select name from profiles")
|
||||
if x != "_global")
|
||||
self.db.list("select name from profiles")
|
||||
if x != "_global")
|
||||
|
||||
def load(self, name, passwd=None):
|
||||
data = self.db.scalar("select cast(data as blob) from profiles where name = ?", name)
|
||||
prof = cPickle.loads(str(data))
|
||||
# some profiles created in python2 may not decode properly
|
||||
prof = pickle.loads(data, errors="ignore")
|
||||
if prof['key'] and prof['key'] != self._pwhash(passwd):
|
||||
self.name = None
|
||||
return False
|
||||
@ -119,14 +119,14 @@ a flash drive.""" % self.base)
|
||||
|
||||
def save(self):
|
||||
sql = "update profiles set data = ? where name = ?"
|
||||
self.db.execute(sql, buffer(cPickle.dumps(self.profile)), self.name)
|
||||
self.db.execute(sql, buffer(cPickle.dumps(self.meta)), "_global")
|
||||
self.db.execute(sql, pickle.dumps(self.profile), self.name)
|
||||
self.db.execute(sql, pickle.dumps(self.meta), "_global")
|
||||
self.db.commit()
|
||||
|
||||
def create(self, name):
|
||||
prof = profileConf.copy()
|
||||
self.db.execute("insert into profiles values (?, ?)",
|
||||
name, buffer(cPickle.dumps(prof)))
|
||||
name, pickle.dumps(prof))
|
||||
self.db.commit()
|
||||
|
||||
def remove(self, name):
|
||||
@ -262,9 +262,9 @@ create table if not exists profiles
|
||||
if not new:
|
||||
# load previously created
|
||||
try:
|
||||
data = self.db.scalar(
|
||||
"select cast(data as blob) from profiles where name = '_global'")
|
||||
self.meta = cPickle.loads(str(data))
|
||||
self.meta = pickle.loads(
|
||||
self.db.scalar(
|
||||
"select cast(data as blob) from profiles where name = '_global'"))
|
||||
return
|
||||
except:
|
||||
recover()
|
||||
@ -272,7 +272,7 @@ create table if not exists profiles
|
||||
# create a default global profile
|
||||
self.meta = metaConf.copy()
|
||||
self.db.execute("insert or replace into profiles values ('_global', ?)",
|
||||
buffer(cPickle.dumps(metaConf)))
|
||||
pickle.dumps(metaConf))
|
||||
self._setDefaultLang()
|
||||
return True
|
||||
|
||||
@ -281,16 +281,16 @@ create table if not exists profiles
|
||||
if self.firstRun:
|
||||
self.create(_("User 1"))
|
||||
p = os.path.join(self.base, "README.txt")
|
||||
open(p, "w").write((_("""\
|
||||
open(p, "w").write(_("""\
|
||||
This folder stores all of your Anki data in a single location,
|
||||
to make backups easy. To tell Anki to use a different location,
|
||||
please see:
|
||||
|
||||
%s
|
||||
""") % (appHelpSite + "#startupopts")).encode("utf8"))
|
||||
""") % (appHelpSite + "#startupopts"))
|
||||
|
||||
def _pwhash(self, passwd):
|
||||
return checksum(unicode(self.meta['id'])+unicode(passwd))
|
||||
return checksum(str(self.meta['id'])+str(passwd))
|
||||
|
||||
# Default language
|
||||
######################################################################
|
||||
@ -299,8 +299,8 @@ please see:
|
||||
def _setDefaultLang(self):
|
||||
# the dialog expects _ to be defined, but we're running before
|
||||
# setupLang() has been called. so we create a dummy op for now
|
||||
import __builtin__
|
||||
__builtin__.__dict__['_'] = lambda x: x
|
||||
import builtins
|
||||
builtins.__dict__['_'] = lambda x: x
|
||||
# create dialog
|
||||
class NoCloseDiag(QDialog):
|
||||
def reject(self):
|
||||
@ -350,6 +350,6 @@ please see:
|
||||
def setLang(self, code):
|
||||
self.meta['defaultLang'] = code
|
||||
sql = "update profiles set data = ? where name = ?"
|
||||
self.db.execute(sql, buffer(cPickle.dumps(self.meta)), "_global")
|
||||
self.db.execute(sql, pickle.dumps(self.meta), "_global")
|
||||
self.db.commit()
|
||||
anki.lang.setLang(code, local=False)
|
||||
|
@ -32,8 +32,8 @@ class ProgressManager(object):
|
||||
try:
|
||||
db.set_progress_handler(self._dbProgress, 10000)
|
||||
except:
|
||||
print """\
|
||||
Your pysqlite2 is too old. Anki will appear frozen during long operations."""
|
||||
print("""\
|
||||
Your pysqlite2 is too old. Anki will appear frozen during long operations.""")
|
||||
|
||||
def _dbProgress(self):
|
||||
"Called from SQLite."
|
||||
|
@ -2,12 +2,11 @@
|
||||
# Copyright: Damien Elmes <anki@ichi2.net>
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
from __future__ import division
|
||||
import difflib
|
||||
import re
|
||||
import cgi
|
||||
import unicodedata as ucd
|
||||
import HTMLParser
|
||||
import html.parser
|
||||
|
||||
from anki.lang import _, ngettext
|
||||
from aqt.qt import *
|
||||
@ -285,7 +284,7 @@ The front of this card is empty. Please run Tools>Empty Cards.""")
|
||||
self.bottom.web.eval("py.link('ans');")
|
||||
|
||||
def _keyHandler(self, evt):
|
||||
key = unicode(evt.text())
|
||||
key = str(evt.text())
|
||||
if key == "e":
|
||||
self.mw.onEditCurrent()
|
||||
elif (key == " " or evt.key() in (Qt.Key_Return, Qt.Key_Enter)):
|
||||
@ -409,12 +408,12 @@ Please run Tools>Empty Cards""")
|
||||
buf = buf.replace("<hr id=answer>", "")
|
||||
hadHR = len(buf) != origSize
|
||||
# munge correct value
|
||||
parser = HTMLParser.HTMLParser()
|
||||
parser = html.parser.HTMLParser()
|
||||
cor = stripHTML(self.mw.col.media.strip(self.typeCorrect))
|
||||
# ensure we don't chomp multiple whitespace
|
||||
cor = cor.replace(" ", " ")
|
||||
cor = parser.unescape(cor)
|
||||
cor = cor.replace(u"\xa0", " ")
|
||||
cor = cor.replace("\xa0", " ")
|
||||
given = self.typedAnswer
|
||||
# compare with typed answer
|
||||
res = self.correct(given, cor, showBad=False)
|
||||
|
29
aqt/sync.py
29
aqt/sync.py
@ -1,7 +1,6 @@
|
||||
# Copyright: Damien Elmes <anki@ichi2.net>
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
from __future__ import division
|
||||
import socket
|
||||
import time
|
||||
import traceback
|
||||
@ -324,8 +323,6 @@ class SyncThread(QThread):
|
||||
self._sync()
|
||||
except:
|
||||
err = traceback.format_exc()
|
||||
if not isinstance(err, unicode):
|
||||
err = unicode(err, "utf8", "replace")
|
||||
self.fireEvent("error", err)
|
||||
finally:
|
||||
# don't bump mod time unless we explicitly save
|
||||
@ -348,7 +345,7 @@ class SyncThread(QThread):
|
||||
# run sync and check state
|
||||
try:
|
||||
ret = self.client.sync()
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
log = traceback.format_exc()
|
||||
err = repr(str(e))
|
||||
if ("Unable to find the server" in err or
|
||||
@ -357,8 +354,6 @@ class SyncThread(QThread):
|
||||
else:
|
||||
if not err:
|
||||
err = log
|
||||
if not isinstance(err, unicode):
|
||||
err = unicode(err, "utf8", "replace")
|
||||
self.fireEvent("error", err)
|
||||
return
|
||||
if ret == "badAuth":
|
||||
@ -429,22 +424,21 @@ class SyncThread(QThread):
|
||||
######################################################################
|
||||
|
||||
CHUNK_SIZE = 65536
|
||||
import httplib, httplib2
|
||||
from cStringIO import StringIO
|
||||
import http.client, httplib2
|
||||
from io import StringIO
|
||||
from anki.hooks import runHook
|
||||
|
||||
# sending in httplib
|
||||
def _incrementalSend(self, data):
|
||||
print("fixme: _incrementalSend needs updating for python3")
|
||||
"""Send `data' to the server."""
|
||||
if self.sock is None:
|
||||
if self.auto_open:
|
||||
self.connect()
|
||||
else:
|
||||
raise httplib.NotConnected()
|
||||
raise http.client.NotConnected()
|
||||
# if it's not a file object, make it one
|
||||
if not hasattr(data, 'read'):
|
||||
if isinstance(data, unicode):
|
||||
data = data.encode("utf8")
|
||||
data = StringIO(data)
|
||||
while 1:
|
||||
block = data.read(CHUNK_SIZE)
|
||||
@ -453,7 +447,7 @@ def _incrementalSend(self, data):
|
||||
self.sock.sendall(block)
|
||||
runHook("httpSend", len(block))
|
||||
|
||||
httplib.HTTPConnection.send = _incrementalSend
|
||||
http.client.HTTPConnection.send = _incrementalSend
|
||||
|
||||
# receiving in httplib2
|
||||
# this is an augmented version of httplib's request routine that:
|
||||
@ -461,6 +455,7 @@ httplib.HTTPConnection.send = _incrementalSend
|
||||
# - calls a hook for each chunk of data so we can update the gui
|
||||
# - retries only when keep-alive connection is closed
|
||||
def _conn_request(self, conn, request_uri, method, body, headers):
|
||||
print("fixme: _conn_request updating for python3")
|
||||
for i in range(2):
|
||||
try:
|
||||
if conn.sock is None:
|
||||
@ -475,20 +470,20 @@ def _conn_request(self, conn, request_uri, method, body, headers):
|
||||
except httplib2.ssl_SSLError:
|
||||
conn.close()
|
||||
raise
|
||||
except socket.error, e:
|
||||
except socket.error as e:
|
||||
conn.close()
|
||||
raise
|
||||
except httplib.HTTPException:
|
||||
except http.client.HTTPException:
|
||||
conn.close()
|
||||
raise
|
||||
try:
|
||||
response = conn.getresponse()
|
||||
except httplib.BadStatusLine:
|
||||
print "retry bad line"
|
||||
except http.client.BadStatusLine:
|
||||
print("retry bad line")
|
||||
conn.close()
|
||||
conn.connect()
|
||||
continue
|
||||
except (socket.error, httplib.HTTPException):
|
||||
except (socket.error, http.client.HTTPException):
|
||||
raise
|
||||
else:
|
||||
content = ""
|
||||
|
@ -68,10 +68,10 @@ class TagCompleter(QCompleter):
|
||||
self.cursor = None
|
||||
|
||||
def splitPath(self, str):
|
||||
str = unicode(str).strip()
|
||||
str = str(str).strip()
|
||||
str = re.sub(" +", " ", str)
|
||||
self.tags = self.edit.col.tags.split(str)
|
||||
self.tags.append(u"")
|
||||
self.tags.append("")
|
||||
p = self.edit.cursorPosition()
|
||||
self.cursor = str.count(" ", 0, p)
|
||||
return [self.tags[self.cursor]]
|
||||
@ -80,9 +80,9 @@ class TagCompleter(QCompleter):
|
||||
if self.cursor is None:
|
||||
return self.edit.text()
|
||||
ret = QCompleter.pathFromIndex(self, idx)
|
||||
self.tags[self.cursor] = unicode(ret)
|
||||
self.tags[self.cursor] = str(ret)
|
||||
try:
|
||||
self.tags.remove(u"")
|
||||
self.tags.remove("")
|
||||
except ValueError:
|
||||
pass
|
||||
return " ".join(self.tags)
|
||||
|
@ -1,8 +1,8 @@
|
||||
# Copyright: Damien Elmes <anki@ichi2.net>
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
import urllib
|
||||
import urllib2
|
||||
import urllib.request, urllib.parse, urllib.error
|
||||
import urllib.request, urllib.error, urllib.parse
|
||||
import time
|
||||
|
||||
from aqt.qt import *
|
||||
@ -32,9 +32,9 @@ class LatestVersionFinder(QThread):
|
||||
return
|
||||
d = self._data()
|
||||
d['proto'] = 1
|
||||
d = urllib.urlencode(d)
|
||||
d = urllib.parse.urlencode(d)
|
||||
try:
|
||||
f = urllib2.urlopen(aqt.appUpdate, d)
|
||||
f = urllib.request.urlopen(aqt.appUpdate, d)
|
||||
resp = f.read()
|
||||
if not resp:
|
||||
return
|
||||
|
30
aqt/utils.py
30
aqt/utils.py
@ -3,7 +3,7 @@
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
from aqt.qt import *
|
||||
import re, os, sys, urllib, subprocess
|
||||
import re, os, sys, urllib.request, urllib.parse, urllib.error, subprocess
|
||||
import aqt
|
||||
from anki.sound import stripSounds
|
||||
from anki.utils import isWin, isMac, invalidFilename
|
||||
@ -143,7 +143,7 @@ def askUserDialog(text, buttons, parent=None, help="", title="Anki"):
|
||||
|
||||
class GetTextDialog(QDialog):
|
||||
|
||||
def __init__(self, parent, question, help=None, edit=None, default=u"", \
|
||||
def __init__(self, parent, question, help=None, edit=None, default="", \
|
||||
title="Anki", minWidth=400):
|
||||
QDialog.__init__(self, parent)
|
||||
self.setWindowTitle(title)
|
||||
@ -183,21 +183,21 @@ class GetTextDialog(QDialog):
|
||||
def helpRequested(self):
|
||||
openHelp(self.help)
|
||||
|
||||
def getText(prompt, parent=None, help=None, edit=None, default=u"", title="Anki"):
|
||||
def getText(prompt, parent=None, help=None, edit=None, default="", title="Anki"):
|
||||
if not parent:
|
||||
parent = aqt.mw.app.activeWindow() or aqt.mw
|
||||
d = GetTextDialog(parent, prompt, help=help, edit=edit,
|
||||
default=default, title=title)
|
||||
d.setWindowModality(Qt.WindowModal)
|
||||
ret = d.exec_()
|
||||
return (unicode(d.l.text()), ret)
|
||||
return (str(d.l.text()), ret)
|
||||
|
||||
def getOnlyText(*args, **kwargs):
|
||||
(s, r) = getText(*args, **kwargs)
|
||||
if r:
|
||||
return s
|
||||
else:
|
||||
return u""
|
||||
return ""
|
||||
|
||||
# fixme: these utilities could be combined into a single base class
|
||||
def chooseList(prompt, choices, startrow=0, parent=None):
|
||||
@ -251,7 +251,7 @@ def getFile(parent, title, cb, filter="*.*", dir=None, key=None):
|
||||
def accept():
|
||||
# work around an osx crash
|
||||
#aqt.mw.app.processEvents()
|
||||
file = unicode(list(d.selectedFiles())[0])
|
||||
file = str(list(d.selectedFiles())[0])
|
||||
if dirkey:
|
||||
dir = os.path.dirname(file)
|
||||
aqt.mw.pm.profile[dirkey] = dir
|
||||
@ -268,8 +268,8 @@ def getSaveFile(parent, title, dir_description, key, ext, fname=None):
|
||||
config_key = dir_description + 'Directory'
|
||||
base = aqt.mw.pm.profile.get(config_key, aqt.mw.pm.base)
|
||||
path = os.path.join(base, fname)
|
||||
file = unicode(QFileDialog.getSaveFileName(
|
||||
parent, title, path, u"{0} (*{1})".format(key, ext),
|
||||
file = str(QFileDialog.getSaveFileName(
|
||||
parent, title, path, "{0} (*{1})".format(key, ext),
|
||||
options=QFileDialog.DontConfirmOverwrite))
|
||||
if file:
|
||||
# add extension
|
||||
@ -350,18 +350,16 @@ def getBase(col):
|
||||
base = None
|
||||
mdir = col.media.dir()
|
||||
if isWin and not mdir.startswith("\\\\"):
|
||||
prefix = u"file:///"
|
||||
prefix = "file:///"
|
||||
else:
|
||||
prefix = u"file://"
|
||||
prefix = "file://"
|
||||
mdir = mdir.replace("\\", "/")
|
||||
base = prefix + unicode(
|
||||
urllib.quote(mdir.encode("utf-8")),
|
||||
"utf-8") + "/"
|
||||
base = prefix + urllib.parse.quote(mdir) + "/"
|
||||
return '<base href="%s">' % base
|
||||
|
||||
def openFolder(path):
|
||||
if isWin:
|
||||
if isinstance(path, unicode):
|
||||
if isinstance(path, str):
|
||||
path = path.encode(sys.getfilesystemencoding())
|
||||
subprocess.Popen(["explorer", path])
|
||||
else:
|
||||
@ -387,9 +385,9 @@ def addCloseShortcut(widg):
|
||||
|
||||
def downArrow():
|
||||
if isWin:
|
||||
return u"▼"
|
||||
return "▼"
|
||||
# windows 10 is lacking the smaller arrow on English installs
|
||||
return u"▾"
|
||||
return "▾"
|
||||
|
||||
# Tooltips
|
||||
######################################################################
|
||||
|
@ -15,10 +15,10 @@ import anki.js
|
||||
class Bridge(QObject):
|
||||
@pyqtSlot(str, result=str)
|
||||
def run(self, str):
|
||||
return unicode(self._bridge(unicode(str)))
|
||||
return self._bridge(str)
|
||||
@pyqtSlot(str)
|
||||
def link(self, str):
|
||||
self._linkHandler(unicode(str))
|
||||
self._linkHandler(str)
|
||||
def setBridge(self, func):
|
||||
self._bridge = func
|
||||
def setLinkHandler(self, func):
|
||||
@ -146,7 +146,7 @@ button {
|
||||
def _jsErr(self, msg, line, srcID):
|
||||
sys.stdout.write(
|
||||
(_("JS error on line %(a)d: %(b)s") %
|
||||
dict(a=line, b=msg+"\n")).encode("utf8"))
|
||||
dict(a=line, b=msg+"\n")))
|
||||
|
||||
def _linkHandler(self, url):
|
||||
self.linkHandler(url.toString())
|
||||
|
@ -37,6 +37,6 @@ def getUpgradeDeckPath(name="anki12.anki"):
|
||||
src = os.path.join(testDir, "support", name)
|
||||
(fd, dst) = tempfile.mkstemp(suffix=".anki2")
|
||||
shutil.copy(src, dst)
|
||||
return unicode(dst, "utf8")
|
||||
return dst
|
||||
|
||||
testDir = os.path.dirname(__file__)
|
||||
|
@ -5,8 +5,8 @@ from tests.shared import getEmptyCol
|
||||
def test_previewCards():
|
||||
deck = getEmptyCol()
|
||||
f = deck.newNote()
|
||||
f['Front'] = u'1'
|
||||
f['Back'] = u'2'
|
||||
f['Front'] = '1'
|
||||
f['Back'] = '2'
|
||||
# non-empty and active
|
||||
cards = deck.previewCards(f, 0)
|
||||
assert len(cards) == 1
|
||||
@ -25,8 +25,8 @@ def test_previewCards():
|
||||
def test_delete():
|
||||
deck = getEmptyCol()
|
||||
f = deck.newNote()
|
||||
f['Front'] = u'1'
|
||||
f['Back'] = u'2'
|
||||
f['Front'] = '1'
|
||||
f['Back'] = '2'
|
||||
deck.addNote(f)
|
||||
cid = f.cards()[0].id
|
||||
deck.reset()
|
||||
@ -41,8 +41,8 @@ def test_delete():
|
||||
def test_misc():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = u'1'
|
||||
f['Back'] = u'2'
|
||||
f['Front'] = '1'
|
||||
f['Back'] = '2'
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
id = d.models.current()['id']
|
||||
@ -51,8 +51,8 @@ def test_misc():
|
||||
def test_genrem():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = u'1'
|
||||
f['Back'] = u''
|
||||
f['Front'] = '1'
|
||||
f['Back'] = ''
|
||||
d.addNote(f)
|
||||
assert len(f.cards()) == 1
|
||||
m = d.models.current()
|
||||
@ -80,7 +80,7 @@ def test_gendeck():
|
||||
cloze = d.models.byName("Cloze")
|
||||
d.models.setCurrent(cloze)
|
||||
f = d.newNote()
|
||||
f['Text'] = u'{{c1::one}}'
|
||||
f['Text'] = '{{c1::one}}'
|
||||
d.addNote(f)
|
||||
assert d.cardCount() == 1
|
||||
assert f.cards()[0].did == 1
|
||||
@ -89,11 +89,11 @@ def test_gendeck():
|
||||
cloze['did'] = newId
|
||||
d.models.save(cloze)
|
||||
# a newly generated card should share the first card's deck
|
||||
f['Text'] += u'{{c2::two}}'
|
||||
f['Text'] += '{{c2::two}}'
|
||||
f.flush()
|
||||
assert f.cards()[1].did == 1
|
||||
# and same with multiple cards
|
||||
f['Text'] += u'{{c3::three}}'
|
||||
f['Text'] += '{{c3::three}}'
|
||||
f.flush()
|
||||
assert f.cards()[2].did == 1
|
||||
# if one of the cards is in a different deck, it should revert to the
|
||||
@ -101,7 +101,7 @@ def test_gendeck():
|
||||
c = f.cards()[1]
|
||||
c.did = newId
|
||||
c.flush()
|
||||
f['Text'] += u'{{c4::four}}'
|
||||
f['Text'] += '{{c4::four}}'
|
||||
f.flush()
|
||||
assert f.cards()[3].did == newId
|
||||
|
||||
|
@ -37,14 +37,14 @@ def test_openReadOnly():
|
||||
os.chmod(newPath, 0)
|
||||
assertException(Exception,
|
||||
lambda: aopen(newPath))
|
||||
os.chmod(newPath, 0666)
|
||||
os.chmod(newPath, 0o666)
|
||||
os.unlink(newPath)
|
||||
|
||||
def test_noteAddDelete():
|
||||
deck = getEmptyCol()
|
||||
# add a note
|
||||
f = deck.newNote()
|
||||
f['Front'] = u"one"; f['Back'] = u"two"
|
||||
f['Front'] = "one"; f['Back'] = "two"
|
||||
n = deck.addNote(f)
|
||||
assert n == 1
|
||||
# test multiple cards - add another template
|
||||
@ -62,7 +62,7 @@ def test_noteAddDelete():
|
||||
assert deck.cardCount() == 2
|
||||
# creating new notes should use both cards
|
||||
f = deck.newNote()
|
||||
f['Front'] = u"three"; f['Back'] = u"four"
|
||||
f['Front'] = "three"; f['Back'] = "four"
|
||||
n = deck.addNote(f)
|
||||
assert n == 2
|
||||
assert deck.cardCount() == 4
|
||||
@ -73,7 +73,7 @@ def test_noteAddDelete():
|
||||
assert not f.dupeOrEmpty()
|
||||
# now let's make a duplicate
|
||||
f2 = deck.newNote()
|
||||
f2['Front'] = u"one"; f2['Back'] = u""
|
||||
f2['Front'] = "one"; f2['Back'] = ""
|
||||
assert f2.dupeOrEmpty()
|
||||
# empty first field should not be permitted either
|
||||
f2['Front'] = " "
|
||||
@ -82,12 +82,12 @@ def test_noteAddDelete():
|
||||
def test_fieldChecksum():
|
||||
deck = getEmptyCol()
|
||||
f = deck.newNote()
|
||||
f['Front'] = u"new"; f['Back'] = u"new2"
|
||||
f['Front'] = "new"; f['Back'] = "new2"
|
||||
deck.addNote(f)
|
||||
assert deck.db.scalar(
|
||||
"select csum from notes") == int("c2a6b03f", 16)
|
||||
# changing the val should change the checksum
|
||||
f['Front'] = u"newx"
|
||||
f['Front'] = "newx"
|
||||
f.flush()
|
||||
assert deck.db.scalar(
|
||||
"select csum from notes") == int("302811ae", 16)
|
||||
@ -95,10 +95,10 @@ def test_fieldChecksum():
|
||||
def test_addDelTags():
|
||||
deck = getEmptyCol()
|
||||
f = deck.newNote()
|
||||
f['Front'] = u"1"
|
||||
f['Front'] = "1"
|
||||
deck.addNote(f)
|
||||
f2 = deck.newNote()
|
||||
f2['Front'] = u"2"
|
||||
f2['Front'] = "2"
|
||||
deck.addNote(f2)
|
||||
# adding for a given id
|
||||
deck.tags.bulkAdd([f.id], "foo")
|
||||
|
@ -46,7 +46,7 @@ def test_remove():
|
||||
# create a new deck, and add a note/card to it
|
||||
g1 = deck.decks.id("g1")
|
||||
f = deck.newNote()
|
||||
f['Front'] = u"1"
|
||||
f['Front'] = "1"
|
||||
f.model()['did'] = g1
|
||||
deck.addNote(f)
|
||||
c = f.cards()[0]
|
||||
@ -92,7 +92,7 @@ def test_renameForDragAndDrop():
|
||||
d = getEmptyCol()
|
||||
|
||||
def deckNames():
|
||||
return [ name for name in sorted(d.decks.allNames()) if name <> u'Default' ]
|
||||
return [ name for name in sorted(d.decks.allNames()) if name != 'Default' ]
|
||||
|
||||
languages_did = d.decks.id('Languages')
|
||||
chinese_did = d.decks.id('Chinese')
|
||||
|
@ -4,7 +4,7 @@ import nose, os, tempfile
|
||||
from anki import Collection as aopen
|
||||
from anki.exporting import *
|
||||
from anki.importing import Anki2Importer
|
||||
from shared import getEmptyCol
|
||||
from .shared import getEmptyCol
|
||||
|
||||
deck = None
|
||||
ds = None
|
||||
@ -14,11 +14,11 @@ def setup1():
|
||||
global deck
|
||||
deck = getEmptyCol()
|
||||
f = deck.newNote()
|
||||
f['Front'] = u"foo"; f['Back'] = u"bar"; f.tags = ["tag", "tag2"]
|
||||
f['Front'] = "foo"; f['Back'] = "bar"; f.tags = ["tag", "tag2"]
|
||||
deck.addNote(f)
|
||||
# with a different deck
|
||||
f = deck.newNote()
|
||||
f['Front'] = u"baz"; f['Back'] = u"qux"
|
||||
f['Front'] = "baz"; f['Back'] = "qux"
|
||||
f.model()['did'] = deck.decks.id("new deck")
|
||||
deck.addNote(f)
|
||||
|
||||
@ -37,7 +37,7 @@ def test_export_anki():
|
||||
# export
|
||||
e = AnkiExporter(deck)
|
||||
fd, newname = tempfile.mkstemp(prefix="ankitest", suffix=".anki2")
|
||||
newname = unicode(newname)
|
||||
newname = str(newname)
|
||||
os.close(fd)
|
||||
os.unlink(newname)
|
||||
e.exportInto(newname)
|
||||
@ -57,7 +57,7 @@ def test_export_anki():
|
||||
assert dobj['conf'] == 1
|
||||
# try again, limited to a deck
|
||||
fd, newname = tempfile.mkstemp(prefix="ankitest", suffix=".anki2")
|
||||
newname = unicode(newname)
|
||||
newname = str(newname)
|
||||
os.close(fd)
|
||||
os.unlink(newname)
|
||||
e.did = 1
|
||||
@ -68,13 +68,13 @@ def test_export_anki():
|
||||
@nose.with_setup(setup1)
|
||||
def test_export_ankipkg():
|
||||
# add a test file to the media folder
|
||||
open(os.path.join(deck.media.dir(), u"今日.mp3"), "w").write("test")
|
||||
open(os.path.join(deck.media.dir(), "今日.mp3"), "w").write("test")
|
||||
n = deck.newNote()
|
||||
n['Front'] = u'[sound:今日.mp3]'
|
||||
n['Front'] = '[sound:今日.mp3]'
|
||||
deck.addNote(n)
|
||||
e = AnkiPackageExporter(deck)
|
||||
fd, newname = tempfile.mkstemp(prefix="ankitest", suffix=".apkg")
|
||||
newname = unicode(newname)
|
||||
newname = str(newname)
|
||||
os.close(fd)
|
||||
os.unlink(newname)
|
||||
e.exportInto(newname)
|
||||
@ -83,7 +83,7 @@ def test_export_ankipkg():
|
||||
def test_export_anki_due():
|
||||
deck = getEmptyCol()
|
||||
f = deck.newNote()
|
||||
f['Front'] = u"foo"
|
||||
f['Front'] = "foo"
|
||||
deck.addNote(f)
|
||||
deck.crt -= 86400*10
|
||||
deck.sched.reset()
|
||||
@ -99,7 +99,7 @@ def test_export_anki_due():
|
||||
e = AnkiExporter(deck)
|
||||
e.includeSched = True
|
||||
fd, newname = tempfile.mkstemp(prefix="ankitest", suffix=".anki2")
|
||||
newname = unicode(newname)
|
||||
newname = str(newname)
|
||||
os.close(fd)
|
||||
os.unlink(newname)
|
||||
e.exportInto(newname)
|
||||
@ -124,7 +124,7 @@ def test_export_anki_due():
|
||||
def test_export_textnote():
|
||||
e = TextNoteExporter(deck)
|
||||
fd, f = tempfile.mkstemp(prefix="ankitest")
|
||||
f = unicode(f)
|
||||
f = str(f)
|
||||
os.close(fd)
|
||||
os.unlink(f)
|
||||
e.exportInto(f)
|
||||
|
@ -22,21 +22,21 @@ def test_parse():
|
||||
def test_findCards():
|
||||
deck = getEmptyCol()
|
||||
f = deck.newNote()
|
||||
f['Front'] = u'dog'
|
||||
f['Back'] = u'cat'
|
||||
f.tags.append(u"monkey")
|
||||
f['Front'] = 'dog'
|
||||
f['Back'] = 'cat'
|
||||
f.tags.append("monkey")
|
||||
f1id = f.id
|
||||
deck.addNote(f)
|
||||
firstCardId = f.cards()[0].id
|
||||
f = deck.newNote()
|
||||
f['Front'] = u'goats are fun'
|
||||
f['Back'] = u'sheep'
|
||||
f.tags.append(u"sheep goat horse")
|
||||
f['Front'] = 'goats are fun'
|
||||
f['Back'] = 'sheep'
|
||||
f.tags.append("sheep goat horse")
|
||||
deck.addNote(f)
|
||||
f2id = f.id
|
||||
f = deck.newNote()
|
||||
f['Front'] = u'cat'
|
||||
f['Back'] = u'sheep'
|
||||
f['Front'] = 'cat'
|
||||
f['Back'] = 'sheep'
|
||||
deck.addNote(f)
|
||||
catCard = f.cards()[0]
|
||||
m = deck.models.current(); mm = deck.models
|
||||
@ -46,8 +46,8 @@ def test_findCards():
|
||||
mm.addTemplate(m, t)
|
||||
mm.save(m)
|
||||
f = deck.newNote()
|
||||
f['Front'] = u'test'
|
||||
f['Back'] = u'foo bar'
|
||||
f['Front'] = 'test'
|
||||
f['Back'] = 'foo bar'
|
||||
deck.addNote(f)
|
||||
latestCardIds = [c.id for c in f.cards()]
|
||||
# tag searches
|
||||
@ -131,8 +131,8 @@ def test_findCards():
|
||||
assert len(deck.findCards("deck:*cefault")) == 0
|
||||
# full search
|
||||
f = deck.newNote()
|
||||
f['Front'] = u'hello<b>world</b>'
|
||||
f['Back'] = u'abc'
|
||||
f['Front'] = 'hello<b>world</b>'
|
||||
f['Back'] = 'abc'
|
||||
deck.addNote(f)
|
||||
# as it's the sort field, it matches
|
||||
assert len(deck.findCards("helloworld")) == 2
|
||||
@ -195,8 +195,8 @@ def test_findCards():
|
||||
# empty field
|
||||
assert len(deck.findCards("front:")) == 0
|
||||
f = deck.newNote()
|
||||
f['Front'] = u''
|
||||
f['Back'] = u'abc2'
|
||||
f['Front'] = ''
|
||||
f['Back'] = 'abc2'
|
||||
assert deck.addNote(f) == 1
|
||||
assert len(deck.findCards("front:")) == 1
|
||||
# OR searches and nesting
|
||||
@ -218,12 +218,12 @@ def test_findCards():
|
||||
def test_findReplace():
|
||||
deck = getEmptyCol()
|
||||
f = deck.newNote()
|
||||
f['Front'] = u'foo'
|
||||
f['Back'] = u'bar'
|
||||
f['Front'] = 'foo'
|
||||
f['Back'] = 'bar'
|
||||
deck.addNote(f)
|
||||
f2 = deck.newNote()
|
||||
f2['Front'] = u'baz'
|
||||
f2['Back'] = u'foo'
|
||||
f2['Front'] = 'baz'
|
||||
f2['Back'] = 'foo'
|
||||
deck.addNote(f2)
|
||||
nids = [f.id, f2.id]
|
||||
# should do nothing
|
||||
@ -245,20 +245,20 @@ def test_findReplace():
|
||||
def test_findDupes():
|
||||
deck = getEmptyCol()
|
||||
f = deck.newNote()
|
||||
f['Front'] = u'foo'
|
||||
f['Back'] = u'bar'
|
||||
f['Front'] = 'foo'
|
||||
f['Back'] = 'bar'
|
||||
deck.addNote(f)
|
||||
f2 = deck.newNote()
|
||||
f2['Front'] = u'baz'
|
||||
f2['Back'] = u'bar'
|
||||
f2['Front'] = 'baz'
|
||||
f2['Back'] = 'bar'
|
||||
deck.addNote(f2)
|
||||
f3 = deck.newNote()
|
||||
f3['Front'] = u'quux'
|
||||
f3['Back'] = u'bar'
|
||||
f3['Front'] = 'quux'
|
||||
f3['Back'] = 'bar'
|
||||
deck.addNote(f3)
|
||||
f4 = deck.newNote()
|
||||
f4['Front'] = u'quuux'
|
||||
f4['Back'] = u'nope'
|
||||
f4['Front'] = 'quuux'
|
||||
f4['Back'] = 'nope'
|
||||
deck.addNote(f4)
|
||||
r = deck.findDupes("Back")
|
||||
assert r[0][0] == "bar"
|
||||
|
@ -2,9 +2,8 @@
|
||||
|
||||
import os
|
||||
from tests.shared import getUpgradeDeckPath, getEmptyCol
|
||||
from anki.upgrade import Upgrader
|
||||
from anki.utils import ids2str
|
||||
from anki.importing import Anki1Importer, Anki2Importer, TextImporter, \
|
||||
from anki.importing import Anki2Importer, TextImporter, \
|
||||
SupermemoXmlImporter, MnemosyneImporter, AnkiPackageImporter
|
||||
|
||||
testDir = os.path.dirname(__file__)
|
||||
@ -12,43 +11,6 @@ testDir = os.path.dirname(__file__)
|
||||
srcNotes=None
|
||||
srcCards=None
|
||||
|
||||
def test_anki2():
|
||||
global srcNotes, srcCards
|
||||
# get the deck to import
|
||||
tmp = getUpgradeDeckPath()
|
||||
u = Upgrader()
|
||||
u.check(tmp)
|
||||
src = u.upgrade()
|
||||
srcpath = src.path
|
||||
srcNotes = src.noteCount()
|
||||
srcCards = src.cardCount()
|
||||
srcRev = src.db.scalar("select count() from revlog")
|
||||
# add a media file for testing
|
||||
open(os.path.join(src.media.dir(), "_foo.jpg"), "w").write("foo")
|
||||
src.close()
|
||||
# create a new empty deck
|
||||
dst = getEmptyCol()
|
||||
# import src into dst
|
||||
imp = Anki2Importer(dst, srcpath)
|
||||
imp.run()
|
||||
def check():
|
||||
assert dst.noteCount() == srcNotes
|
||||
assert dst.cardCount() == srcCards
|
||||
assert srcRev == dst.db.scalar("select count() from revlog")
|
||||
mids = [int(x) for x in dst.models.models.keys()]
|
||||
assert not dst.db.scalar(
|
||||
"select count() from notes where mid not in "+ids2str(mids))
|
||||
assert not dst.db.scalar(
|
||||
"select count() from cards where nid not in (select id from notes)")
|
||||
assert not dst.db.scalar(
|
||||
"select count() from revlog where cid not in (select id from cards)")
|
||||
assert dst.fixIntegrity()[0].startswith("Database rebuilt")
|
||||
check()
|
||||
# importing should be idempotent
|
||||
imp.run()
|
||||
check()
|
||||
assert len(os.listdir(dst.media.dir())) == 1
|
||||
|
||||
def test_anki2_mediadupes():
|
||||
tmp = getEmptyCol()
|
||||
# add a note that references a sound
|
||||
@ -96,7 +58,7 @@ def test_anki2_mediadupes():
|
||||
|
||||
def test_apkg():
|
||||
tmp = getEmptyCol()
|
||||
apkg = unicode(os.path.join(testDir, "support/media.apkg"))
|
||||
apkg = str(os.path.join(testDir, "support/media.apkg"))
|
||||
imp = AnkiPackageImporter(tmp, apkg)
|
||||
assert os.listdir(tmp.media.dir()) == []
|
||||
imp.run()
|
||||
@ -113,65 +75,6 @@ def test_apkg():
|
||||
imp.run()
|
||||
assert len(os.listdir(tmp.media.dir())) == 2
|
||||
|
||||
def test_anki1():
|
||||
# get the deck path to import
|
||||
tmp = getUpgradeDeckPath()
|
||||
# make sure media is imported properly through the upgrade
|
||||
mdir = tmp.replace(".anki2", ".media")
|
||||
if not os.path.exists(mdir):
|
||||
os.mkdir(mdir)
|
||||
open(os.path.join(mdir, "_foo.jpg"), "w").write("foo")
|
||||
# create a new empty deck
|
||||
dst = getEmptyCol()
|
||||
# import src into dst
|
||||
imp = Anki1Importer(dst, tmp)
|
||||
imp.run()
|
||||
def check():
|
||||
assert dst.noteCount() == srcNotes
|
||||
assert dst.cardCount() == srcCards
|
||||
assert len(os.listdir(dst.media.dir())) == 1
|
||||
check()
|
||||
# importing should be idempotent
|
||||
imp = Anki1Importer(dst, tmp)
|
||||
imp.run()
|
||||
check()
|
||||
|
||||
def test_anki1_diffmodels():
|
||||
# create a new empty deck
|
||||
dst = getEmptyCol()
|
||||
# import the 1 card version of the model
|
||||
tmp = getUpgradeDeckPath("diffmodels1.anki")
|
||||
imp = Anki1Importer(dst, tmp)
|
||||
imp.run()
|
||||
before = dst.noteCount()
|
||||
# repeating the process should do nothing
|
||||
imp = Anki1Importer(dst, tmp)
|
||||
imp.run()
|
||||
assert before == dst.noteCount()
|
||||
# then the 2 card version
|
||||
tmp = getUpgradeDeckPath("diffmodels2.anki")
|
||||
imp = Anki1Importer(dst, tmp)
|
||||
imp.run()
|
||||
after = dst.noteCount()
|
||||
# as the model schemas differ, should have been imported as new model
|
||||
assert after == before + 1
|
||||
# repeating the process should do nothing
|
||||
beforeModels = len(dst.models.all())
|
||||
imp = Anki1Importer(dst, tmp)
|
||||
imp.run()
|
||||
after = dst.noteCount()
|
||||
assert after == before + 1
|
||||
assert beforeModels == len(dst.models.all())
|
||||
|
||||
def test_suspended():
|
||||
# create a new empty deck
|
||||
dst = getEmptyCol()
|
||||
# import the 1 card version of the model
|
||||
tmp = getUpgradeDeckPath("suspended12.anki")
|
||||
imp = Anki1Importer(dst, tmp)
|
||||
imp.run()
|
||||
assert dst.db.scalar("select due from cards") < 0
|
||||
|
||||
def test_anki2_diffmodels():
|
||||
# create a new empty deck
|
||||
dst = getEmptyCol()
|
||||
@ -254,7 +157,7 @@ def test_anki2_updates():
|
||||
|
||||
def test_csv():
|
||||
deck = getEmptyCol()
|
||||
file = unicode(os.path.join(testDir, "support/text-2fields.txt"))
|
||||
file = str(os.path.join(testDir, "support/text-2fields.txt"))
|
||||
i = TextImporter(deck, file)
|
||||
i.initMapping()
|
||||
i.run()
|
||||
@ -299,7 +202,7 @@ def test_csv2():
|
||||
n['Three'] = "3"
|
||||
deck.addNote(n)
|
||||
# an update with unmapped fields should not clobber those fields
|
||||
file = unicode(os.path.join(testDir, "support/text-update.txt"))
|
||||
file = str(os.path.join(testDir, "support/text-update.txt"))
|
||||
i = TextImporter(deck, file)
|
||||
i.initMapping()
|
||||
i.run()
|
||||
@ -311,7 +214,7 @@ def test_csv2():
|
||||
|
||||
def test_supermemo_xml_01_unicode():
|
||||
deck = getEmptyCol()
|
||||
file = unicode(os.path.join(testDir, "support/supermemo1.xml"))
|
||||
file = str(os.path.join(testDir, "support/supermemo1.xml"))
|
||||
i = SupermemoXmlImporter(deck, file)
|
||||
#i.META.logToStdOutput = True
|
||||
i.run()
|
||||
@ -325,7 +228,7 @@ def test_supermemo_xml_01_unicode():
|
||||
|
||||
def test_mnemo():
|
||||
deck = getEmptyCol()
|
||||
file = unicode(os.path.join(testDir, "support/mnemo.db"))
|
||||
file = str(os.path.join(testDir, "support/mnemo.db"))
|
||||
i = MnemosyneImporter(deck, file)
|
||||
i.run()
|
||||
assert deck.cardCount() == 7
|
||||
|
@ -1,6 +1,9 @@
|
||||
# coding: utf-8
|
||||
|
||||
import os
|
||||
|
||||
import shutil
|
||||
|
||||
from tests.shared import getEmptyCol
|
||||
from anki.utils import stripHTML
|
||||
|
||||
@ -11,7 +14,7 @@ def test_latex():
|
||||
anki.latex.latexCmds[0][0] = "nolatex"
|
||||
# add a note with latex
|
||||
f = d.newNote()
|
||||
f['Front'] = u"[latex]hello[/latex]"
|
||||
f['Front'] = "[latex]hello[/latex]"
|
||||
d.addNote(f)
|
||||
# but since latex couldn't run, there's nothing there
|
||||
assert len(os.listdir(d.media.dir())) == 0
|
||||
@ -20,11 +23,9 @@ def test_latex():
|
||||
assert "executing nolatex" in msg
|
||||
assert "installed" in msg
|
||||
# check if we have latex installed, and abort test if we don't
|
||||
for cmd in ("latex", "dvipng"):
|
||||
if (not os.path.exists("/usr/bin/"+cmd) and
|
||||
not os.path.exists("/usr/texbin/"+cmd)):
|
||||
print "aborting test; %s is not installed" % cmd
|
||||
return
|
||||
if not shutil.which("latex") or not shutil.which("dvipng"):
|
||||
print("aborting test; %s is not installed" % cmd)
|
||||
return
|
||||
# fix path
|
||||
anki.latex.latexCmds[0][0] = "latex"
|
||||
# check media db should cause latex to be generated
|
||||
@ -33,13 +34,13 @@ def test_latex():
|
||||
assert ".png" in f.cards()[0].q()
|
||||
# adding new notes should cause generation on question display
|
||||
f = d.newNote()
|
||||
f['Front'] = u"[latex]world[/latex]"
|
||||
f['Front'] = "[latex]world[/latex]"
|
||||
d.addNote(f)
|
||||
f.cards()[0].q()
|
||||
assert len(os.listdir(d.media.dir())) == 2
|
||||
# another note with the same media should reuse
|
||||
f = d.newNote()
|
||||
f['Front'] = u" [latex]world[/latex]"
|
||||
f['Front'] = " [latex]world[/latex]"
|
||||
d.addNote(f)
|
||||
assert len(os.listdir(d.media.dir())) == 2
|
||||
oldcard = f.cards()[0]
|
||||
@ -48,7 +49,7 @@ def test_latex():
|
||||
# missing media will show the latex
|
||||
anki.latex.build = False
|
||||
f = d.newNote()
|
||||
f['Front'] = u"[latex]foo[/latex]"
|
||||
f['Front'] = "[latex]foo[/latex]"
|
||||
d.addNote(f)
|
||||
assert len(os.listdir(d.media.dir())) == 2
|
||||
assert stripHTML(f.cards()[0].q()) == "[latex]foo[/latex]"
|
||||
@ -107,7 +108,7 @@ def test_good_latex_command_works():
|
||||
def _test_includes_bad_command(bad):
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = u'[latex]%s[/latex]' % bad;
|
||||
f['Front'] = '[latex]%s[/latex]' % bad;
|
||||
d.addNote(f)
|
||||
q = f.cards()[0].q()
|
||||
return ("'%s' is not allowed on cards" % bad in q, "Card content: %s" % q)
|
@ -4,14 +4,14 @@ import tempfile
|
||||
import os
|
||||
import time
|
||||
|
||||
from shared import getEmptyCol, testDir
|
||||
from .shared import getEmptyCol, testDir
|
||||
|
||||
|
||||
# copying files to media folder
|
||||
def test_add():
|
||||
d = getEmptyCol()
|
||||
dir = tempfile.mkdtemp(prefix="anki")
|
||||
path = os.path.join(dir, u"foo.jpg")
|
||||
path = os.path.join(dir, "foo.jpg")
|
||||
open(path, "w").write("hello")
|
||||
# new file, should preserve name
|
||||
assert d.media.addFile(path) == "foo.jpg"
|
||||
@ -24,7 +24,7 @@ def test_add():
|
||||
def test_strings():
|
||||
d = getEmptyCol()
|
||||
mf = d.media.filesInStr
|
||||
mid = d.models.models.keys()[0]
|
||||
mid = list(d.models.models.keys())[0]
|
||||
assert mf(mid, "aoeu") == []
|
||||
assert mf(mid, "aoeu<img src='foo.jpg'>ao") == ["foo.jpg"]
|
||||
assert mf(mid, "aoeu<img src='foo.jpg' style='test'>ao") == ["foo.jpg"]
|
||||
@ -50,18 +50,18 @@ def test_deckIntegration():
|
||||
# create a media dir
|
||||
d.media.dir()
|
||||
# put a file into it
|
||||
file = unicode(os.path.join(testDir, "support/fake.png"))
|
||||
file = str(os.path.join(testDir, "support/fake.png"))
|
||||
d.media.addFile(file)
|
||||
# add a note which references it
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"; f['Back'] = u"<img src='fake.png'>"
|
||||
f['Front'] = "one"; f['Back'] = "<img src='fake.png'>"
|
||||
d.addNote(f)
|
||||
# and one which references a non-existent file
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"; f['Back'] = u"<img src='fake2.png'>"
|
||||
f['Front'] = "one"; f['Back'] = "<img src='fake2.png'>"
|
||||
d.addNote(f)
|
||||
# and add another file which isn't used
|
||||
open(os.path.join(d.media.dir(), "foo.jpg"), "wb").write("test")
|
||||
open(os.path.join(d.media.dir(), "foo.jpg"), "w").write("test")
|
||||
# check media
|
||||
ret = d.media.check()
|
||||
assert ret[0] == ["fake2.png"]
|
||||
@ -78,7 +78,7 @@ def test_changes():
|
||||
assert not list(removed())
|
||||
# add a file
|
||||
dir = tempfile.mkdtemp(prefix="anki")
|
||||
path = os.path.join(dir, u"foo.jpg")
|
||||
path = os.path.join(dir, "foo.jpg")
|
||||
open(path, "w").write("hello")
|
||||
time.sleep(1)
|
||||
path = d.media.addFile(path)
|
||||
@ -106,8 +106,8 @@ def test_changes():
|
||||
|
||||
def test_illegal():
|
||||
d = getEmptyCol()
|
||||
aString = u"a:b|cd\\e/f\0g*h"
|
||||
good = u"abcdefgh"
|
||||
aString = "a:b|cd\\e/f\0g*h"
|
||||
good = "abcdefgh"
|
||||
assert d.media.stripIllegal(aString) == good
|
||||
for c in aString:
|
||||
bad = d.media.hasIllegal("somestring"+c+"morestring")
|
||||
|
@ -6,8 +6,8 @@ from anki.utils import stripHTML, joinFields
|
||||
def test_modelDelete():
|
||||
deck = getEmptyCol()
|
||||
f = deck.newNote()
|
||||
f['Front'] = u'1'
|
||||
f['Back'] = u'2'
|
||||
f['Front'] = '1'
|
||||
f['Back'] = '2'
|
||||
deck.addNote(f)
|
||||
assert deck.cardCount() == 1
|
||||
deck.models.rem(deck.models.current())
|
||||
@ -29,8 +29,8 @@ def test_modelCopy():
|
||||
def test_fields():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = u'1'
|
||||
f['Back'] = u'2'
|
||||
f['Front'] = '1'
|
||||
f['Back'] = '2'
|
||||
d.addNote(f)
|
||||
m = d.models.current()
|
||||
# make sure renaming a field updates the templates
|
||||
@ -82,8 +82,8 @@ def test_templates():
|
||||
mm.addTemplate(m, t)
|
||||
mm.save(m)
|
||||
f = d.newNote()
|
||||
f['Front'] = u'1'
|
||||
f['Back'] = u'2'
|
||||
f['Front'] = '1'
|
||||
f['Back'] = '2'
|
||||
d.addNote(f)
|
||||
assert d.cardCount() == 2
|
||||
(c, c2) = f.cards()
|
||||
@ -121,7 +121,7 @@ def test_cloze_ordinals():
|
||||
d.models.remTemplate(m, m['tmpls'][0])
|
||||
|
||||
f = d.newNote()
|
||||
f['Text'] = u'{{c1::firstQ::firstA}}{{c2::secondQ::secondA}}'
|
||||
f['Text'] = '{{c1::firstQ::firstA}}{{c2::secondQ::secondA}}'
|
||||
d.addNote(f)
|
||||
assert d.cardCount() == 2
|
||||
(c, c2) = f.cards()
|
||||
@ -136,7 +136,7 @@ def test_text():
|
||||
m['tmpls'][0]['qfmt'] = "{{text:Front}}"
|
||||
d.models.save(m)
|
||||
f = d.newNote()
|
||||
f['Front'] = u'hello<b>world'
|
||||
f['Front'] = 'hello<b>world'
|
||||
d.addNote(f)
|
||||
assert "helloworld" in f.cards()[0].q()
|
||||
|
||||
@ -146,7 +146,7 @@ def test_cloze():
|
||||
f = d.newNote()
|
||||
assert f.model()['name'] == "Cloze"
|
||||
# a cloze model with no clozes is not empty
|
||||
f['Text'] = u'nothing'
|
||||
f['Text'] = 'nothing'
|
||||
assert d.addNote(f)
|
||||
# try with one cloze
|
||||
f = d.newNote()
|
||||
@ -221,8 +221,8 @@ def test_modelChange():
|
||||
mm.addTemplate(m, t)
|
||||
mm.save(m)
|
||||
f = deck.newNote()
|
||||
f['Front'] = u'f'
|
||||
f['Back'] = u'b123'
|
||||
f['Front'] = 'f'
|
||||
f['Back'] = 'b123'
|
||||
deck.addNote(f)
|
||||
# switch fields
|
||||
map = {0: 1, 1: 0}
|
||||
@ -267,8 +267,8 @@ def test_modelChange():
|
||||
assert f['Back'] == 'f'
|
||||
# another note to try model conversion
|
||||
f = deck.newNote()
|
||||
f['Front'] = u'f2'
|
||||
f['Back'] = u'b2'
|
||||
f['Front'] = 'f2'
|
||||
f['Back'] = 'b2'
|
||||
deck.addNote(f)
|
||||
assert deck.models.useCount(basic) == 2
|
||||
assert deck.models.useCount(cloze) == 0
|
||||
|
@ -28,7 +28,7 @@ def test_new():
|
||||
assert d.sched.newCount == 0
|
||||
# add a note
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"; f['Back'] = u"two"
|
||||
f['Front'] = "one"; f['Back'] = "two"
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
assert d.sched.newCount == 1
|
||||
@ -99,7 +99,7 @@ def test_newLimits():
|
||||
def test_newBoxes():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"
|
||||
f['Front'] = "one"
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
c = d.sched.getCard()
|
||||
@ -113,7 +113,7 @@ def test_learn():
|
||||
d = getEmptyCol()
|
||||
# add a note
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"; f['Back'] = u"two"
|
||||
f['Front'] = "one"; f['Back'] = "two"
|
||||
f = d.addNote(f)
|
||||
# set as a learn card and rebuild queues
|
||||
d.db.execute("update cards set queue=0, type=0")
|
||||
@ -126,7 +126,7 @@ def test_learn():
|
||||
d.sched.answerCard(c, 1)
|
||||
# it should have three reps left to graduation
|
||||
assert c.left%1000 == 3
|
||||
assert c.left/1000 == 3
|
||||
assert c.left//1000 == 3
|
||||
# it should by due in 30 seconds
|
||||
t = round(c.due - time.time())
|
||||
assert t >= 25 and t <= 40
|
||||
@ -135,7 +135,7 @@ def test_learn():
|
||||
# it should by due in 3 minutes
|
||||
assert round(c.due - time.time()) in (179, 180)
|
||||
assert c.left%1000 == 2
|
||||
assert c.left/1000 == 2
|
||||
assert c.left//1000 == 2
|
||||
# check log is accurate
|
||||
log = d.db.first("select * from revlog order by id desc")
|
||||
assert log[3] == 2
|
||||
@ -146,7 +146,7 @@ def test_learn():
|
||||
# it should by due in 10 minutes
|
||||
assert round(c.due - time.time()) in (599, 600)
|
||||
assert c.left%1000 == 1
|
||||
assert c.left/1000 == 1
|
||||
assert c.left//1000 == 1
|
||||
# the next pass should graduate the card
|
||||
assert c.queue == 1
|
||||
assert c.type == 1
|
||||
@ -187,10 +187,10 @@ def test_learn_collapsed():
|
||||
d = getEmptyCol()
|
||||
# add 2 notes
|
||||
f = d.newNote()
|
||||
f['Front'] = u"1"
|
||||
f['Front'] = "1"
|
||||
f = d.addNote(f)
|
||||
f = d.newNote()
|
||||
f['Front'] = u"2"
|
||||
f['Front'] = "2"
|
||||
f = d.addNote(f)
|
||||
# set as a learn card and rebuild queues
|
||||
d.db.execute("update cards set queue=0, type=0")
|
||||
@ -213,7 +213,7 @@ def test_learn_day():
|
||||
d = getEmptyCol()
|
||||
# add a note
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"
|
||||
f['Front'] = "one"
|
||||
f = d.addNote(f)
|
||||
d.sched.reset()
|
||||
c = d.sched.getCard()
|
||||
@ -222,7 +222,7 @@ def test_learn_day():
|
||||
d.sched.answerCard(c, 2)
|
||||
# two reps to graduate, 1 more today
|
||||
assert c.left%1000 == 3
|
||||
assert c.left/1000 == 1
|
||||
assert c.left//1000 == 1
|
||||
assert d.sched.counts() == (0, 1, 0)
|
||||
c = d.sched.getCard()
|
||||
ni = d.sched.nextIvl
|
||||
@ -271,7 +271,7 @@ def test_reviews():
|
||||
d = getEmptyCol()
|
||||
# add a note
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"; f['Back'] = u"two"
|
||||
f['Front'] = "one"; f['Back'] = "two"
|
||||
d.addNote(f)
|
||||
# set the card up as a review card, due 8 days ago
|
||||
c = f.cards()[0]
|
||||
@ -362,7 +362,7 @@ def test_reviews():
|
||||
def test_button_spacing():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"
|
||||
f['Front'] = "one"
|
||||
d.addNote(f)
|
||||
# 1 day ivl review card due now
|
||||
c = f.cards()[0]
|
||||
@ -385,7 +385,7 @@ def test_overdue_lapse():
|
||||
d = getEmptyCol()
|
||||
# add a note
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"
|
||||
f['Front'] = "one"
|
||||
d.addNote(f)
|
||||
# simulate a review that was lapsed and is now due for its normal review
|
||||
c = f.cards()[0]
|
||||
@ -420,7 +420,7 @@ def test_finished():
|
||||
assert "Congratulations" in d.sched.finishedMsg()
|
||||
assert "limit" not in d.sched.finishedMsg()
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"; f['Back'] = u"two"
|
||||
f['Front'] = "one"; f['Back'] = "two"
|
||||
d.addNote(f)
|
||||
# have a new card
|
||||
assert "new cards available" in d.sched.finishedMsg()
|
||||
@ -436,7 +436,7 @@ def test_finished():
|
||||
def test_nextIvl():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"; f['Back'] = u"two"
|
||||
f['Front'] = "one"; f['Back'] = "two"
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
conf = d.decks.confForDid(1)
|
||||
@ -492,7 +492,7 @@ def test_nextIvl():
|
||||
def test_misc():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"
|
||||
f['Front'] = "one"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
# burying
|
||||
@ -506,7 +506,7 @@ def test_misc():
|
||||
def test_suspend():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"
|
||||
f['Front'] = "one"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
# suspending
|
||||
@ -549,7 +549,7 @@ def test_suspend():
|
||||
def test_cram():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"
|
||||
f['Front'] = "one"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
c.ivl = 100
|
||||
@ -657,7 +657,7 @@ def test_cram():
|
||||
def test_cram_rem():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"
|
||||
f['Front'] = "one"
|
||||
d.addNote(f)
|
||||
oldDue = f.cards()[0].due
|
||||
did = d.decks.newDyn("Cram")
|
||||
@ -678,7 +678,7 @@ def test_cram_resched():
|
||||
# add card
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"
|
||||
f['Front'] = "one"
|
||||
d.addNote(f)
|
||||
# cram deck
|
||||
did = d.decks.newDyn("Cram")
|
||||
@ -806,7 +806,7 @@ def test_ordcycle():
|
||||
def test_counts_idx():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"; f['Back'] = u"two"
|
||||
f['Front'] = "one"; f['Back'] = "two"
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
assert d.sched.counts() == (1, 0, 0)
|
||||
@ -828,7 +828,7 @@ def test_counts_idx():
|
||||
def test_repCounts():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"
|
||||
f['Front'] = "one"
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
# lrnReps should be accurate on pass/fail
|
||||
@ -846,7 +846,7 @@ def test_repCounts():
|
||||
d.sched.answerCard(d.sched.getCard(), 2)
|
||||
assert d.sched.counts() == (0, 0, 0)
|
||||
f = d.newNote()
|
||||
f['Front'] = u"two"
|
||||
f['Front'] = "two"
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
# initial pass should be correct too
|
||||
@ -858,14 +858,14 @@ def test_repCounts():
|
||||
assert d.sched.counts() == (0, 0, 0)
|
||||
# immediate graduate should work
|
||||
f = d.newNote()
|
||||
f['Front'] = u"three"
|
||||
f['Front'] = "three"
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
d.sched.answerCard(d.sched.getCard(), 3)
|
||||
assert d.sched.counts() == (0, 0, 0)
|
||||
# and failing a review should too
|
||||
f = d.newNote()
|
||||
f['Front'] = u"three"
|
||||
f['Front'] = "three"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
c.type = 2
|
||||
@ -907,7 +907,7 @@ def test_collapse():
|
||||
d = getEmptyCol()
|
||||
# add a note
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"
|
||||
f['Front'] = "one"
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
# test collapsing
|
||||
@ -921,11 +921,11 @@ def test_deckDue():
|
||||
d = getEmptyCol()
|
||||
# add a note with default deck
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"
|
||||
f['Front'] = "one"
|
||||
d.addNote(f)
|
||||
# and one that's a child
|
||||
f = d.newNote()
|
||||
f['Front'] = u"two"
|
||||
f['Front'] = "two"
|
||||
default1 = f.model()['did'] = d.decks.id("Default::1")
|
||||
d.addNote(f)
|
||||
# make it a review card
|
||||
@ -935,12 +935,12 @@ def test_deckDue():
|
||||
c.flush()
|
||||
# add one more with a new deck
|
||||
f = d.newNote()
|
||||
f['Front'] = u"two"
|
||||
f['Front'] = "two"
|
||||
foobar = f.model()['did'] = d.decks.id("foo::bar")
|
||||
d.addNote(f)
|
||||
# and one that's a sibling
|
||||
f = d.newNote()
|
||||
f['Front'] = u"three"
|
||||
f['Front'] = "three"
|
||||
foobaz = f.model()['did'] = d.decks.id("foo::baz")
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
@ -980,16 +980,16 @@ def test_deckFlow():
|
||||
d = getEmptyCol()
|
||||
# add a note with default deck
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"
|
||||
f['Front'] = "one"
|
||||
d.addNote(f)
|
||||
# and one that's a child
|
||||
f = d.newNote()
|
||||
f['Front'] = u"two"
|
||||
f['Front'] = "two"
|
||||
default1 = f.model()['did'] = d.decks.id("Default::2")
|
||||
d.addNote(f)
|
||||
# and another that's higher up
|
||||
f = d.newNote()
|
||||
f['Front'] = u"three"
|
||||
f['Front'] = "three"
|
||||
default1 = f.model()['did'] = d.decks.id("Default::1")
|
||||
d.addNote(f)
|
||||
# should get top level one first, then ::1, then ::2
|
||||
@ -1004,10 +1004,10 @@ def test_reorder():
|
||||
d = getEmptyCol()
|
||||
# add a note with default deck
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"
|
||||
f['Front'] = "one"
|
||||
d.addNote(f)
|
||||
f2 = d.newNote()
|
||||
f2['Front'] = u"two"
|
||||
f2['Front'] = "two"
|
||||
d.addNote(f2)
|
||||
assert f2.cards()[0].due == 2
|
||||
found=False
|
||||
@ -1022,10 +1022,10 @@ def test_reorder():
|
||||
assert f.cards()[0].due == 1
|
||||
# shifting
|
||||
f3 = d.newNote()
|
||||
f3['Front'] = u"three"
|
||||
f3['Front'] = "three"
|
||||
d.addNote(f3)
|
||||
f4 = d.newNote()
|
||||
f4['Front'] = u"four"
|
||||
f4['Front'] = "four"
|
||||
d.addNote(f4)
|
||||
assert f.cards()[0].due == 1
|
||||
assert f2.cards()[0].due == 2
|
||||
@ -1041,7 +1041,7 @@ def test_reorder():
|
||||
def test_forget():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"
|
||||
f['Front'] = "one"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
c.queue = 2; c.type = 2; c.ivl = 100; c.due = 0
|
||||
@ -1055,7 +1055,7 @@ def test_forget():
|
||||
def test_resched():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"
|
||||
f['Front'] = "one"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
d.sched.reschedCards([c.id], 0, 0)
|
||||
@ -1072,7 +1072,7 @@ def test_norelearn():
|
||||
d = getEmptyCol()
|
||||
# add a note
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"
|
||||
f['Front'] = "one"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
c.type = 2
|
||||
@ -1092,7 +1092,7 @@ def test_norelearn():
|
||||
def test_failmult():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"; f['Back'] = u"two"
|
||||
f['Front'] = "one"; f['Back'] = "two"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
c.type = 2
|
||||
|
@ -21,14 +21,14 @@ def setup_basic():
|
||||
deck1 = getEmptyCol()
|
||||
# add a note to deck 1
|
||||
f = deck1.newNote()
|
||||
f['Front'] = u"foo"; f['Back'] = u"bar"; f.tags = [u"foo"]
|
||||
f['Front'] = "foo"; f['Back'] = "bar"; f.tags = ["foo"]
|
||||
deck1.addNote(f)
|
||||
# answer it
|
||||
deck1.reset(); deck1.sched.answerCard(deck1.sched.getCard(), 4)
|
||||
# repeat for deck2
|
||||
deck2 = getEmptyDeckWith(server=True)
|
||||
f = deck2.newNote()
|
||||
f['Front'] = u"bar"; f['Back'] = u"bar"; f.tags = [u"bar"]
|
||||
f['Front'] = "bar"; f['Back'] = "bar"; f.tags = ["bar"]
|
||||
deck2.addNote(f)
|
||||
deck2.reset(); deck2.sched.answerCard(deck2.sched.getCard(), 4)
|
||||
# start with same schema and sync time
|
||||
@ -223,7 +223,7 @@ def test_threeway():
|
||||
# client 1 adds a card at time 1
|
||||
time.sleep(1)
|
||||
f = deck1.newNote()
|
||||
f['Front'] = u"1";
|
||||
f['Front'] = "1";
|
||||
deck1.addNote(f)
|
||||
deck1.save()
|
||||
# at time 2, client 2 syncs to server
|
||||
@ -249,7 +249,7 @@ def test_threeway2():
|
||||
# create collection 1 with a single note
|
||||
c1 = getEmptyCol()
|
||||
f = c1.newNote()
|
||||
f['Front'] = u"startingpoint"
|
||||
f['Front'] = "startingpoint"
|
||||
nid = f.id
|
||||
c1.addNote(f)
|
||||
cid = f.cards()[0].id
|
||||
@ -329,9 +329,9 @@ def _test_speed():
|
||||
deck1.scm = deck2.scm = 0
|
||||
server = LocalServer(deck2)
|
||||
client = Syncer(deck1, server)
|
||||
print "load %d" % ((time.time() - t)*1000); t = time.time()
|
||||
print("load %d" % ((time.time() - t)*1000)); t = time.time()
|
||||
assert client.sync() == "success"
|
||||
print "sync %d" % ((time.time() - t)*1000); t = time.time()
|
||||
print("sync %d" % ((time.time() - t)*1000)); t = time.time()
|
||||
|
||||
@nose.with_setup(setup_modified)
|
||||
def test_filtered_delete():
|
||||
|
@ -27,7 +27,7 @@ def test_op():
|
||||
# and a review will, too
|
||||
d.save("add")
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"
|
||||
f['Front'] = "one"
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
assert d.undoName() == "add"
|
||||
@ -39,7 +39,7 @@ def test_review():
|
||||
d = getEmptyCol()
|
||||
d.conf['counts'] = COUNT_REMAINING
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"
|
||||
f['Front'] = "one"
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
assert not d.undoName()
|
||||
@ -62,7 +62,7 @@ def test_review():
|
||||
assert not d.undoName()
|
||||
# we should be able to undo multiple answers too
|
||||
f = d.newNote()
|
||||
f['Front'] = u"two"
|
||||
f['Front'] = "two"
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
assert d.sched.counts() == (2, 0, 0)
|
||||
|
@ -22,5 +22,5 @@ else
|
||||
args=""
|
||||
echo "Call with coverage=1 to run coverage tests"
|
||||
fi
|
||||
(cd $dir && nosetests -vs $lim $args --cover-package=anki)
|
||||
(cd $dir && nosetests3 -vs $lim $args --cover-package=anki)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user