start port to python 3

unit tests pass and main screens of GUI load
This commit is contained in:
Damien Elmes 2016-05-12 14:45:35 +10:00
parent 1dce3eaaff
commit 15b349e3a8
67 changed files with 510 additions and 759 deletions

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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):

View File

@ -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))

View File

@ -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()

View File

@ -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:

View File

@ -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(

View File

@ -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(),

View File

@ -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

View File

@ -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()

View File

@ -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'],

View File

@ -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'&amp;',u'&',s)
s = re.sub('&amp;','&',s)
#unescaped solitary chars < or > that were ok for minidom confuse btfl soup
#s = re.sub(u'>',u'&gt;',s)
#s = re.sub(u'<',u'&lt;',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):

View File

@ -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(

View File

@ -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()

View File

@ -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)

View File

@ -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):

View File

@ -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
##################################################

View File

@ -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

View File

@ -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:

View File

@ -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)))

View File

@ -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' %

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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,

View File

@ -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:

View File

@ -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_()

View File

@ -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 "

View File

@ -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

View File

@ -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()

View File

@ -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:

View File

@ -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)

View File

@ -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:

View File

@ -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):

View File

@ -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

View File

@ -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(

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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:

View File

@ -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)))

View File

@ -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)

View File

@ -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."

View File

@ -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(" ", "&nbsp;")
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)

View File

@ -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 = ""

View File

@ -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)

View File

@ -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

View File

@ -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
######################################################################

View File

@ -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())

View File

@ -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__)

View 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

View File

@ -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")

View File

@ -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')

View File

@ -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)

View File

@ -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"

View File

@ -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

View File

@ -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)

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -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():

View File

@ -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)

View File

@ -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)