Merge branch 'refs/heads/develop'
This commit is contained in:
@@ -3,6 +3,7 @@ from logging import handlers
|
|||||||
from os.path import dirname
|
from os.path import dirname
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import select
|
||||||
import signal
|
import signal
|
||||||
import socket
|
import socket
|
||||||
import subprocess
|
import subprocess
|
||||||
@@ -121,6 +122,8 @@ if __name__ == '__main__':
|
|||||||
l.run()
|
l.run()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
|
except select.error:
|
||||||
|
pass
|
||||||
except SystemExit:
|
except SystemExit:
|
||||||
raise
|
raise
|
||||||
except socket.error as (nr, msg):
|
except socket.error as (nr, msg):
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ Windows:
|
|||||||
* Install [PyWin32 2.7](http://sourceforge.net/projects/pywin32/files/pywin32/Build%20217/) and [GIT](http://git-scm.com/)
|
* Install [PyWin32 2.7](http://sourceforge.net/projects/pywin32/files/pywin32/Build%20217/) and [GIT](http://git-scm.com/)
|
||||||
* If you come and ask on the forums 'why directory selection no work?', I will kill a kitten, also this is because you need PyWin32
|
* If you come and ask on the forums 'why directory selection no work?', I will kill a kitten, also this is because you need PyWin32
|
||||||
* Open up `Git Bash` (or CMD) and go to the folder you want to install CP. Something like Program Files.
|
* Open up `Git Bash` (or CMD) and go to the folder you want to install CP. Something like Program Files.
|
||||||
* Run `git clone https://RuudBurger@github.com/RuudBurger/CouchPotatoServer.git`.
|
* Run `git clone https://github.com/RuudBurger/CouchPotatoServer.git`.
|
||||||
* You can now start CP via `CouchPotatoServer\CouchPotato.py` to start
|
* You can now start CP via `CouchPotatoServer\CouchPotato.py` to start
|
||||||
|
|
||||||
OSx:
|
OSx:
|
||||||
@@ -23,14 +23,14 @@ OSx:
|
|||||||
* Install [GIT](http://git-scm.com/)
|
* Install [GIT](http://git-scm.com/)
|
||||||
* Open up `Terminal`
|
* Open up `Terminal`
|
||||||
* Go to your App folder `cd /Applications`
|
* Go to your App folder `cd /Applications`
|
||||||
* Run `git clone https://RuudBurger@github.com/RuudBurger/CouchPotatoServer.git`
|
* Run `git clone https://github.com/RuudBurger/CouchPotatoServer.git`
|
||||||
* Then do `python CouchPotatoServer/CouchPotato.py`
|
* Then do `python CouchPotatoServer/CouchPotato.py`
|
||||||
|
|
||||||
Linux (ubuntu / debian):
|
Linux (ubuntu / debian):
|
||||||
|
|
||||||
* Install [GIT](http://git-scm.com/) with `apt-get install git-core`
|
* Install [GIT](http://git-scm.com/) with `apt-get install git-core`
|
||||||
* 'cd' to the folder of your choosing.
|
* 'cd' to the folder of your choosing.
|
||||||
* Run `git clone https://RuudBurger@github.com/RuudBurger/CouchPotatoServer.git`
|
* Run `git clone https://github.com/RuudBurger/CouchPotatoServer.git`
|
||||||
* Then do `python CouchPotatoServer/CouchPotato.py` to start
|
* Then do `python CouchPotatoServer/CouchPotato.py` to start
|
||||||
* To run on boot copy the init script. `cp CouchPotatoServer/init/ubuntu /etc/init.d/couchpotato`
|
* To run on boot copy the init script. `cp CouchPotatoServer/init/ubuntu /etc/init.d/couchpotato`
|
||||||
* Change the paths inside the init script. `nano /etc/init.d/couchpotato`
|
* Change the paths inside the init script. `nano /etc/init.d/couchpotato`
|
||||||
|
|||||||
@@ -24,11 +24,7 @@ web = Blueprint('web', __name__)
|
|||||||
|
|
||||||
|
|
||||||
def get_session(engine = None):
|
def get_session(engine = None):
|
||||||
engine = engine if engine else get_engine()
|
return Env.getSession(engine)
|
||||||
return scoped_session(sessionmaker(bind = engine))
|
|
||||||
|
|
||||||
def get_engine():
|
|
||||||
return create_engine(Env.get('db_path') + '?check_same_thread=False', echo = False)
|
|
||||||
|
|
||||||
def addView(route, func, static = False):
|
def addView(route, func, static = False):
|
||||||
web.add_url_rule(route + ('' if static else '/'), endpoint = route if route else 'index', view_func = func)
|
web.add_url_rule(route + ('' if static else '/'), endpoint = route if route else 'index', view_func = func)
|
||||||
|
|||||||
+2
-2
@@ -6,8 +6,8 @@ api = Blueprint('api', __name__)
|
|||||||
api_docs = {}
|
api_docs = {}
|
||||||
api_docs_missing = []
|
api_docs_missing = []
|
||||||
|
|
||||||
def addApiView(route, func, static = False, docs = None):
|
def addApiView(route, func, static = False, docs = None, **kwargs):
|
||||||
api.add_url_rule(route + ('' if static else '/'), endpoint = route.replace('.', '::') if route else 'index', view_func = func)
|
api.add_url_rule(route + ('' if static else '/'), endpoint = route.replace('.', '::') if route else 'index', view_func = func, **kwargs)
|
||||||
if docs:
|
if docs:
|
||||||
api_docs[route[4:] if route[0:4] == 'api.' else route] = docs
|
api_docs[route[4:] if route[0:4] == 'api.' else route] = docs
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ class Core(Plugin):
|
|||||||
addEvent('setting.save.core.password', self.md5Password)
|
addEvent('setting.save.core.password', self.md5Password)
|
||||||
addEvent('setting.save.core.api_key', self.checkApikey)
|
addEvent('setting.save.core.api_key', self.checkApikey)
|
||||||
|
|
||||||
self.removeRestartFile()
|
|
||||||
|
|
||||||
def md5Password(self, value):
|
def md5Password(self, value):
|
||||||
return md5(value) if value else ''
|
return md5(value) if value else ''
|
||||||
@@ -119,9 +118,6 @@ class Core(Plugin):
|
|||||||
|
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
if restart:
|
|
||||||
self.createFile(self.restartFilePath(), 'This is the most suckiest way to register if CP is restarted. Ever...')
|
|
||||||
|
|
||||||
log.debug('Save to shutdown/restart')
|
log.debug('Save to shutdown/restart')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -133,15 +129,6 @@ class Core(Plugin):
|
|||||||
|
|
||||||
fireEvent('app.after_shutdown', restart = restart)
|
fireEvent('app.after_shutdown', restart = restart)
|
||||||
|
|
||||||
def removeRestartFile(self):
|
|
||||||
try:
|
|
||||||
os.remove(self.restartFilePath())
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def restartFilePath(self):
|
|
||||||
return os.path.join(Env.get('data_dir'), 'restart')
|
|
||||||
|
|
||||||
def launchBrowser(self):
|
def launchBrowser(self):
|
||||||
|
|
||||||
if Env.setting('launch_browser'):
|
if Env.setting('launch_browser'):
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ class Scheduler(Plugin):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
logging.getLogger('apscheduler').setLevel(logging.ERROR)
|
|
||||||
|
|
||||||
addEvent('schedule.cron', self.cron)
|
addEvent('schedule.cron', self.cron)
|
||||||
addEvent('schedule.interval', self.interval)
|
addEvent('schedule.interval', self.interval)
|
||||||
addEvent('schedule.start', self.start)
|
addEvent('schedule.start', self.start)
|
||||||
|
|||||||
@@ -232,6 +232,7 @@ class SourceUpdater(BaseUpdater):
|
|||||||
# Extract
|
# Extract
|
||||||
tar = tarfile.open(destination)
|
tar = tarfile.open(destination)
|
||||||
tar.extractall(path = extracted_path)
|
tar.extractall(path = extracted_path)
|
||||||
|
tar.close()
|
||||||
os.remove(destination)
|
os.remove(destination)
|
||||||
|
|
||||||
self.replaceWith(os.path.join(extracted_path, os.listdir(extracted_path)[0]))
|
self.replaceWith(os.path.join(extracted_path, os.listdir(extracted_path)[0]))
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ def flattenList(l):
|
|||||||
def md5(text):
|
def md5(text):
|
||||||
return hashlib.md5(text).hexdigest()
|
return hashlib.md5(text).hexdigest()
|
||||||
|
|
||||||
|
def sha1(text):
|
||||||
|
return hashlib.sha1(text).hexdigest()
|
||||||
|
|
||||||
def getExt(filename):
|
def getExt(filename):
|
||||||
return os.path.splitext(filename)[1][1:]
|
return os.path.splitext(filename)[1][1:]
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ from couchpotato.core.event import addEvent
|
|||||||
from couchpotato.core.helpers.request import jsonified
|
from couchpotato.core.helpers.request import jsonified
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
|
from couchpotato.environment import Env
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Notification(Plugin):
|
class Notification(Plugin):
|
||||||
|
|
||||||
default_title = 'CouchPotato'
|
default_title = Env.get('appname')
|
||||||
test_message = 'ZOMG Lazors Pewpewpew!'
|
test_message = 'ZOMG Lazors Pewpewpew!'
|
||||||
|
|
||||||
listen_to = ['movie.downloaded', 'movie.snatched', 'updater.available']
|
listen_to = ['movie.downloaded', 'movie.snatched', 'updater.available']
|
||||||
@@ -29,11 +30,11 @@ class Notification(Plugin):
|
|||||||
def notify(message, data):
|
def notify(message, data):
|
||||||
if not self.conf('on_snatch', default = True) and listener == 'movie.snatched':
|
if not self.conf('on_snatch', default = True) and listener == 'movie.snatched':
|
||||||
return
|
return
|
||||||
return self.notify(message = message, data = data)
|
return self.notify(message = message, data = data, listener = listener)
|
||||||
|
|
||||||
return notify
|
return notify
|
||||||
|
|
||||||
def notify(self, message = '', data = {}):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test(self):
|
def test(self):
|
||||||
@@ -44,7 +45,8 @@ class Notification(Plugin):
|
|||||||
|
|
||||||
success = self.notify(
|
success = self.notify(
|
||||||
message = self.test_message,
|
message = self.test_message,
|
||||||
data = {}
|
data = {},
|
||||||
|
listener = 'test'
|
||||||
)
|
)
|
||||||
|
|
||||||
return jsonified({'success': success})
|
return jsonified({'success': success})
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class Boxcar(Notification):
|
|||||||
|
|
||||||
url = 'https://boxcar.io/devices/providers/7MNNXY3UIzVBwvzkKwkC/notifications'
|
url = 'https://boxcar.io/devices/providers/7MNNXY3UIzVBwvzkKwkC/notifications'
|
||||||
|
|
||||||
def notify(self, message = '', data = {}):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
if self.isDisabled(): return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ class CoreNotifier(Notification):
|
|||||||
q.update({Notif.read: True})
|
q.update({Notif.read: True})
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
|
db.close()
|
||||||
|
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True
|
'success': True
|
||||||
@@ -90,16 +91,19 @@ class CoreNotifier(Notification):
|
|||||||
ndict['type'] = 'notification'
|
ndict['type'] = 'notification'
|
||||||
notifications.append(ndict)
|
notifications.append(ndict)
|
||||||
|
|
||||||
|
db.close()
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True,
|
'success': True,
|
||||||
'empty': len(notifications) == 0,
|
'empty': len(notifications) == 0,
|
||||||
'notifications': notifications
|
'notifications': notifications
|
||||||
})
|
})
|
||||||
|
|
||||||
def notify(self, message = '', data = {}):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
|
|
||||||
|
data['notification_type'] = listener if listener else 'unknown'
|
||||||
|
|
||||||
n = Notif(
|
n = Notif(
|
||||||
message = toUnicode(message),
|
message = toUnicode(message),
|
||||||
data = data
|
data = data
|
||||||
@@ -112,7 +116,8 @@ class CoreNotifier(Notification):
|
|||||||
ndict['time'] = time.time()
|
ndict['time'] = time.time()
|
||||||
self.messages.append(ndict)
|
self.messages.append(ndict)
|
||||||
|
|
||||||
db.remove()
|
db.close()
|
||||||
|
return True
|
||||||
|
|
||||||
def frontend(self, type = 'notification', data = {}):
|
def frontend(self, type = 'notification', data = {}):
|
||||||
self.messages.append({
|
self.messages.append({
|
||||||
@@ -141,6 +146,8 @@ class CoreNotifier(Notification):
|
|||||||
ndict['type'] = 'notification'
|
ndict['type'] = 'notification'
|
||||||
messages.append(ndict)
|
messages.append(ndict)
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
|
||||||
self.messages = []
|
self.messages = []
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True,
|
'success': True,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from couchpotato.core.event import fireEvent
|
from couchpotato.core.event import fireEvent
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.notifications.base import Notification
|
from couchpotato.core.notifications.base import Notification
|
||||||
|
from couchpotato.environment import Env
|
||||||
from gntp import notifier
|
from gntp import notifier
|
||||||
import logging
|
import logging
|
||||||
import traceback
|
import traceback
|
||||||
@@ -15,8 +16,6 @@ class Growl(Notification):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(Growl, self).__init__()
|
super(Growl, self).__init__()
|
||||||
|
|
||||||
logging.getLogger('gntp').setLevel(logging.WARNING)
|
|
||||||
|
|
||||||
if self.isEnabled():
|
if self.isEnabled():
|
||||||
self.register()
|
self.register()
|
||||||
|
|
||||||
@@ -29,7 +28,7 @@ class Growl(Notification):
|
|||||||
port = self.conf('port')
|
port = self.conf('port')
|
||||||
|
|
||||||
self.growl = notifier.GrowlNotifier(
|
self.growl = notifier.GrowlNotifier(
|
||||||
applicationName = 'CouchPotato',
|
applicationName = Env.get('appname'),
|
||||||
notifications = ["Updates"],
|
notifications = ["Updates"],
|
||||||
defaultNotifications = ["Updates"],
|
defaultNotifications = ["Updates"],
|
||||||
applicationIcon = '%s/static/images/couch.png' % fireEvent('app.api_url', single = True),
|
applicationIcon = '%s/static/images/couch.png' % fireEvent('app.api_url', single = True),
|
||||||
@@ -42,7 +41,7 @@ class Growl(Notification):
|
|||||||
except:
|
except:
|
||||||
log.error('Failed register of growl: %s' % traceback.format_exc())
|
log.error('Failed register of growl: %s' % traceback.format_exc())
|
||||||
|
|
||||||
def notify(self, message = '', data = {}):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
if self.isDisabled(): return
|
||||||
|
|
||||||
self.register()
|
self.register()
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class History(Notification):
|
|||||||
|
|
||||||
listen_to = ['movie.downloaded', 'movie.snatched', 'renamer.canceled']
|
listen_to = ['movie.downloaded', 'movie.snatched', 'renamer.canceled']
|
||||||
|
|
||||||
def notify(self, message = '', data = {}):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
history = Hist(
|
history = Hist(
|
||||||
@@ -22,3 +22,6 @@ class History(Notification):
|
|||||||
)
|
)
|
||||||
db.add(history)
|
db.add(history)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class Notifo(Notification):
|
|||||||
|
|
||||||
url = 'https://api.notifo.com/v1/send_notification'
|
url = 'https://api.notifo.com/v1/send_notification'
|
||||||
|
|
||||||
def notify(self, message = '', data = {}):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
if self.isDisabled(): return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ log = CPLog(__name__)
|
|||||||
|
|
||||||
class NotifyMyAndroid(Notification):
|
class NotifyMyAndroid(Notification):
|
||||||
|
|
||||||
def notify(self, message = '', data = {}):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
if self.isDisabled(): return
|
||||||
|
|
||||||
nma = pynma.PyNMA()
|
nma = pynma.PyNMA()
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ log = CPLog(__name__)
|
|||||||
|
|
||||||
class NotifyMyWP(Notification):
|
class NotifyMyWP(Notification):
|
||||||
|
|
||||||
def notify(self, message = '', data = {}):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
if self.isDisabled(): return
|
||||||
|
|
||||||
keys = [x.strip() for x in self.conf('api_key').split(',')]
|
keys = [x.strip() for x in self.conf('api_key').split(',')]
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ log = CPLog(__name__)
|
|||||||
|
|
||||||
class Prowl(Notification):
|
class Prowl(Notification):
|
||||||
|
|
||||||
def notify(self, message = '', data = {}):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
if self.isDisabled(): return
|
||||||
|
|
||||||
http_handler = HTTPSConnection('api.prowlapp.com')
|
http_handler = HTTPSConnection('api.prowlapp.com')
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class Pushover(Notification):
|
|||||||
|
|
||||||
app_token = 'YkxHMYDZp285L265L3IwH3LmzkTaCy'
|
app_token = 'YkxHMYDZp285L265L3IwH3LmzkTaCy'
|
||||||
|
|
||||||
def notify(self, message = '', data = {}):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
if self.isDisabled(): return
|
||||||
|
|
||||||
http_handler = HTTPSConnection("api.pushover.net:443")
|
http_handler = HTTPSConnection("api.pushover.net:443")
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class Twitter(Notification):
|
|||||||
addApiView('notify.%s.auth_url' % self.getName().lower(), self.getAuthorizationUrl)
|
addApiView('notify.%s.auth_url' % self.getName().lower(), self.getAuthorizationUrl)
|
||||||
addApiView('notify.%s.credentials' % self.getName().lower(), self.getCredentials)
|
addApiView('notify.%s.credentials' % self.getName().lower(), self.getCredentials)
|
||||||
|
|
||||||
def notify(self, message = '', data = {}):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
if self.isDisabled(): return
|
||||||
|
|
||||||
api = Api(self.consumer_key, self.consumer_secret, self.conf('access_token_key'), self.conf('access_token_secret'))
|
api = Api(self.consumer_key, self.consumer_secret, self.conf('access_token_key'), self.conf('access_token_secret'))
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class XBMC(Notification):
|
|||||||
|
|
||||||
listen_to = ['movie.downloaded']
|
listen_to = ['movie.downloaded']
|
||||||
|
|
||||||
def notify(self, message = '', data = {}):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
if self.isDisabled(): return
|
||||||
|
|
||||||
for host in [x.strip() for x in self.conf('host').split(",")]:
|
for host in [x.strip() for x in self.conf('host').split(",")]:
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ class FileManager(Plugin):
|
|||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
type_dict = ft.to_dict()
|
type_dict = ft.to_dict()
|
||||||
db.remove()
|
db.close()
|
||||||
return type_dict
|
return type_dict
|
||||||
|
|
||||||
def getTypes(self):
|
def getTypes(self):
|
||||||
@@ -100,4 +100,5 @@ class FileManager(Plugin):
|
|||||||
for type_object in results:
|
for type_object in results:
|
||||||
types.append(type_object.to_dict())
|
types.append(type_object.to_dict())
|
||||||
|
|
||||||
|
db.close()
|
||||||
return types
|
return types
|
||||||
|
|||||||
@@ -51,7 +51,10 @@ class LibraryPlugin(Plugin):
|
|||||||
handle = fireEventAsync if update_after is 'async' else fireEvent
|
handle = fireEventAsync if update_after is 'async' else fireEvent
|
||||||
handle('library.update', identifier = l.identifier, default_title = toUnicode(attrs.get('title', '')))
|
handle('library.update', identifier = l.identifier, default_title = toUnicode(attrs.get('title', '')))
|
||||||
|
|
||||||
return l.to_dict(self.default_dict)
|
library_dict = l.to_dict(self.default_dict)
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
return library_dict
|
||||||
|
|
||||||
def update(self, identifier, default_title = '', force = False):
|
def update(self, identifier, default_title = '', force = False):
|
||||||
|
|
||||||
@@ -68,7 +71,13 @@ class LibraryPlugin(Plugin):
|
|||||||
do_update = False
|
do_update = False
|
||||||
else:
|
else:
|
||||||
info = fireEvent('movie.info', merge = True, identifier = identifier)
|
info = fireEvent('movie.info', merge = True, identifier = identifier)
|
||||||
del info['in_wanted'], info['in_library'] # Don't need those here
|
|
||||||
|
# Don't need those here
|
||||||
|
try: del info['in_wanted']
|
||||||
|
except: pass
|
||||||
|
try: del info['in_library']
|
||||||
|
except: pass
|
||||||
|
|
||||||
if not info or len(info) == 0:
|
if not info or len(info) == 0:
|
||||||
log.error('Could not update, no movie info to work with: %s' % identifier)
|
log.error('Could not update, no movie info to work with: %s' % identifier)
|
||||||
return False
|
return False
|
||||||
@@ -121,6 +130,7 @@ class LibraryPlugin(Plugin):
|
|||||||
|
|
||||||
fireEvent('library.update_finish', data = library_dict)
|
fireEvent('library.update_finish', data = library_dict)
|
||||||
|
|
||||||
|
db.close()
|
||||||
return library_dict
|
return library_dict
|
||||||
|
|
||||||
def updateReleaseDate(self, identifier):
|
def updateReleaseDate(self, identifier):
|
||||||
@@ -134,7 +144,7 @@ class LibraryPlugin(Plugin):
|
|||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
dates = library.info.get('release_date', {})
|
dates = library.info.get('release_date', {})
|
||||||
db.remove()
|
db.close()
|
||||||
|
|
||||||
return dates
|
return dates
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from couchpotato.core.helpers.request import getParams, jsonified, getParam
|
|||||||
from couchpotato.core.helpers.variable import getImdb
|
from couchpotato.core.helpers.variable import getImdb
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
from couchpotato.core.settings.model import Movie, Library, LibraryTitle
|
from couchpotato.core.settings.model import Library, LibraryTitle, Movie
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
from sqlalchemy.orm import joinedload_all
|
from sqlalchemy.orm import joinedload_all
|
||||||
from sqlalchemy.sql.expression import or_, asc, not_
|
from sqlalchemy.sql.expression import or_, asc, not_
|
||||||
@@ -60,7 +60,7 @@ class MoviePlugin(Plugin):
|
|||||||
addApiView('movie.refresh', self.refresh, docs = {
|
addApiView('movie.refresh', self.refresh, docs = {
|
||||||
'desc': 'Refresh a movie by id',
|
'desc': 'Refresh a movie by id',
|
||||||
'params': {
|
'params': {
|
||||||
'id': {'desc': 'The id of the movie that needs to be refreshed'},
|
'id': {'desc': 'Movie ID(s) you want to refresh.', 'type': 'int (comma separated)'},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
addApiView('movie.available_chars', self.charView)
|
addApiView('movie.available_chars', self.charView)
|
||||||
@@ -109,10 +109,12 @@ class MoviePlugin(Plugin):
|
|||||||
db = get_session()
|
db = get_session()
|
||||||
m = db.query(Movie).filter_by(id = movie_id).first()
|
m = db.query(Movie).filter_by(id = movie_id).first()
|
||||||
|
|
||||||
|
results = None
|
||||||
if m:
|
if m:
|
||||||
return m.to_dict(self.default_dict)
|
results = m.to_dict(self.default_dict)
|
||||||
|
|
||||||
return None
|
db.close()
|
||||||
|
return results
|
||||||
|
|
||||||
def list(self, status = ['active'], limit_offset = None, starts_with = None, search = None):
|
def list(self, status = ['active'], limit_offset = None, starts_with = None, search = None):
|
||||||
|
|
||||||
@@ -175,6 +177,7 @@ class MoviePlugin(Plugin):
|
|||||||
})
|
})
|
||||||
movies.append(temp)
|
movies.append(temp)
|
||||||
|
|
||||||
|
db.close()
|
||||||
return movies
|
return movies
|
||||||
|
|
||||||
def availableChars(self, status = ['active']):
|
def availableChars(self, status = ['active']):
|
||||||
@@ -200,6 +203,7 @@ class MoviePlugin(Plugin):
|
|||||||
if char not in chars:
|
if char not in chars:
|
||||||
chars += char
|
chars += char
|
||||||
|
|
||||||
|
db.close()
|
||||||
return chars
|
return chars
|
||||||
|
|
||||||
def listView(self):
|
def listView(self):
|
||||||
@@ -232,20 +236,21 @@ class MoviePlugin(Plugin):
|
|||||||
|
|
||||||
def refresh(self):
|
def refresh(self):
|
||||||
|
|
||||||
params = getParams()
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
|
|
||||||
movie = db.query(Movie).filter_by(id = params.get('id')).first()
|
for id in getParam('id').split(','):
|
||||||
|
movie = db.query(Movie).filter_by(id = id).first()
|
||||||
|
|
||||||
# Get current selected title
|
# Get current selected title
|
||||||
default_title = ''
|
default_title = ''
|
||||||
for title in movie.library.titles:
|
for title in movie.library.titles:
|
||||||
if title.default: default_title = title.title
|
if title.default: default_title = title.title
|
||||||
|
|
||||||
if movie:
|
if movie:
|
||||||
fireEventAsync('library.update', identifier = movie.library.identifier, default_title = default_title, force = True)
|
fireEventAsync('library.update', identifier = movie.library.identifier, default_title = default_title, force = True)
|
||||||
fireEventAsync('searcher.single', movie.to_dict(self.default_dict))
|
fireEventAsync('searcher.single', movie.to_dict(self.default_dict))
|
||||||
|
|
||||||
|
db.close()
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True,
|
'success': True,
|
||||||
})
|
})
|
||||||
@@ -270,7 +275,7 @@ class MoviePlugin(Plugin):
|
|||||||
'movies': movies,
|
'movies': movies,
|
||||||
})
|
})
|
||||||
|
|
||||||
def add(self, params = {}, force_readd = True):
|
def add(self, params = {}, force_readd = True, search_after = True):
|
||||||
|
|
||||||
library = fireEvent('library.add', single = True, attrs = params, update_after = False)
|
library = fireEvent('library.add', single = True, attrs = params, update_after = False)
|
||||||
|
|
||||||
@@ -316,9 +321,10 @@ class MoviePlugin(Plugin):
|
|||||||
|
|
||||||
movie_dict = m.to_dict(self.default_dict)
|
movie_dict = m.to_dict(self.default_dict)
|
||||||
|
|
||||||
if force_readd or do_search:
|
if (force_readd or do_search) and search_after:
|
||||||
fireEventAsync('searcher.single', movie_dict)
|
fireEventAsync('searcher.single', movie_dict)
|
||||||
|
|
||||||
|
db.close()
|
||||||
return movie_dict
|
return movie_dict
|
||||||
|
|
||||||
|
|
||||||
@@ -365,6 +371,7 @@ class MoviePlugin(Plugin):
|
|||||||
movie_dict = m.to_dict(self.default_dict)
|
movie_dict = m.to_dict(self.default_dict)
|
||||||
fireEventAsync('searcher.single', movie_dict)
|
fireEventAsync('searcher.single', movie_dict)
|
||||||
|
|
||||||
|
db.close()
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True,
|
'success': True,
|
||||||
})
|
})
|
||||||
@@ -419,6 +426,7 @@ class MoviePlugin(Plugin):
|
|||||||
else:
|
else:
|
||||||
fireEvent('movie.restatus', movie.id, single = True)
|
fireEvent('movie.restatus', movie.id, single = True)
|
||||||
|
|
||||||
|
db.close()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def restatus(self, movie_id):
|
def restatus(self, movie_id):
|
||||||
@@ -429,6 +437,9 @@ class MoviePlugin(Plugin):
|
|||||||
db = get_session()
|
db = get_session()
|
||||||
|
|
||||||
m = db.query(Movie).filter_by(id = movie_id).first()
|
m = db.query(Movie).filter_by(id = movie_id).first()
|
||||||
|
if not m:
|
||||||
|
log.debug('Can\'t restatus movie, doesn\'t seem to exist.')
|
||||||
|
return False
|
||||||
|
|
||||||
log.debug('Changing status for %s' % (m.library.titles[0].title))
|
log.debug('Changing status for %s' % (m.library.titles[0].title))
|
||||||
if not m.profile:
|
if not m.profile:
|
||||||
@@ -444,3 +455,6 @@ class MoviePlugin(Plugin):
|
|||||||
m.status_id = active_status.get('id') if move_to_wanted else done_status.get('id')
|
m.status_id = active_status.get('id') if move_to_wanted else done_status.get('id')
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|||||||
@@ -144,6 +144,15 @@ var MovieList = new Class({
|
|||||||
'click': self.deleteSelected.bind(self)
|
'click': self.deleteSelected.bind(self)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
),
|
||||||
|
new Element('div.refresh').adopt(
|
||||||
|
new Element('span[text=or]'),
|
||||||
|
new Element('a.button.green', {
|
||||||
|
'text': 'Refresh',
|
||||||
|
'events': {
|
||||||
|
'click': self.refreshSelected.bind(self)
|
||||||
|
}
|
||||||
|
})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
).inject(self.el, 'top');
|
).inject(self.el, 'top');
|
||||||
@@ -245,8 +254,8 @@ var MovieList = new Class({
|
|||||||
var self = this;
|
var self = this;
|
||||||
var ids = self.getSelectedMovies()
|
var ids = self.getSelectedMovies()
|
||||||
|
|
||||||
var qObj = new Question('Are you sure you want to delete the selected movies?', 'Items using this profile, will be set to the default quality.', [{
|
var qObj = new Question('Are you sure you want to delete '+ids.length+' movie'+ (ids.length != 1 ? 's' : '') +'?', 'If you do, you won\'t be able to watch them, as they won\'t get downloaded!', [{
|
||||||
'text': 'Yes, delete them',
|
'text': 'Yes, delete '+(ids.length != 1 ? 'them' : 'it'),
|
||||||
'class': 'delete',
|
'class': 'delete',
|
||||||
'events': {
|
'events': {
|
||||||
'click': function(e){
|
'click': function(e){
|
||||||
@@ -292,6 +301,17 @@ var MovieList = new Class({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
refreshSelected: function(){
|
||||||
|
var self = this;
|
||||||
|
var ids = self.getSelectedMovies()
|
||||||
|
|
||||||
|
Api.request('movie.refresh', {
|
||||||
|
'data': {
|
||||||
|
'id': ids.join(','),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
getSelectedMovies: function(){
|
getSelectedMovies: function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
|||||||
@@ -456,11 +456,13 @@
|
|||||||
padding: 3px 7px;
|
padding: 3px 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.movies .alph_nav .mass_edit_form .refresh,
|
||||||
.movies .alph_nav .mass_edit_form .delete {
|
.movies .alph_nav .mass_edit_form .delete {
|
||||||
float: left;
|
float: left;
|
||||||
padding: 8px 0 0 8px;
|
padding: 8px 0 0 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.movies .alph_nav .mass_edit_form .refresh span,
|
||||||
.movies .alph_nav .mass_edit_form .delete span {
|
.movies .alph_nav .mass_edit_form .delete span {
|
||||||
margin: 0 10px 0 0;
|
margin: 0 10px 0 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ var Movie = new Class({
|
|||||||
self.profile.getTypes().each(function(type){
|
self.profile.getTypes().each(function(type){
|
||||||
|
|
||||||
var q = self.addQuality(type.quality_id || type.get('quality_id'));
|
var q = self.addQuality(type.quality_id || type.get('quality_id'));
|
||||||
if(type.finish || type.get('finish'))
|
if(type.finish == true || type.get('finish'))
|
||||||
q.addClass('finish');
|
q.addClass('finish');
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -82,7 +82,7 @@ var Movie = new Class({
|
|||||||
|
|
||||||
if(!q && (status.identifier == 'snatched' || status.identifier == 'done'))
|
if(!q && (status.identifier == 'snatched' || status.identifier == 'done'))
|
||||||
var q = self.addQuality(release.quality_id)
|
var q = self.addQuality(release.quality_id)
|
||||||
if (q)
|
if (status && q)
|
||||||
q.addClass(status.identifier);
|
q.addClass(status.identifier);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ class ProfilePlugin(Plugin):
|
|||||||
for profile in profiles:
|
for profile in profiles:
|
||||||
temp.append(profile.to_dict(self.to_dict))
|
temp.append(profile.to_dict(self.to_dict))
|
||||||
|
|
||||||
|
db.close()
|
||||||
return temp
|
return temp
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
@@ -83,6 +84,7 @@ class ProfilePlugin(Plugin):
|
|||||||
|
|
||||||
profile_dict = p.to_dict(self.to_dict)
|
profile_dict = p.to_dict(self.to_dict)
|
||||||
|
|
||||||
|
db.close()
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True,
|
'success': True,
|
||||||
'profile': profile_dict
|
'profile': profile_dict
|
||||||
@@ -92,8 +94,10 @@ class ProfilePlugin(Plugin):
|
|||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
default = db.query(Profile).first()
|
default = db.query(Profile).first()
|
||||||
|
default_dict = default.to_dict(self.to_dict)
|
||||||
|
db.close()
|
||||||
|
|
||||||
return default.to_dict(self.to_dict)
|
return default_dict
|
||||||
|
|
||||||
def saveOrder(self):
|
def saveOrder(self):
|
||||||
|
|
||||||
@@ -109,6 +113,7 @@ class ProfilePlugin(Plugin):
|
|||||||
order += 1
|
order += 1
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
|
db.close()
|
||||||
|
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True
|
'success': True
|
||||||
@@ -133,6 +138,8 @@ class ProfilePlugin(Plugin):
|
|||||||
message = 'Failed deleting Profile: %s' % e
|
message = 'Failed deleting Profile: %s' % e
|
||||||
log.error(message)
|
log.error(message)
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': success,
|
'success': success,
|
||||||
'message': message
|
'message': message
|
||||||
@@ -180,4 +187,5 @@ class ProfilePlugin(Plugin):
|
|||||||
|
|
||||||
order += 1
|
order += 1
|
||||||
|
|
||||||
|
db.close()
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class QualityPlugin(Plugin):
|
|||||||
{'identifier': '720p', 'hd': True, 'size': (3500, 10000), 'label': '720P', 'width': 1280, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts', 'ts']},
|
{'identifier': '720p', 'hd': True, 'size': (3500, 10000), 'label': '720P', 'width': 1280, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts', 'ts']},
|
||||||
{'identifier': 'brrip', 'hd': True, 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p'], 'ext':['avi']},
|
{'identifier': 'brrip', 'hd': True, 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p'], 'ext':['avi']},
|
||||||
{'identifier': 'dvdr', 'size': (3000, 10000), 'label': 'DVD-R', 'alternative': [], 'allow': [], 'ext':['iso', 'img'], 'tags': ['pal', 'ntsc', 'video_ts', 'audio_ts']},
|
{'identifier': 'dvdr', 'size': (3000, 10000), 'label': 'DVD-R', 'alternative': [], 'allow': [], 'ext':['iso', 'img'], 'tags': ['pal', 'ntsc', 'video_ts', 'audio_ts']},
|
||||||
{'identifier': 'dvdrip', 'size': (600, 2400), 'label': 'DVD-Rip', 'alternative': ['dvdrip'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
|
{'identifier': 'dvdrip', 'size': (600, 2400), 'label': 'DVD-Rip', 'width': 720, 'alternative': ['dvdrip'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
|
||||||
{'identifier': 'scr', 'size': (600, 1600), 'label': 'Screener', 'alternative': ['screener', 'dvdscr', 'ppvrip'], 'allow': ['dvdr', 'dvd'], 'ext':['avi', 'mpg', 'mpeg']},
|
{'identifier': 'scr', 'size': (600, 1600), 'label': 'Screener', 'alternative': ['screener', 'dvdscr', 'ppvrip'], 'allow': ['dvdr', 'dvd'], 'ext':['avi', 'mpg', 'mpeg']},
|
||||||
{'identifier': 'r5', 'size': (600, 1000), 'label': 'R5', 'alternative': [], 'allow': ['dvdr'], 'ext':['avi', 'mpg', 'mpeg']},
|
{'identifier': 'r5', 'size': (600, 1000), 'label': 'R5', 'alternative': [], 'allow': ['dvdr'], 'ext':['avi', 'mpg', 'mpeg']},
|
||||||
{'identifier': 'tc', 'size': (600, 1000), 'label': 'TeleCine', 'alternative': ['telecine'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
|
{'identifier': 'tc', 'size': (600, 1000), 'label': 'TeleCine', 'alternative': ['telecine'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
|
||||||
@@ -68,6 +68,7 @@ class QualityPlugin(Plugin):
|
|||||||
q = mergeDicts(self.getQuality(quality.identifier), quality.to_dict())
|
q = mergeDicts(self.getQuality(quality.identifier), quality.to_dict())
|
||||||
temp.append(q)
|
temp.append(q)
|
||||||
|
|
||||||
|
db.close()
|
||||||
return temp
|
return temp
|
||||||
|
|
||||||
def single(self, identifier = ''):
|
def single(self, identifier = ''):
|
||||||
@@ -79,6 +80,7 @@ class QualityPlugin(Plugin):
|
|||||||
if quality:
|
if quality:
|
||||||
quality_dict = dict(self.getQuality(quality.identifier), **quality.to_dict())
|
quality_dict = dict(self.getQuality(quality.identifier), **quality.to_dict())
|
||||||
|
|
||||||
|
db.close()
|
||||||
return quality_dict
|
return quality_dict
|
||||||
|
|
||||||
def getQuality(self, identifier):
|
def getQuality(self, identifier):
|
||||||
@@ -98,6 +100,7 @@ class QualityPlugin(Plugin):
|
|||||||
setattr(quality, params.get('value_type'), params.get('value'))
|
setattr(quality, params.get('value_type'), params.get('value'))
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
|
db.close()
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True
|
'success': True
|
||||||
})
|
})
|
||||||
@@ -149,9 +152,10 @@ class QualityPlugin(Plugin):
|
|||||||
order += 1
|
order += 1
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
|
db.close()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def guess(self, files, extra = {}, loose = False):
|
def guess(self, files, extra = {}):
|
||||||
|
|
||||||
# Create hash for cache
|
# Create hash for cache
|
||||||
hash = md5(str([f.replace('.' + getExt(f), '') for f in files]))
|
hash = md5(str([f.replace('.' + getExt(f), '') for f in files]))
|
||||||
@@ -182,25 +186,25 @@ class QualityPlugin(Plugin):
|
|||||||
log.debug('Found %s via tag %s in %s' % (quality['identifier'], quality.get('tags'), cur_file))
|
log.debug('Found %s via tag %s in %s' % (quality['identifier'], quality.get('tags'), cur_file))
|
||||||
return self.setCache(hash, quality)
|
return self.setCache(hash, quality)
|
||||||
|
|
||||||
# Check on unreliable stuff
|
|
||||||
if loose:
|
|
||||||
|
|
||||||
# Last check on resolution only
|
|
||||||
if quality.get('width', 480) == extra.get('resolution_width', 0):
|
|
||||||
log.debug('Found %s via resolution_width: %s == %s' % (quality['identifier'], quality.get('width', 480), extra.get('resolution_width', 0)))
|
|
||||||
return self.setCache(hash, quality)
|
|
||||||
|
|
||||||
# Check extension + filesize
|
|
||||||
if list(set(quality.get('ext', [])) & set(words)) and size >= quality['size_min'] and size <= quality['size_max']:
|
|
||||||
log.debug('Found %s via ext and filesize %s in %s' % (quality['identifier'], quality.get('ext'), words))
|
|
||||||
return self.setCache(hash, quality)
|
|
||||||
|
|
||||||
|
|
||||||
# Try again with loose testing
|
# Try again with loose testing
|
||||||
if not loose:
|
quality = self.guessLoose(hash, extra = extra)
|
||||||
quality = self.guess(files, extra = extra, loose = True)
|
if quality:
|
||||||
if quality:
|
return self.setCache(hash, quality)
|
||||||
return self.setCache(hash, quality)
|
|
||||||
|
|
||||||
log.debug('Could not identify quality for: %s' % files)
|
log.debug('Could not identify quality for: %s' % files)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def guessLoose(self, hash, extra):
|
||||||
|
|
||||||
|
for quality in self.all():
|
||||||
|
|
||||||
|
# Last check on resolution only
|
||||||
|
if quality.get('width', 480) == extra.get('resolution_width', 0):
|
||||||
|
log.debug('Found %s via resolution_width: %s == %s' % (quality['identifier'], quality.get('width', 480), extra.get('resolution_width', 0)))
|
||||||
|
return self.setCache(hash, quality)
|
||||||
|
|
||||||
|
if 480 <= extra.get('resolution_width', 0) <= 720:
|
||||||
|
log.debug('Found as dvdrip')
|
||||||
|
return self.setCache(hash, self.single('dvdrip'))
|
||||||
|
|
||||||
|
return None
|
||||||
|
|||||||
@@ -83,7 +83,9 @@ class Release(Plugin):
|
|||||||
|
|
||||||
fireEvent('movie.restatus', movie.id)
|
fireEvent('movie.restatus', movie.id)
|
||||||
|
|
||||||
db.remove()
|
db.close()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def saveFile(self, filepath, type = 'unknown', include_media_info = False):
|
def saveFile(self, filepath, type = 'unknown', include_media_info = False):
|
||||||
@@ -107,6 +109,7 @@ class Release(Plugin):
|
|||||||
rel.delete()
|
rel.delete()
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
|
db.close()
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True
|
'success': True
|
||||||
})
|
})
|
||||||
@@ -123,6 +126,7 @@ class Release(Plugin):
|
|||||||
rel.status_id = available_status.get('id') if rel.status_id is ignored_status.get('id') else ignored_status.get('id')
|
rel.status_id = available_status.get('id') if rel.status_id is ignored_status.get('id') else ignored_status.get('id')
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
|
db.close()
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True
|
'success': True
|
||||||
})
|
})
|
||||||
@@ -149,12 +153,14 @@ class Release(Plugin):
|
|||||||
'files': {}
|
'files': {}
|
||||||
}), manual = True)
|
}), manual = True)
|
||||||
|
|
||||||
|
db.close()
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True
|
'success': True
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
log.error('Couldn\'t find release with id: %s' % id)
|
log.error('Couldn\'t find release with id: %s' % id)
|
||||||
|
|
||||||
|
db.close()
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': False
|
'success': False
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from couchpotato.core.helpers.request import jsonified
|
|||||||
from couchpotato.core.helpers.variable import getExt, mergeDicts
|
from couchpotato.core.helpers.variable import getExt, mergeDicts
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
from couchpotato.core.settings.model import Library, File
|
from couchpotato.core.settings.model import Library, File, Profile
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@@ -67,6 +67,12 @@ class Renamer(Plugin):
|
|||||||
nfo_name = self.conf('nfo_name')
|
nfo_name = self.conf('nfo_name')
|
||||||
separator = self.conf('separator')
|
separator = self.conf('separator')
|
||||||
|
|
||||||
|
# Statusses
|
||||||
|
done_status = fireEvent('status.get', 'done', single = True)
|
||||||
|
active_status = fireEvent('status.get', 'active', single = True)
|
||||||
|
downloaded_status = fireEvent('status.get', 'downloaded', single = True)
|
||||||
|
snatched_status = fireEvent('status.get', 'snatched', single = True)
|
||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
|
|
||||||
for group_identifier in groups:
|
for group_identifier in groups:
|
||||||
@@ -185,7 +191,7 @@ class Renamer(Plugin):
|
|||||||
break
|
break
|
||||||
|
|
||||||
if not found:
|
if not found:
|
||||||
log.error('Could not determin dvd structure for: %s' % current_file)
|
log.error('Could not determine dvd structure for: %s' % current_file)
|
||||||
|
|
||||||
# Do rename others
|
# Do rename others
|
||||||
else:
|
else:
|
||||||
@@ -240,10 +246,15 @@ class Renamer(Plugin):
|
|||||||
cd += 1
|
cd += 1
|
||||||
|
|
||||||
# Before renaming, remove the lower quality files
|
# Before renaming, remove the lower quality files
|
||||||
|
|
||||||
library = db.query(Library).filter_by(identifier = group['library']['identifier']).first()
|
library = db.query(Library).filter_by(identifier = group['library']['identifier']).first()
|
||||||
done_status = fireEvent('status.get', 'done', single = True)
|
remove_leftovers = True
|
||||||
active_status = fireEvent('status.get', 'active', single = True)
|
|
||||||
|
# Add it to the wanted list before we continue
|
||||||
|
if len(library.movies) == 0:
|
||||||
|
profile = db.query(Profile).filter_by(core = True, label = group['meta_data']['quality']['label']).first()
|
||||||
|
fireEvent('movie.add', params = {'identifier': group['library']['identifier'], 'profile_id': profile.id}, search_after = False)
|
||||||
|
db.expire_all()
|
||||||
|
library = db.query(Library).filter_by(identifier = group['library']['identifier']).first()
|
||||||
|
|
||||||
for movie in library.movies:
|
for movie in library.movies:
|
||||||
|
|
||||||
@@ -293,14 +304,25 @@ class Renamer(Plugin):
|
|||||||
# Notify on rename fail
|
# Notify on rename fail
|
||||||
download_message = 'Renaming of %s (%s) canceled, exists in %s already.' % (movie.library.titles[0].title, group['meta_data']['quality']['label'], release.quality.label)
|
download_message = 'Renaming of %s (%s) canceled, exists in %s already.' % (movie.library.titles[0].title, group['meta_data']['quality']['label'], release.quality.label)
|
||||||
fireEvent('movie.renaming.canceled', message = download_message, data = group)
|
fireEvent('movie.renaming.canceled', message = download_message, data = group)
|
||||||
|
remove_leftovers = False
|
||||||
|
|
||||||
break
|
break
|
||||||
|
elif release.status_id is snatched_status.get('id'):
|
||||||
|
print release.quality.label, group['meta_data']['quality']['label']
|
||||||
|
if release.quality.id is group['meta_data']['quality']['id']:
|
||||||
|
log.debug('Marking release as downloaded')
|
||||||
|
release.status_id = downloaded_status.get('id')
|
||||||
|
db.commit()
|
||||||
|
|
||||||
# Remove leftover files
|
# Remove leftover files
|
||||||
if self.conf('cleanup') and not self.conf('move_leftover'):
|
if self.conf('cleanup') and not self.conf('move_leftover') and remove_leftovers:
|
||||||
log.debug('Removing leftover files')
|
log.debug('Removing leftover files')
|
||||||
for current_file in group['files']['leftover']:
|
for current_file in group['files']['leftover']:
|
||||||
remove_files.append(current_file)
|
remove_files.append(current_file)
|
||||||
|
elif not remove_leftovers: # Don't remove anything
|
||||||
|
remove_files = []
|
||||||
|
|
||||||
|
continue
|
||||||
|
|
||||||
# Rename all files marked
|
# Rename all files marked
|
||||||
group['renamed_files'] = []
|
group['renamed_files'] = []
|
||||||
@@ -356,6 +378,7 @@ class Renamer(Plugin):
|
|||||||
if self.shuttingDown():
|
if self.shuttingDown():
|
||||||
break
|
break
|
||||||
|
|
||||||
|
db.close()
|
||||||
self.renaming_started = False
|
self.renaming_started = False
|
||||||
|
|
||||||
def getRenameExtras(self, extra_type = '', replacements = {}, folder_name = '', file_name = '', destination = '', group = {}, current_file = ''):
|
def getRenameExtras(self, extra_type = '', replacements = {}, folder_name = '', file_name = '', destination = '', group = {}, current_file = ''):
|
||||||
|
|||||||
@@ -8,17 +8,13 @@ from couchpotato.core.settings.model import File
|
|||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
from enzyme.exceptions import NoParserError, ParseError
|
from enzyme.exceptions import NoParserError, ParseError
|
||||||
from guessit import guess_movie_info
|
from guessit import guess_movie_info
|
||||||
from subliminal.videos import scan
|
from subliminal.videos import scan, Video
|
||||||
import enzyme
|
import enzyme
|
||||||
import logging
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
enzyme_logger = logging.getLogger('enzyme')
|
|
||||||
enzyme_logger.setLevel(logging.INFO)
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -97,10 +93,6 @@ class Scanner(Plugin):
|
|||||||
|
|
||||||
addEvent('rename.after', after_rename)
|
addEvent('rename.after', after_rename)
|
||||||
|
|
||||||
# Disable lib logging
|
|
||||||
logging.getLogger('guessit').setLevel(logging.ERROR)
|
|
||||||
logging.getLogger('subliminal').setLevel(logging.ERROR)
|
|
||||||
|
|
||||||
def scanFilesToLibrary(self, folder = None, files = None):
|
def scanFilesToLibrary(self, folder = None, files = None):
|
||||||
|
|
||||||
groups = self.scan(folder = folder, files = files)
|
groups = self.scan(folder = folder, files = files)
|
||||||
@@ -109,12 +101,12 @@ class Scanner(Plugin):
|
|||||||
if group['library']:
|
if group['library']:
|
||||||
fireEvent('release.add', group = group)
|
fireEvent('release.add', group = group)
|
||||||
|
|
||||||
def scanFolderToLibrary(self, folder = None, newer_than = None):
|
def scanFolderToLibrary(self, folder = None, newer_than = None, simple = True):
|
||||||
|
|
||||||
if not os.path.isdir(folder):
|
if not os.path.isdir(folder):
|
||||||
return
|
return
|
||||||
|
|
||||||
groups = self.scan(folder = folder)
|
groups = self.scan(folder = folder, simple = simple)
|
||||||
|
|
||||||
added_identifier = []
|
added_identifier = []
|
||||||
while True and not self.shuttingDown():
|
while True and not self.shuttingDown():
|
||||||
@@ -135,7 +127,7 @@ class Scanner(Plugin):
|
|||||||
return added_identifier
|
return added_identifier
|
||||||
|
|
||||||
|
|
||||||
def scan(self, folder = None, files = []):
|
def scan(self, folder = None, files = [], simple = False):
|
||||||
|
|
||||||
if not folder or not os.path.isdir(folder):
|
if not folder or not os.path.isdir(folder):
|
||||||
log.error('Folder doesn\'t exists: %s' % folder)
|
log.error('Folder doesn\'t exists: %s' % folder)
|
||||||
@@ -299,7 +291,7 @@ class Scanner(Plugin):
|
|||||||
group['meta_data'] = self.getMetaData(group)
|
group['meta_data'] = self.getMetaData(group)
|
||||||
|
|
||||||
# Subtitle meta
|
# Subtitle meta
|
||||||
group['subtitle_language'] = self.getSubtitleLanguage(group)
|
group['subtitle_language'] = self.getSubtitleLanguage(group) if not simple else {}
|
||||||
|
|
||||||
# Get parent dir from movie files
|
# Get parent dir from movie files
|
||||||
for movie_file in group['files']['movie']:
|
for movie_file in group['files']['movie']:
|
||||||
@@ -328,7 +320,7 @@ class Scanner(Plugin):
|
|||||||
# Determine movie
|
# Determine movie
|
||||||
group['library'] = self.determineMovie(group)
|
group['library'] = self.determineMovie(group)
|
||||||
if not group['library']:
|
if not group['library']:
|
||||||
log.error('Unable to determin movie: %s' % group['identifiers'])
|
log.error('Unable to determine movie: %s' % group['identifiers'])
|
||||||
|
|
||||||
processed_movies[identifier] = group
|
processed_movies[identifier] = group
|
||||||
|
|
||||||
@@ -400,7 +392,9 @@ class Scanner(Plugin):
|
|||||||
scan_result = []
|
scan_result = []
|
||||||
for p in paths:
|
for p in paths:
|
||||||
if not group['is_dvd']:
|
if not group['is_dvd']:
|
||||||
scan_result.extend(scan(p))
|
video = Video.from_path(p)
|
||||||
|
video_result = [(video, video.scan())]
|
||||||
|
scan_result.extend(video_result)
|
||||||
|
|
||||||
for video, detected_subtitles in scan_result:
|
for video, detected_subtitles in scan_result:
|
||||||
for s in detected_subtitles:
|
for s in detected_subtitles:
|
||||||
@@ -461,7 +455,7 @@ class Scanner(Plugin):
|
|||||||
break
|
break
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
db.remove()
|
db.close()
|
||||||
|
|
||||||
# Search based on OpenSubtitleHash
|
# Search based on OpenSubtitleHash
|
||||||
if not imdb_id and not group['is_dvd']:
|
if not imdb_id and not group['is_dvd']:
|
||||||
@@ -482,7 +476,7 @@ class Scanner(Plugin):
|
|||||||
try: filename = list(group['files'].get('movie'))[0]
|
try: filename = list(group['files'].get('movie'))[0]
|
||||||
except: filename = None
|
except: filename = None
|
||||||
|
|
||||||
name_year = self.getReleaseNameYear(identifier, file_name = filename)
|
name_year = self.getReleaseNameYear(identifier, file_name = filename if not group['is_dvd'] else None)
|
||||||
if name_year.get('name') and name_year.get('year'):
|
if name_year.get('name') and name_year.get('year'):
|
||||||
movie = fireEvent('movie.search', q = '%(name)s %(year)s' % name_year, merge = True, limit = 1)
|
movie = fireEvent('movie.search', q = '%(name)s %(year)s' % name_year, merge = True, limit = 1)
|
||||||
|
|
||||||
|
|||||||
@@ -61,10 +61,17 @@ class Searcher(Plugin):
|
|||||||
if self.shuttingDown():
|
if self.shuttingDown():
|
||||||
break
|
break
|
||||||
|
|
||||||
|
db.close()
|
||||||
self.in_progress = False
|
self.in_progress = False
|
||||||
|
|
||||||
def single(self, movie):
|
def single(self, movie):
|
||||||
|
|
||||||
|
done_status = fireEvent('status.get', 'done', single = True)
|
||||||
|
|
||||||
|
if not movie['profile'] or movie['status_id'] == done_status.get('id'):
|
||||||
|
log.debug('Movie doesn\'t have a profile or already done, assuming in manage tab.')
|
||||||
|
return
|
||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
|
|
||||||
pre_releases = fireEvent('quality.pre_releases', single = True)
|
pre_releases = fireEvent('quality.pre_releases', single = True)
|
||||||
@@ -141,7 +148,7 @@ class Searcher(Plugin):
|
|||||||
if self.shuttingDown():
|
if self.shuttingDown():
|
||||||
break
|
break
|
||||||
|
|
||||||
db.remove()
|
db.close()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def download(self, data, movie, manual = False):
|
def download(self, data, movie, manual = False):
|
||||||
@@ -184,6 +191,7 @@ class Searcher(Plugin):
|
|||||||
except Exception, e:
|
except Exception, e:
|
||||||
log.error('Failed marking movie finished: %s %s' % (e, traceback.format_exc()))
|
log.error('Failed marking movie finished: %s %s' % (e, traceback.format_exc()))
|
||||||
|
|
||||||
|
db.close()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
log.info('Tried to download, but none of the downloaders are enabled')
|
log.info('Tried to download, but none of the downloaders are enabled')
|
||||||
|
|||||||
@@ -48,7 +48,10 @@ class StatusPlugin(Plugin):
|
|||||||
def getById(self, id):
|
def getById(self, id):
|
||||||
db = get_session()
|
db = get_session()
|
||||||
status = db.query(Status).filter_by(id = id).first()
|
status = db.query(Status).filter_by(id = id).first()
|
||||||
return status.to_dict()
|
status_dict = status.to_dict()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
return status_dict
|
||||||
|
|
||||||
def all(self):
|
def all(self):
|
||||||
|
|
||||||
@@ -61,6 +64,7 @@ class StatusPlugin(Plugin):
|
|||||||
s = status.to_dict()
|
s = status.to_dict()
|
||||||
temp.append(s)
|
temp.append(s)
|
||||||
|
|
||||||
|
db.close()
|
||||||
return temp
|
return temp
|
||||||
|
|
||||||
def add(self, identifier):
|
def add(self, identifier):
|
||||||
@@ -78,6 +82,7 @@ class StatusPlugin(Plugin):
|
|||||||
|
|
||||||
status_dict = s.to_dict()
|
status_dict = s.to_dict()
|
||||||
|
|
||||||
|
db.close()
|
||||||
return status_dict
|
return status_dict
|
||||||
|
|
||||||
def fill(self):
|
def fill(self):
|
||||||
@@ -97,3 +102,5 @@ class StatusPlugin(Plugin):
|
|||||||
s.label = toUnicode(label)
|
s.label = toUnicode(label)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ class Subtitle(Plugin):
|
|||||||
# get subtitles for those files
|
# get subtitles for those files
|
||||||
subliminal.list_subtitles(files, cache_dir = Env.get('cache_dir'), multi = True, languages = self.getLanguages(), services = self.services)
|
subliminal.list_subtitles(files, cache_dir = Env.get('cache_dir'), multi = True, languages = self.getLanguages(), services = self.services)
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
|
||||||
def searchSingle(self, group):
|
def searchSingle(self, group):
|
||||||
|
|
||||||
if self.isDisabled(): return
|
if self.isDisabled(): return
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
|
|
||||||
<script type="text/javascript" src="{{ url_for('web.static', filename='scripts/library/mootools.js') }}"></script>
|
|
||||||
<script type="text/javascript" src="{{ url_for('web.static', filename='scripts/library/mootools_more.js') }}"></script>
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
|
||||||
{{url}}
|
|
||||||
console.log('test');
|
|
||||||
Api.request('')
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body></body>
|
|
||||||
</html>
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
from .main import V1Importer
|
||||||
|
|
||||||
|
def start():
|
||||||
|
return V1Importer()
|
||||||
|
|
||||||
|
config = []
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('web.static', filename='style/main.css') }}" type="text/css">
|
||||||
|
<link rel="stylesheet" href="{{ url_for('web.static', filename='style/uniform.generic.css') }}" type="text/css">
|
||||||
|
<link rel="stylesheet" href="{{ url_for('web.static', filename='style/uniform.css') }}" type="text/css">
|
||||||
|
|
||||||
|
<script type="text/javascript" src="{{ url_for('web.static', filename='scripts/library/mootools.js') }}"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
window.addEvent('domready', function(){
|
||||||
|
if($('old_db'))
|
||||||
|
$('old_db').addEvent('change', function(){
|
||||||
|
$('form').submit();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% if message: %}
|
||||||
|
{{ message }}
|
||||||
|
{% else: %}
|
||||||
|
<form id="form" method="post" enctype="multipart/form-data">
|
||||||
|
<input type="file" name="old_db" id="old_db" />
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
from couchpotato.api import addApiView
|
||||||
|
from couchpotato.core.event import fireEventAsync
|
||||||
|
from couchpotato.core.helpers.variable import getImdb
|
||||||
|
from couchpotato.core.logger import CPLog
|
||||||
|
from couchpotato.core.plugins.base import Plugin
|
||||||
|
from couchpotato.environment import Env
|
||||||
|
from flask.globals import request
|
||||||
|
from flask.helpers import url_for
|
||||||
|
import os
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class V1Importer(Plugin):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
addApiView('v1.import', self.fromOld, methods = ['GET', 'POST'])
|
||||||
|
|
||||||
|
def fromOld(self):
|
||||||
|
|
||||||
|
if request.method != 'POST':
|
||||||
|
return self.renderTemplate(__file__, 'form.html', url_for = url_for)
|
||||||
|
|
||||||
|
file = request.files['old_db']
|
||||||
|
|
||||||
|
uploaded_file = os.path.join(Env.get('cache_dir'), 'v1_database.db')
|
||||||
|
|
||||||
|
if os.path.isfile(uploaded_file):
|
||||||
|
os.remove(uploaded_file)
|
||||||
|
|
||||||
|
file.save(uploaded_file)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import sqlite3
|
||||||
|
conn = sqlite3.connect(uploaded_file)
|
||||||
|
|
||||||
|
wanted = []
|
||||||
|
|
||||||
|
t = ('want',)
|
||||||
|
cur = conn.execute('SELECT status, imdb FROM Movie WHERE status=?', t)
|
||||||
|
for row in cur:
|
||||||
|
status, imdb = row
|
||||||
|
if getImdb(imdb):
|
||||||
|
wanted.append(imdb)
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
wanted = set(wanted)
|
||||||
|
for imdb in wanted:
|
||||||
|
fireEventAsync('movie.add', {'identifier': imdb}, search_after = False)
|
||||||
|
|
||||||
|
message = 'Successfully imported %s movie(s)' % len(wanted)
|
||||||
|
except Exception, e:
|
||||||
|
message = 'Failed: %s' % e
|
||||||
|
|
||||||
|
return self.renderTemplate(__file__, 'form.html', url_for = url_for, message = message)
|
||||||
|
|
||||||
@@ -8,8 +8,28 @@ Page.Wizard = new Class({
|
|||||||
|
|
||||||
headers: {
|
headers: {
|
||||||
'welcome': {
|
'welcome': {
|
||||||
'title': 'Welcome to CouchPotato',
|
'title': 'Welcome to the new CouchPotato',
|
||||||
'description': 'To get started, fill in each of the following settings as much as your can.'
|
'description': 'To get started, fill in each of the following settings as much as your can. <br />Maybe first start with importing your movies from the previous CouchPotato',
|
||||||
|
'content': new Element('div', {
|
||||||
|
'styles': {
|
||||||
|
'margin': '0 0 0 30px'
|
||||||
|
}
|
||||||
|
}).adopt(
|
||||||
|
new Element('div', {
|
||||||
|
'html': 'Select the <strong>data.db</strong>. It should be in your CouchPotato root directory.'
|
||||||
|
}),
|
||||||
|
self.import_iframe = new Element('iframe', {
|
||||||
|
'styles': {
|
||||||
|
'height': 40,
|
||||||
|
'width': 300,
|
||||||
|
'border': 0,
|
||||||
|
'overflow': 'hidden'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
),
|
||||||
|
'event': function(){
|
||||||
|
self.import_iframe.set('src', Api.createUrl('v1.import'))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
'general': {
|
'general': {
|
||||||
'title': 'General',
|
'title': 'General',
|
||||||
@@ -105,7 +125,7 @@ Page.Wizard = new Class({
|
|||||||
'text': self.headers[group].title
|
'text': self.headers[group].title
|
||||||
}),
|
}),
|
||||||
self.headers[group].description ? new Element('span.description', {
|
self.headers[group].description ? new Element('span.description', {
|
||||||
'text': self.headers[group].description
|
'html': self.headers[group].description
|
||||||
}) : null,
|
}) : null,
|
||||||
self.headers[group].content ? self.headers[group].content : null
|
self.headers[group].content ? self.headers[group].content : null
|
||||||
).inject(form);
|
).inject(form);
|
||||||
@@ -132,6 +152,9 @@ Page.Wizard = new Class({
|
|||||||
})
|
})
|
||||||
).inject(tabs);
|
).inject(tabs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(self.headers[group] && self.headers[group].event)
|
||||||
|
self.headers[group].event.call()
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remove toggle
|
// Remove toggle
|
||||||
|
|||||||
@@ -25,6 +25,12 @@ config = [{
|
|||||||
'name': 'automation_username',
|
'name': 'automation_username',
|
||||||
'label': 'Username',
|
'label': 'Username',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'automation_password',
|
||||||
|
'label': 'Password',
|
||||||
|
'type': 'password',
|
||||||
|
'description': 'When you have "Protect my data" checked <a href="http://trakt.tv/settings/account">on trakt</a>.',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
from couchpotato.core.helpers.variable import md5
|
from couchpotato.core.event import addEvent
|
||||||
|
from couchpotato.core.helpers.variable import md5, sha1
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.automation.base import Automation
|
from couchpotato.core.providers.automation.base import Automation
|
||||||
|
import base64
|
||||||
import json
|
import json
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
@@ -13,6 +15,14 @@ class Trakt(Automation):
|
|||||||
'watchlist': 'user/watchlist/movies.json/%s/',
|
'watchlist': 'user/watchlist/movies.json/%s/',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(Trakt, self).__init__()
|
||||||
|
|
||||||
|
addEvent('setting.save.trakt.automation_password', self.sha1Password)
|
||||||
|
|
||||||
|
def sha1Password(self, value):
|
||||||
|
return sha1(value) if value else ''
|
||||||
|
|
||||||
def getIMDBids(self):
|
def getIMDBids(self):
|
||||||
|
|
||||||
if self.isDisabled():
|
if self.isDisabled():
|
||||||
@@ -31,6 +41,13 @@ class Trakt(Automation):
|
|||||||
|
|
||||||
def call(self, method_url):
|
def call(self, method_url):
|
||||||
|
|
||||||
|
if self.conf('automation_password'):
|
||||||
|
headers = {
|
||||||
|
'Authorization': "Basic %s" % base64.encodestring('%s:%s' % (self.conf('automation_username'), self.conf('automation_password')))[:-1]
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
headers = {}
|
||||||
|
|
||||||
cache_key = 'trakt.%s' % md5(method_url)
|
cache_key = 'trakt.%s' % md5(method_url)
|
||||||
json_string = self.getCache(cache_key, self.urls['base'] + method_url)
|
json_string = self.getCache(cache_key, self.urls['base'] + method_url, headers = headers)
|
||||||
return json.loads(json_string)
|
return json.loads(json_string)
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ class MovieResultModifier(Plugin):
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Add release info from current library
|
# Add release info from current library
|
||||||
|
db = get_session()
|
||||||
try:
|
try:
|
||||||
db = get_session()
|
|
||||||
l = db.query(Library).filter_by(identifier = imdb).first()
|
l = db.query(Library).filter_by(identifier = imdb).first()
|
||||||
if l:
|
if l:
|
||||||
|
|
||||||
@@ -63,6 +63,7 @@ class MovieResultModifier(Plugin):
|
|||||||
except:
|
except:
|
||||||
log.error('Tried getting more info on searched movies: %s' % traceback.format_exc())
|
log.error('Tried getting more info on searched movies: %s' % traceback.format_exc())
|
||||||
|
|
||||||
|
db.close()
|
||||||
return temp
|
return temp
|
||||||
|
|
||||||
def checkLibrary(self, result):
|
def checkLibrary(self, result):
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ class CouchPotatoApi(MovieProvider):
|
|||||||
db = get_session()
|
db = get_session()
|
||||||
active_movies = db.query(Movie).filter(Movie.status.has(identifier = 'active')).all()
|
active_movies = db.query(Movie).filter(Movie.status.has(identifier = 'active')).all()
|
||||||
movies = [x.library.identifier for x in active_movies]
|
movies = [x.library.identifier for x in active_movies]
|
||||||
|
db.close()
|
||||||
|
|
||||||
suggestions = self.suggest(movies, ignore)
|
suggestions = self.suggest(movies, ignore)
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from couchpotato.core.helpers.rss import RSS
|
|||||||
from couchpotato.core.helpers.variable import cleanHost
|
from couchpotato.core.helpers.variable import cleanHost
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.nzb.base import NZBProvider
|
from couchpotato.core.providers.nzb.base import NZBProvider
|
||||||
|
from couchpotato.environment import Env
|
||||||
from dateutil.parser import parse
|
from dateutil.parser import parse
|
||||||
import time
|
import time
|
||||||
import xml.etree.ElementTree as XMLTree
|
import xml.etree.ElementTree as XMLTree
|
||||||
@@ -99,7 +100,7 @@ class Newznab(NZBProvider, RSS):
|
|||||||
def createItems(self, url, cache_key, host, single_cat = False, movie = None, quality = None, for_feed = False):
|
def createItems(self, url, cache_key, host, single_cat = False, movie = None, quality = None, for_feed = False):
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
data = self.getCache(cache_key, url)
|
data = self.getCache(cache_key, url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()})
|
||||||
if data:
|
if data:
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ class NZBMatrix(NZBProvider, RSS):
|
|||||||
cache_key = 'nzbmatrix.%s.%s' % (movie['library'].get('identifier'), cat_ids)
|
cache_key = 'nzbmatrix.%s.%s' % (movie['library'].get('identifier'), cat_ids)
|
||||||
single_cat = True
|
single_cat = True
|
||||||
|
|
||||||
data = self.getCache(cache_key, url, cache_timeout = 1800, headers = {'User-Agent': 'CouchPotato'})
|
data = self.getCache(cache_key, url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()})
|
||||||
if data:
|
if data:
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -23,8 +23,10 @@ class HDTrailers(TrailerProvider):
|
|||||||
|
|
||||||
url = self.urls['api'] % self.movieUrlName(movie_name)
|
url = self.urls['api'] % self.movieUrlName(movie_name)
|
||||||
data = self.getCache('hdtrailers.%s' % group['library']['identifier'], url)
|
data = self.getCache('hdtrailers.%s' % group['library']['identifier'], url)
|
||||||
|
result_data = {'480p':[], '720p':[], '1080p':[]}
|
||||||
|
|
||||||
result_data = {}
|
if not data:
|
||||||
|
return result_data
|
||||||
|
|
||||||
did_alternative = False
|
did_alternative = False
|
||||||
for provider in self.providers:
|
for provider in self.providers:
|
||||||
|
|||||||
@@ -197,11 +197,15 @@ class Settings(object):
|
|||||||
from couchpotato import get_session
|
from couchpotato import get_session
|
||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
|
prop = None
|
||||||
try:
|
try:
|
||||||
prop = db.query(Properties).filter_by(identifier = identifier).first()
|
propert = db.query(Properties).filter_by(identifier = identifier).first()
|
||||||
return prop.value if prop else None
|
prop = propert.value
|
||||||
except:
|
except:
|
||||||
return None
|
pass
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
return prop
|
||||||
|
|
||||||
def setProperty(self, identifier, value = ''):
|
def setProperty(self, identifier, value = ''):
|
||||||
from couchpotato import get_session
|
from couchpotato import get_session
|
||||||
@@ -217,3 +221,4 @@ class Settings(object):
|
|||||||
p.value = toUnicode(value)
|
p.value = toUnicode(value)
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
|
db.close()
|
||||||
|
|||||||
@@ -238,7 +238,15 @@ class Properties(Entity):
|
|||||||
def setup():
|
def setup():
|
||||||
"""Setup the database and create the tables that don't exists yet"""
|
"""Setup the database and create the tables that don't exists yet"""
|
||||||
from elixir import setup_all, create_all
|
from elixir import setup_all, create_all
|
||||||
from couchpotato import get_engine
|
from couchpotato.environment import Env
|
||||||
|
|
||||||
|
engine = Env.getEngine()
|
||||||
|
|
||||||
setup_all()
|
setup_all()
|
||||||
create_all(get_engine())
|
create_all(engine)
|
||||||
|
|
||||||
|
try:
|
||||||
|
engine.execute("PRAGMA journal_mode = WAL")
|
||||||
|
engine.execute("PRAGMA temp_store = MEMORY")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
from couchpotato.core.event import fireEvent, addEvent
|
from couchpotato.core.event import fireEvent, addEvent
|
||||||
from couchpotato.core.loader import Loader
|
from couchpotato.core.loader import Loader
|
||||||
from couchpotato.core.settings import Settings
|
from couchpotato.core.settings import Settings
|
||||||
|
from sqlalchemy.engine import create_engine
|
||||||
|
from sqlalchemy.orm import scoped_session
|
||||||
|
from sqlalchemy.orm.session import sessionmaker
|
||||||
import os
|
import os
|
||||||
|
|
||||||
class Env(object):
|
class Env(object):
|
||||||
|
|
||||||
|
_appname = 'CouchPotato'
|
||||||
|
|
||||||
''' Environment variables '''
|
''' Environment variables '''
|
||||||
_encoding = ''
|
_encoding = ''
|
||||||
_uses_git = False
|
_uses_git = False
|
||||||
@@ -18,6 +23,7 @@ class Env(object):
|
|||||||
_quiet = False
|
_quiet = False
|
||||||
_deamonize = False
|
_deamonize = False
|
||||||
_desktop = None
|
_desktop = None
|
||||||
|
_session = None
|
||||||
|
|
||||||
''' Data paths and directories '''
|
''' Data paths and directories '''
|
||||||
_app_dir = ""
|
_app_dir = ""
|
||||||
@@ -46,6 +52,22 @@ class Env(object):
|
|||||||
def set(attr, value):
|
def set(attr, value):
|
||||||
return setattr(Env, '_' + attr, value)
|
return setattr(Env, '_' + attr, value)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def getSession(engine = None):
|
||||||
|
existing_session = Env.get('session')
|
||||||
|
if existing_session:
|
||||||
|
return existing_session
|
||||||
|
|
||||||
|
engine = Env.getEngine()
|
||||||
|
session = scoped_session(sessionmaker(bind = engine))
|
||||||
|
Env.set('session', session)
|
||||||
|
|
||||||
|
return session
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def getEngine():
|
||||||
|
return create_engine(Env.get('db_path'), echo = False)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def setting(attr, section = 'core', value = None, default = '', type = None):
|
def setting(attr, section = 'core', value = None, default = '', type = None):
|
||||||
|
|
||||||
@@ -96,3 +118,7 @@ class Env(object):
|
|||||||
return '%d %s' % (os.getpid(), '(%d)' % parent if parent and parent > 1 else '')
|
return '%d %s' % (os.getpid(), '(%d)' % parent if parent and parent > 1 else '')
|
||||||
except:
|
except:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def getIdentifier():
|
||||||
|
return '%s %s' % (Env.get('appname'), fireEvent('app.version', single = True))
|
||||||
|
|||||||
+42
-6
@@ -9,6 +9,7 @@ import atexit
|
|||||||
import locale
|
import locale
|
||||||
import logging
|
import logging
|
||||||
import os.path
|
import os.path
|
||||||
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import warnings
|
import warnings
|
||||||
@@ -58,13 +59,45 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
|
|||||||
if not encoding or encoding in ('ANSI_X3.4-1968', 'US-ASCII', 'ASCII'):
|
if not encoding or encoding in ('ANSI_X3.4-1968', 'US-ASCII', 'ASCII'):
|
||||||
encoding = 'UTF-8'
|
encoding = 'UTF-8'
|
||||||
|
|
||||||
|
# Do db stuff
|
||||||
|
db_path = os.path.join(data_dir, 'couchpotato.db')
|
||||||
|
|
||||||
|
# Backup before start and cleanup old databases
|
||||||
|
new_backup = os.path.join(data_dir, 'db_backup', str(int(time.time())))
|
||||||
|
|
||||||
|
# Create path and copy
|
||||||
|
if not os.path.isdir(new_backup): os.makedirs(new_backup)
|
||||||
|
src_files = [options.config_file, db_path, db_path + '-shm', db_path + '-wal']
|
||||||
|
for src_file in src_files:
|
||||||
|
if os.path.isfile(src_file):
|
||||||
|
shutil.copy2(src_file, os.path.join(new_backup, os.path.basename(src_file)))
|
||||||
|
|
||||||
|
# Remove older backups, keep backups 3 days or at least 3
|
||||||
|
backups = []
|
||||||
|
for directory in os.listdir(os.path.dirname(new_backup)):
|
||||||
|
backup = os.path.join(os.path.dirname(new_backup), directory)
|
||||||
|
if os.path.isdir(backup):
|
||||||
|
backups.append(backup)
|
||||||
|
|
||||||
|
total_backups = len(backups)
|
||||||
|
for backup in backups:
|
||||||
|
if total_backups > 3:
|
||||||
|
if int(os.path.basename(backup)) < time.time() - 259200:
|
||||||
|
for src_file in src_files:
|
||||||
|
b_file = os.path.join(backup, os.path.basename(src_file))
|
||||||
|
if os.path.isfile(b_file):
|
||||||
|
os.remove(b_file)
|
||||||
|
os.rmdir(backup)
|
||||||
|
total_backups -= 1
|
||||||
|
|
||||||
|
|
||||||
# Register environment settings
|
# Register environment settings
|
||||||
Env.set('encoding', encoding)
|
Env.set('encoding', encoding)
|
||||||
Env.set('uses_git', not options.nogit)
|
Env.set('uses_git', not options.nogit)
|
||||||
Env.set('app_dir', base_path)
|
Env.set('app_dir', base_path)
|
||||||
Env.set('data_dir', data_dir)
|
Env.set('data_dir', data_dir)
|
||||||
Env.set('log_path', os.path.join(log_dir, 'CouchPotato.log'))
|
Env.set('log_path', os.path.join(log_dir, 'CouchPotato.log'))
|
||||||
Env.set('db_path', 'sqlite:///' + os.path.join(data_dir, 'couchpotato.db'))
|
Env.set('db_path', 'sqlite:///' + db_path)
|
||||||
Env.set('cache_dir', os.path.join(data_dir, 'cache'))
|
Env.set('cache_dir', os.path.join(data_dir, 'cache'))
|
||||||
Env.set('cache', FileSystemCache(os.path.join(Env.get('cache_dir'), 'python')))
|
Env.set('cache', FileSystemCache(os.path.join(Env.get('cache_dir'), 'python')))
|
||||||
Env.set('console_log', options.console_log)
|
Env.set('console_log', options.console_log)
|
||||||
@@ -83,12 +116,16 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
|
|||||||
if not development:
|
if not development:
|
||||||
atexit.register(cleanup)
|
atexit.register(cleanup)
|
||||||
|
|
||||||
|
# Disable logging for some modules
|
||||||
|
for logger_name in ['enzyme', 'guessit', 'subliminal', 'apscheduler']:
|
||||||
|
logging.getLogger(logger_name).setLevel(logging.ERROR)
|
||||||
|
|
||||||
|
for logger_name in ['gntp', 'werkzeug', 'migrate']:
|
||||||
|
logging.getLogger(logger_name).setLevel(logging.WARNING)
|
||||||
|
|
||||||
# Use reloader
|
# Use reloader
|
||||||
reloader = debug is True and development and not Env.get('desktop') and not options.daemon
|
reloader = debug is True and development and not Env.get('desktop') and not options.daemon
|
||||||
|
|
||||||
# Disable server access log
|
|
||||||
logging.getLogger('werkzeug').setLevel(logging.WARNING)
|
|
||||||
|
|
||||||
# Only run once when debugging
|
# Only run once when debugging
|
||||||
fire_load = False
|
fire_load = False
|
||||||
if os.environ.get('WERKZEUG_RUN_MAIN') or not reloader:
|
if os.environ.get('WERKZEUG_RUN_MAIN') or not reloader:
|
||||||
@@ -130,12 +167,11 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
|
|||||||
# Load migrations
|
# Load migrations
|
||||||
initialize = True
|
initialize = True
|
||||||
db = Env.get('db_path')
|
db = Env.get('db_path')
|
||||||
if os.path.isfile(db.replace('sqlite:///', '')):
|
if os.path.isfile(db_path):
|
||||||
initialize = False
|
initialize = False
|
||||||
|
|
||||||
from migrate.versioning.api import version_control, db_version, version, upgrade
|
from migrate.versioning.api import version_control, db_version, version, upgrade
|
||||||
repo = os.path.join(base_path, 'couchpotato', 'core', 'migration')
|
repo = os.path.join(base_path, 'couchpotato', 'core', 'migration')
|
||||||
logging.getLogger('migrate').setLevel(logging.WARNING) # Disable logging for migration
|
|
||||||
|
|
||||||
latest_db_version = version(repo)
|
latest_db_version = version(repo)
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -84,6 +84,10 @@ var CouchPotato = new Class({
|
|||||||
'events': {
|
'events': {
|
||||||
'click': self.checkForUpdate.bind(self)
|
'click': self.checkForUpdate.bind(self)
|
||||||
}
|
}
|
||||||
|
}),
|
||||||
|
new Element('a', {
|
||||||
|
'text': 'Run install wizard',
|
||||||
|
'href': App.createUrl('wizard')
|
||||||
})].each(function(a){
|
})].each(function(a){
|
||||||
self.block.more.addLink(a)
|
self.block.more.addLink(a)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -70,10 +70,16 @@ Page.Settings = new Class({
|
|||||||
t.tab[a](c);
|
t.tab[a](c);
|
||||||
t.subtabs[subtab].tab[a](c);
|
t.subtabs[subtab].tab[a](c);
|
||||||
t.subtabs[subtab].content[a](c);
|
t.subtabs[subtab].content[a](c);
|
||||||
|
|
||||||
|
if(!hide)
|
||||||
|
t.subtabs[subtab].content.fireEvent('activate');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
t.tab[a](c);
|
t.tab[a](c);
|
||||||
t.content[a](c);
|
t.content[a](c);
|
||||||
|
|
||||||
|
if(!hide)
|
||||||
|
t.content.fireEvent('activate');
|
||||||
}
|
}
|
||||||
|
|
||||||
return t
|
return t
|
||||||
@@ -869,6 +875,7 @@ Option.Choice = new Class({
|
|||||||
afterInject: function(){
|
afterInject: function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
self.tags = [];
|
||||||
self.replaceInput();
|
self.replaceInput();
|
||||||
|
|
||||||
self.select = new Element('select').adopt(
|
self.select = new Element('select').adopt(
|
||||||
@@ -941,6 +948,10 @@ Option.Choice = new Class({
|
|||||||
self.reset();
|
self.reset();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Calc width on show
|
||||||
|
var input_group = self.tag_input.getParent('.tab_content');
|
||||||
|
input_group.addEvent('activate', self.setAllWidth.bind(self));
|
||||||
},
|
},
|
||||||
|
|
||||||
addLastTag: function(){
|
addLastTag: function(){
|
||||||
@@ -952,10 +963,8 @@ Option.Choice = new Class({
|
|||||||
var self = this;
|
var self = this;
|
||||||
tag = new Option.Choice.Tag(tag, {
|
tag = new Option.Choice.Tag(tag, {
|
||||||
'onChange': self.setOrder.bind(self),
|
'onChange': self.setOrder.bind(self),
|
||||||
'onFocus': self.activate.bind(self),
|
|
||||||
'onBlur': function(){
|
'onBlur': function(){
|
||||||
self.addLastTag();
|
self.addLastTag();
|
||||||
self.deactivate();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$(tag).inject(self.tag_input);
|
$(tag).inject(self.tag_input);
|
||||||
@@ -965,6 +974,8 @@ Option.Choice = new Class({
|
|||||||
else
|
else
|
||||||
(function(){ tag.setWidth(); }).delay(10, self);
|
(function(){ tag.setWidth(); }).delay(10, self);
|
||||||
|
|
||||||
|
self.tags.include(tag);
|
||||||
|
|
||||||
return tag;
|
return tag;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -979,6 +990,7 @@ Option.Choice = new Class({
|
|||||||
|
|
||||||
self.input.set('value', value);
|
self.input.set('value', value);
|
||||||
self.input.fireEvent('change');
|
self.input.fireEvent('change');
|
||||||
|
self.setAllWidth();
|
||||||
},
|
},
|
||||||
|
|
||||||
addSelection: function(){
|
addSelection: function(){
|
||||||
@@ -987,6 +999,7 @@ Option.Choice = new Class({
|
|||||||
var tag = self.addTag(self.el.getElement('.selection input').get('value'));
|
var tag = self.addTag(self.el.getElement('.selection input').get('value'));
|
||||||
self.sortable.addItems($(tag));
|
self.sortable.addItems($(tag));
|
||||||
self.setOrder();
|
self.setOrder();
|
||||||
|
self.setAllWidth();
|
||||||
},
|
},
|
||||||
|
|
||||||
reset: function(){
|
reset: function(){
|
||||||
@@ -996,14 +1009,14 @@ Option.Choice = new Class({
|
|||||||
self.sortable.detach();
|
self.sortable.detach();
|
||||||
|
|
||||||
self.replaceInput();
|
self.replaceInput();
|
||||||
|
self.setAllWidth();
|
||||||
},
|
},
|
||||||
|
|
||||||
activate: function(){
|
setAllWidth: function(){
|
||||||
|
var self = this;
|
||||||
},
|
self.tags.each(function(tag){
|
||||||
|
tag.setWidth.delay(10, tag);
|
||||||
deactivate: function(){
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -1032,6 +1045,9 @@ Option.Choice.Tag = new Class({
|
|||||||
|
|
||||||
self.el = new Element('li', {
|
self.el = new Element('li', {
|
||||||
'class': self.is_choice ? 'choice' : '',
|
'class': self.is_choice ? 'choice' : '',
|
||||||
|
'styles': {
|
||||||
|
'border': 0
|
||||||
|
},
|
||||||
'events': {
|
'events': {
|
||||||
'mouseover': !self.is_choice ? self.fireEvent.bind(self, 'focus') : function(){}
|
'mouseover': !self.is_choice ? self.fireEvent.bind(self, 'focus') : function(){}
|
||||||
}
|
}
|
||||||
@@ -1039,6 +1055,9 @@ Option.Choice.Tag = new Class({
|
|||||||
self.input = new Element(self.is_choice ? 'span' : 'input', {
|
self.input = new Element(self.is_choice ? 'span' : 'input', {
|
||||||
'text': self.tag,
|
'text': self.tag,
|
||||||
'value': self.tag,
|
'value': self.tag,
|
||||||
|
'styles': {
|
||||||
|
'width': 0
|
||||||
|
},
|
||||||
'events': {
|
'events': {
|
||||||
'keyup': self.is_choice ? null : function(){
|
'keyup': self.is_choice ? null : function(){
|
||||||
self.setWidth();
|
self.setWidth();
|
||||||
|
|||||||
@@ -211,6 +211,7 @@ window.addEvent('domready', function(){
|
|||||||
},
|
},
|
||||||
'onComplete': function(){
|
'onComplete': function(){
|
||||||
movie.set('tween', {
|
movie.set('tween', {
|
||||||
|
'duration': 300,
|
||||||
'onComplete': function(){
|
'onComplete': function(){
|
||||||
movie.destroy();
|
movie.destroy();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -362,28 +362,28 @@
|
|||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
.page .tag_input > ul:hover > li.choice {
|
.page .tag_input > ul:hover > li.choice {
|
||||||
background: url('../images/sprite.png') no-repeat 94% -53px, -webkit-gradient(
|
background: url('../../images/sprite.png') no-repeat 94% -53px, -webkit-gradient(
|
||||||
linear,
|
linear,
|
||||||
left bottom,
|
left bottom,
|
||||||
left top,
|
left top,
|
||||||
color-stop(0, rgba(255,255,255,0.1)),
|
color-stop(0, rgba(255,255,255,0.1)),
|
||||||
color-stop(1, rgba(255,255,255,0.3))
|
color-stop(1, rgba(255,255,255,0.3))
|
||||||
);
|
);
|
||||||
background: url('../images/sprite.png') no-repeat 94% -53px, -moz-linear-gradient(
|
background: url('../../images/sprite.png') no-repeat 94% -53px, -moz-linear-gradient(
|
||||||
center top,
|
center top,
|
||||||
rgba(255,255,255,0.3) 0%,
|
rgba(255,255,255,0.3) 0%,
|
||||||
rgba(255,255,255,0.1) 100%
|
rgba(255,255,255,0.1) 100%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
.page .tag_input > ul > li.choice:hover {
|
.page .tag_input > ul > li.choice:hover {
|
||||||
background: url('../images/sprite.png') no-repeat 94% -53px, -webkit-gradient(
|
background: url('../../images/sprite.png') no-repeat 94% -53px, -webkit-gradient(
|
||||||
linear,
|
linear,
|
||||||
left bottom,
|
left bottom,
|
||||||
left top,
|
left top,
|
||||||
color-stop(0, #406db8),
|
color-stop(0, #406db8),
|
||||||
color-stop(1, #5b9bd1)
|
color-stop(1, #5b9bd1)
|
||||||
);
|
);
|
||||||
background: url('../images/sprite.png') no-repeat 94% -53px, -moz-linear-gradient(
|
background: url('../../images/sprite.png') no-repeat 94% -53px, -moz-linear-gradient(
|
||||||
center top,
|
center top,
|
||||||
#5b9bd1 0%,
|
#5b9bd1 0%,
|
||||||
#406db8 100%
|
#406db8 100%
|
||||||
|
|||||||
+6
-1
@@ -164,7 +164,12 @@ class Event(object):
|
|||||||
if not self.asynchronous:
|
if not self.asynchronous:
|
||||||
self.queue.join()
|
self.queue.join()
|
||||||
|
|
||||||
return self.result or None
|
res = self.result or None
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
self.result = {}
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
def count(self):
|
def count(self):
|
||||||
""" Returns the count of registered handlers """
|
""" Returns the count of registered handlers """
|
||||||
|
|||||||
@@ -0,0 +1,249 @@
|
|||||||
|
Afghanistan|AF|AFG|004|ISO 3166-2:AF
|
||||||
|
Åland Islands|AX|ALA|248|ISO 3166-2:AX
|
||||||
|
Albania|AL|ALB|008|ISO 3166-2:AL
|
||||||
|
Algeria|DZ|DZA|012|ISO 3166-2:DZ
|
||||||
|
American Samoa|AS|ASM|016|ISO 3166-2:AS
|
||||||
|
Andorra|AD|AND|020|ISO 3166-2:AD
|
||||||
|
Angola|AO|AGO|024|ISO 3166-2:AO
|
||||||
|
Anguilla|AI|AIA|660|ISO 3166-2:AI
|
||||||
|
Antarctica|AQ|ATA|010|ISO 3166-2:AQ
|
||||||
|
Antigua and Barbuda|AG|ATG|028|ISO 3166-2:AG
|
||||||
|
Argentina|AR|ARG|032|ISO 3166-2:AR
|
||||||
|
Armenia|AM|ARM|051|ISO 3166-2:AM
|
||||||
|
Aruba|AW|ABW|533|ISO 3166-2:AW
|
||||||
|
Australia|AU|AUS|036|ISO 3166-2:AU
|
||||||
|
Austria|AT|AUT|040|ISO 3166-2:AT
|
||||||
|
Azerbaijan|AZ|AZE|031|ISO 3166-2:AZ
|
||||||
|
Bahamas|BS|BHS|044|ISO 3166-2:BS
|
||||||
|
Bahrain|BH|BHR|048|ISO 3166-2:BH
|
||||||
|
Bangladesh|BD|BGD|050|ISO 3166-2:BD
|
||||||
|
Barbados|BB|BRB|052|ISO 3166-2:BB
|
||||||
|
Belarus|BY|BLR|112|ISO 3166-2:BY
|
||||||
|
Belgium|BE|BEL|056|ISO 3166-2:BE
|
||||||
|
Belize|BZ|BLZ|084|ISO 3166-2:BZ
|
||||||
|
Benin|BJ|BEN|204|ISO 3166-2:BJ
|
||||||
|
Bermuda|BM|BMU|060|ISO 3166-2:BM
|
||||||
|
Bhutan|BT|BTN|064|ISO 3166-2:BT
|
||||||
|
Bolivia, Plurinational State of|BO|BOL|068|ISO 3166-2:BO
|
||||||
|
Bonaire, Sint Eustatius and Saba|BQ|BES|535|ISO 3166-2:BQ
|
||||||
|
Bosnia and Herzegovina|BA|BIH|070|ISO 3166-2:BA
|
||||||
|
Botswana|BW|BWA|072|ISO 3166-2:BW
|
||||||
|
Bouvet Island|BV|BVT|074|ISO 3166-2:BV
|
||||||
|
Brazil|BR|BRA|076|ISO 3166-2:BR
|
||||||
|
British Indian Ocean Territory|IO|IOT|086|ISO 3166-2:IO
|
||||||
|
Brunei Darussalam|BN|BRN|096|ISO 3166-2:BN
|
||||||
|
Bulgaria|BG|BGR|100|ISO 3166-2:BG
|
||||||
|
Burkina Faso|BF|BFA|854|ISO 3166-2:BF
|
||||||
|
Burundi|BI|BDI|108|ISO 3166-2:BI
|
||||||
|
Cambodia|KH|KHM|116|ISO 3166-2:KH
|
||||||
|
Cameroon|CM|CMR|120|ISO 3166-2:CM
|
||||||
|
Canada|CA|CAN|124|ISO 3166-2:CA
|
||||||
|
Cape Verde|CV|CPV|132|ISO 3166-2:CV
|
||||||
|
Cayman Islands|KY|CYM|136|ISO 3166-2:KY
|
||||||
|
Central African Republic|CF|CAF|140|ISO 3166-2:CF
|
||||||
|
Chad|TD|TCD|148|ISO 3166-2:TD
|
||||||
|
Chile|CL|CHL|152|ISO 3166-2:CL
|
||||||
|
China|CN|CHN|156|ISO 3166-2:CN
|
||||||
|
Christmas Island|CX|CXR|162|ISO 3166-2:CX
|
||||||
|
Cocos (Keeling) Islands|CC|CCK|166|ISO 3166-2:CC
|
||||||
|
Colombia|CO|COL|170|ISO 3166-2:CO
|
||||||
|
Comoros|KM|COM|174|ISO 3166-2:KM
|
||||||
|
Congo|CG|COG|178|ISO 3166-2:CG
|
||||||
|
Congo, the Democratic Republic of the|CD|COD|180|ISO 3166-2:CD
|
||||||
|
Cook Islands|CK|COK|184|ISO 3166-2:CK
|
||||||
|
Costa Rica|CR|CRI|188|ISO 3166-2:CR
|
||||||
|
Côte d'Ivoire|CI|CIV|384|ISO 3166-2:CI
|
||||||
|
Croatia|HR|HRV|191|ISO 3166-2:HR
|
||||||
|
Cuba|CU|CUB|192|ISO 3166-2:CU
|
||||||
|
Curaçao|CW|CUW|531|ISO 3166-2:CW
|
||||||
|
Cyprus|CY|CYP|196|ISO 3166-2:CY
|
||||||
|
Czech Republic|CZ|CZE|203|ISO 3166-2:CZ
|
||||||
|
Denmark|DK|DNK|208|ISO 3166-2:DK
|
||||||
|
Djibouti|DJ|DJI|262|ISO 3166-2:DJ
|
||||||
|
Dominica|DM|DMA|212|ISO 3166-2:DM
|
||||||
|
Dominican Republic|DO|DOM|214|ISO 3166-2:DO
|
||||||
|
Ecuador|EC|ECU|218|ISO 3166-2:EC
|
||||||
|
Egypt|EG|EGY|818|ISO 3166-2:EG
|
||||||
|
El Salvador|SV|SLV|222|ISO 3166-2:SV
|
||||||
|
Equatorial Guinea|GQ|GNQ|226|ISO 3166-2:GQ
|
||||||
|
Eritrea|ER|ERI|232|ISO 3166-2:ER
|
||||||
|
Estonia|EE|EST|233|ISO 3166-2:EE
|
||||||
|
Ethiopia|ET|ETH|231|ISO 3166-2:ET
|
||||||
|
Falkland Islands (Malvinas|FK|FLK|238|ISO 3166-2:FK
|
||||||
|
Faroe Islands|FO|FRO|234|ISO 3166-2:FO
|
||||||
|
Fiji|FJ|FJI|242|ISO 3166-2:FJ
|
||||||
|
Finland|FI|FIN|246|ISO 3166-2:FI
|
||||||
|
France|FR|FRA|250|ISO 3166-2:FR
|
||||||
|
French Guiana|GF|GUF|254|ISO 3166-2:GF
|
||||||
|
French Polynesia|PF|PYF|258|ISO 3166-2:PF
|
||||||
|
French Southern Territories|TF|ATF|260|ISO 3166-2:TF
|
||||||
|
Gabon|GA|GAB|266|ISO 3166-2:GA
|
||||||
|
Gambia|GM|GMB|270|ISO 3166-2:GM
|
||||||
|
Georgia|GE|GEO|268|ISO 3166-2:GE
|
||||||
|
Germany|DE|DEU|276|ISO 3166-2:DE
|
||||||
|
Ghana|GH|GHA|288|ISO 3166-2:GH
|
||||||
|
Gibraltar|GI|GIB|292|ISO 3166-2:GI
|
||||||
|
Greece|GR|GRC|300|ISO 3166-2:GR
|
||||||
|
Greenland|GL|GRL|304|ISO 3166-2:GL
|
||||||
|
Grenada|GD|GRD|308|ISO 3166-2:GD
|
||||||
|
Guadeloupe|GP|GLP|312|ISO 3166-2:GP
|
||||||
|
Guam|GU|GUM|316|ISO 3166-2:GU
|
||||||
|
Guatemala|GT|GTM|320|ISO 3166-2:GT
|
||||||
|
Guernsey|GG|GGY|831|ISO 3166-2:GG
|
||||||
|
Guinea|GN|GIN|324|ISO 3166-2:GN
|
||||||
|
Guinea-Bissau|GW|GNB|624|ISO 3166-2:GW
|
||||||
|
Guyana|GY|GUY|328|ISO 3166-2:GY
|
||||||
|
Haiti|HT|HTI|332|ISO 3166-2:HT
|
||||||
|
Heard Island and McDonald Islands|HM|HMD|334|ISO 3166-2:HM
|
||||||
|
Holy See (Vatican City State|VA|VAT|336|ISO 3166-2:VA
|
||||||
|
Honduras|HN|HND|340|ISO 3166-2:HN
|
||||||
|
Hong Kong|HK|HKG|344|ISO 3166-2:HK
|
||||||
|
Hungary|HU|HUN|348|ISO 3166-2:HU
|
||||||
|
Iceland|IS|ISL|352|ISO 3166-2:IS
|
||||||
|
India|IN|IND|356|ISO 3166-2:IN
|
||||||
|
Indonesia|ID|IDN|360|ISO 3166-2:ID
|
||||||
|
Iran, Islamic Republic of|IR|IRN|364|ISO 3166-2:IR
|
||||||
|
Iraq|IQ|IRQ|368|ISO 3166-2:IQ
|
||||||
|
Ireland|IE|IRL|372|ISO 3166-2:IE
|
||||||
|
Isle of Man|IM|IMN|833|ISO 3166-2:IM
|
||||||
|
Israel|IL|ISR|376|ISO 3166-2:IL
|
||||||
|
Italy|IT|ITA|380|ISO 3166-2:IT
|
||||||
|
Jamaica|JM|JAM|388|ISO 3166-2:JM
|
||||||
|
Japan|JP|JPN|392|ISO 3166-2:JP
|
||||||
|
Jersey|JE|JEY|832|ISO 3166-2:JE
|
||||||
|
Jordan|JO|JOR|400|ISO 3166-2:JO
|
||||||
|
Kazakhstan|KZ|KAZ|398|ISO 3166-2:KZ
|
||||||
|
Kenya|KE|KEN|404|ISO 3166-2:KE
|
||||||
|
Kiribati|KI|KIR|296|ISO 3166-2:KI
|
||||||
|
Korea, Democratic People's Republic of|KP|PRK|408|ISO 3166-2:KP
|
||||||
|
Korea, Republic of|KR|KOR|410|ISO 3166-2:KR
|
||||||
|
Kuwait|KW|KWT|414|ISO 3166-2:KW
|
||||||
|
Kyrgyzstan|KG|KGZ|417|ISO 3166-2:KG
|
||||||
|
Lao People's Democratic Republic|LA|LAO|418|ISO 3166-2:LA
|
||||||
|
Latvia|LV|LVA|428|ISO 3166-2:LV
|
||||||
|
Lebanon|LB|LBN|422|ISO 3166-2:LB
|
||||||
|
Lesotho|LS|LSO|426|ISO 3166-2:LS
|
||||||
|
Liberia|LR|LBR|430|ISO 3166-2:LR
|
||||||
|
Libya|LY|LBY|434|ISO 3166-2:LY
|
||||||
|
Liechtenstein|LI|LIE|438|ISO 3166-2:LI
|
||||||
|
Lithuania|LT|LTU|440|ISO 3166-2:LT
|
||||||
|
Luxembourg|LU|LUX|442|ISO 3166-2:LU
|
||||||
|
Macao|MO|MAC|446|ISO 3166-2:MO
|
||||||
|
Macedonia, the former Yugoslav Republic of|MK|MKD|807|ISO 3166-2:MK
|
||||||
|
Madagascar|MG|MDG|450|ISO 3166-2:MG
|
||||||
|
Malawi|MW|MWI|454|ISO 3166-2:MW
|
||||||
|
Malaysia|MY|MYS|458|ISO 3166-2:MY
|
||||||
|
Maldives|MV|MDV|462|ISO 3166-2:MV
|
||||||
|
Mali|ML|MLI|466|ISO 3166-2:ML
|
||||||
|
Malta|MT|MLT|470|ISO 3166-2:MT
|
||||||
|
Marshall Islands|MH|MHL|584|ISO 3166-2:MH
|
||||||
|
Martinique|MQ|MTQ|474|ISO 3166-2:MQ
|
||||||
|
Mauritania|MR|MRT|478|ISO 3166-2:MR
|
||||||
|
Mauritius|MU|MUS|480|ISO 3166-2:MU
|
||||||
|
Mayotte|YT|MYT|175|ISO 3166-2:YT
|
||||||
|
Mexico|MX|MEX|484|ISO 3166-2:MX
|
||||||
|
Micronesia, Federated States of|FM|FSM|583|ISO 3166-2:FM
|
||||||
|
Moldova, Republic of|MD|MDA|498|ISO 3166-2:MD
|
||||||
|
Monaco|MC|MCO|492|ISO 3166-2:MC
|
||||||
|
Mongolia|MN|MNG|496|ISO 3166-2:MN
|
||||||
|
Montenegro|ME|MNE|499|ISO 3166-2:ME
|
||||||
|
Montserrat|MS|MSR|500|ISO 3166-2:MS
|
||||||
|
Morocco|MA|MAR|504|ISO 3166-2:MA
|
||||||
|
Mozambique|MZ|MOZ|508|ISO 3166-2:MZ
|
||||||
|
Myanmar|MM|MMR|104|ISO 3166-2:MM
|
||||||
|
Namibia|NA|NAM|516|ISO 3166-2:NA
|
||||||
|
Nauru|NR|NRU|520|ISO 3166-2:NR
|
||||||
|
Nepal|NP|NPL|524|ISO 3166-2:NP
|
||||||
|
Netherlands|NL|NLD|528|ISO 3166-2:NL
|
||||||
|
New Caledonia|NC|NCL|540|ISO 3166-2:NC
|
||||||
|
New Zealand|NZ|NZL|554|ISO 3166-2:NZ
|
||||||
|
Nicaragua|NI|NIC|558|ISO 3166-2:NI
|
||||||
|
Niger|NE|NER|562|ISO 3166-2:NE
|
||||||
|
Nigeria|NG|NGA|566|ISO 3166-2:NG
|
||||||
|
Niue|NU|NIU|570|ISO 3166-2:NU
|
||||||
|
Norfolk Island|NF|NFK|574|ISO 3166-2:NF
|
||||||
|
Northern Mariana Islands|MP|MNP|580|ISO 3166-2:MP
|
||||||
|
Norway|NO|NOR|578|ISO 3166-2:NO
|
||||||
|
Oman|OM|OMN|512|ISO 3166-2:OM
|
||||||
|
Pakistan|PK|PAK|586|ISO 3166-2:PK
|
||||||
|
Palau|PW|PLW|585|ISO 3166-2:PW
|
||||||
|
Palestinian Territory, Occupied|PS|PSE|275|ISO 3166-2:PS
|
||||||
|
Panama|PA|PAN|591|ISO 3166-2:PA
|
||||||
|
Papua New Guinea|PG|PNG|598|ISO 3166-2:PG
|
||||||
|
Paraguay|PY|PRY|600|ISO 3166-2:PY
|
||||||
|
Peru|PE|PER|604|ISO 3166-2:PE
|
||||||
|
Philippines|PH|PHL|608|ISO 3166-2:PH
|
||||||
|
Pitcairn|PN|PCN|612|ISO 3166-2:PN
|
||||||
|
Poland|PL|POL|616|ISO 3166-2:PL
|
||||||
|
Portugal|PT|PRT|620|ISO 3166-2:PT
|
||||||
|
Puerto Rico|PR|PRI|630|ISO 3166-2:PR
|
||||||
|
Qatar|QA|QAT|634|ISO 3166-2:QA
|
||||||
|
Réunion|RE|REU|638|ISO 3166-2:RE
|
||||||
|
Romania|RO|ROU|642|ISO 3166-2:RO
|
||||||
|
Russian Federation|RU|RUS|643|ISO 3166-2:RU
|
||||||
|
Rwanda|RW|RWA|646|ISO 3166-2:RW
|
||||||
|
Saint Barthélemy|BL|BLM|652|ISO 3166-2:BL
|
||||||
|
Saint Helena, Ascension and Tristan da Cunha|SH|SHN|654|ISO 3166-2:SH
|
||||||
|
Saint Kitts and Nevis|KN|KNA|659|ISO 3166-2:KN
|
||||||
|
Saint Lucia|LC|LCA|662|ISO 3166-2:LC
|
||||||
|
Saint Martin (French part|MF|MAF|663|ISO 3166-2:MF
|
||||||
|
Saint Pierre and Miquelon|PM|SPM|666|ISO 3166-2:PM
|
||||||
|
Saint Vincent and the Grenadines|VC|VCT|670|ISO 3166-2:VC
|
||||||
|
Samoa|WS|WSM|882|ISO 3166-2:WS
|
||||||
|
San Marino|SM|SMR|674|ISO 3166-2:SM
|
||||||
|
Sao Tome and Principe|ST|STP|678|ISO 3166-2:ST
|
||||||
|
Saudi Arabia|SA|SAU|682|ISO 3166-2:SA
|
||||||
|
Senegal|SN|SEN|686|ISO 3166-2:SN
|
||||||
|
Serbia|RS|SRB|688|ISO 3166-2:RS
|
||||||
|
Seychelles|SC|SYC|690|ISO 3166-2:SC
|
||||||
|
Sierra Leone|SL|SLE|694|ISO 3166-2:SL
|
||||||
|
Singapore|SG|SGP|702|ISO 3166-2:SG
|
||||||
|
Sint Maarten (Dutch part|SX|SXM|534|ISO 3166-2:SX
|
||||||
|
Slovakia|SK|SVK|703|ISO 3166-2:SK
|
||||||
|
Slovenia|SI|SVN|705|ISO 3166-2:SI
|
||||||
|
Solomon Islands|SB|SLB|090|ISO 3166-2:SB
|
||||||
|
Somalia|SO|SOM|706|ISO 3166-2:SO
|
||||||
|
South Africa|ZA|ZAF|710|ISO 3166-2:ZA
|
||||||
|
South Georgia and the South Sandwich Islands|GS|SGS|239|ISO 3166-2:GS
|
||||||
|
South Sudan|SS|SSD|728|ISO 3166-2:SS
|
||||||
|
Spain|ES|ESP|724|ISO 3166-2:ES
|
||||||
|
Sri Lanka|LK|LKA|144|ISO 3166-2:LK
|
||||||
|
Sudan|SD|SDN|729|ISO 3166-2:SD
|
||||||
|
Suriname|SR|SUR|740|ISO 3166-2:SR
|
||||||
|
Svalbard and Jan Mayen|SJ|SJM|744|ISO 3166-2:SJ
|
||||||
|
Swaziland|SZ|SWZ|748|ISO 3166-2:SZ
|
||||||
|
Sweden|SE|SWE|752|ISO 3166-2:SE
|
||||||
|
Switzerland|CH|CHE|756|ISO 3166-2:CH
|
||||||
|
Syrian Arab Republic|SY|SYR|760|ISO 3166-2:SY
|
||||||
|
Taiwan, Province of China|TW|TWN|158|ISO 3166-2:TW
|
||||||
|
Tajikistan|TJ|TJK|762|ISO 3166-2:TJ
|
||||||
|
Tanzania, United Republic of|TZ|TZA|834|ISO 3166-2:TZ
|
||||||
|
Thailand|TH|THA|764|ISO 3166-2:TH
|
||||||
|
Timor-Leste|TL|TLS|626|ISO 3166-2:TL
|
||||||
|
Togo|TG|TGO|768|ISO 3166-2:TG
|
||||||
|
Tokelau|TK|TKL|772|ISO 3166-2:TK
|
||||||
|
Tonga|TO|TON|776|ISO 3166-2:TO
|
||||||
|
Trinidad and Tobago|TT|TTO|780|ISO 3166-2:TT
|
||||||
|
Tunisia|TN|TUN|788|ISO 3166-2:TN
|
||||||
|
Turkey|TR|TUR|792|ISO 3166-2:TR
|
||||||
|
Turkmenistan|TM|TKM|795|ISO 3166-2:TM
|
||||||
|
Turks and Caicos Islands|TC|TCA|796|ISO 3166-2:TC
|
||||||
|
Tuvalu|TV|TUV|798|ISO 3166-2:TV
|
||||||
|
Uganda|UG|UGA|800|ISO 3166-2:UG
|
||||||
|
Ukraine|UA|UKR|804|ISO 3166-2:UA
|
||||||
|
United Arab Emirates|AE|ARE|784|ISO 3166-2:AE
|
||||||
|
United Kingdom|GB|GBR|826|ISO 3166-2:GB
|
||||||
|
United States|US|USA|840|ISO 3166-2:US
|
||||||
|
United States Minor Outlying Islands|UM|UMI|581|ISO 3166-2:UM
|
||||||
|
Uruguay|UY|URY|858|ISO 3166-2:UY
|
||||||
|
Uzbekistan|UZ|UZB|860|ISO 3166-2:UZ
|
||||||
|
Vanuatu|VU|VUT|548|ISO 3166-2:VU
|
||||||
|
Venezuela, Bolivarian Republic of|VE|VEN|862|ISO 3166-2:VE
|
||||||
|
Viet Nam|VN|VNM|704|ISO 3166-2:VN
|
||||||
|
Virgin Islands, British|VG|VGB|092|ISO 3166-2:VG
|
||||||
|
Virgin Islands, U.S|VI|VIR|850|ISO 3166-2:VI
|
||||||
|
Wallis and Futuna|WF|WLF|876|ISO 3166-2:WF
|
||||||
|
Western Sahara|EH|ESH|732|ISO 3166-2:EH
|
||||||
|
Yemen|YE|YEM|887|ISO 3166-2:YE
|
||||||
|
Zambia|ZM|ZMB|894|ISO 3166-2:ZM
|
||||||
|
Zimbabwe|ZW|ZWE|716|ISO 3166-2:ZW
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
__version__ = '0.3.1'
|
__version__ = '0.4'
|
||||||
__all__ = ['Guess', 'Language',
|
__all__ = ['Guess', 'Language',
|
||||||
'guess_file_info', 'guess_video_info',
|
'guess_file_info', 'guess_video_info',
|
||||||
'guess_movie_info', 'guess_episode_info']
|
'guess_movie_info', 'guess_episode_info']
|
||||||
@@ -29,7 +29,7 @@ from guessit.language import Language
|
|||||||
from guessit.matcher import IterativeMatcher
|
from guessit.matcher import IterativeMatcher
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger("guessit")
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class NullHandler(logging.Handler):
|
class NullHandler(logging.Handler):
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# GuessIt - A library for guessing information from filenames
|
||||||
|
# Copyright (c) 2012 Nicolas Wack <wackou@gmail.com>
|
||||||
|
#
|
||||||
|
# GuessIt is free software; you can redistribute it and/or modify it under
|
||||||
|
# the terms of the Lesser GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# GuessIt is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# Lesser GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the Lesser GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
from guessit import fileutils
|
||||||
|
import logging
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# parsed from http://en.wikipedia.org/wiki/ISO_3166-1
|
||||||
|
#
|
||||||
|
# Description of the fields:
|
||||||
|
# "An English name, an alpha-2 code (when given),
|
||||||
|
# an alpha-3 code (when given), a numeric code, and an ISO 31666-2 code
|
||||||
|
# are all separated by pipe (|) characters."
|
||||||
|
_iso3166_contents = fileutils.load_file_in_same_dir(__file__,
|
||||||
|
'ISO-3166-1_utf8.txt').decode('utf-8')
|
||||||
|
|
||||||
|
country_matrix = [ l.strip().split('|')
|
||||||
|
for l in _iso3166_contents.strip().split('\n') ]
|
||||||
|
|
||||||
|
country_matrix += [ [ 'Unknown', 'un', 'unk', '', '' ],
|
||||||
|
[ 'Latin America', '', 'lat', '', '' ]
|
||||||
|
]
|
||||||
|
|
||||||
|
country_to_alpha3 = dict((c[0].lower(), c[2].lower()) for c in country_matrix)
|
||||||
|
country_to_alpha3.update(dict((c[1].lower(), c[2].lower()) for c in country_matrix))
|
||||||
|
country_to_alpha3.update(dict((c[2].lower(), c[2].lower()) for c in country_matrix))
|
||||||
|
|
||||||
|
# add here exceptions / non ISO representations
|
||||||
|
# Note: remember to put those exceptions in lower-case, they won't work otherwise
|
||||||
|
country_to_alpha3.update({ 'latinoamérica': 'lat',
|
||||||
|
'brazilian': 'bra',
|
||||||
|
'españa': 'esp',
|
||||||
|
'uk': 'gbr'
|
||||||
|
})
|
||||||
|
|
||||||
|
country_alpha3_to_en_name = dict((c[2].lower(), c[0]) for c in country_matrix)
|
||||||
|
country_alpha3_to_alpha2 = dict((c[2].lower(), c[1].lower()) for c in country_matrix)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Country(object):
|
||||||
|
"""This class represents a country.
|
||||||
|
|
||||||
|
You can initialize it with pretty much anything, as it knows conversion
|
||||||
|
from ISO-3166 2-letter and 3-letter codes, and an English name.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, country, strict=False):
|
||||||
|
self.alpha3 = country_to_alpha3.get(country.lower())
|
||||||
|
|
||||||
|
if self.alpha3 is None and strict:
|
||||||
|
msg = 'The given string "%s" could not be identified as a country'
|
||||||
|
raise ValueError(msg % country)
|
||||||
|
|
||||||
|
if self.alpha3 is None:
|
||||||
|
self.alpha3 = 'unk'
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def alpha2(self):
|
||||||
|
return country_alpha3_to_alpha2[self.alpha3]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def english_name(self):
|
||||||
|
return country_alpha3_to_en_name[self.alpha3]
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.alpha3)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, Country):
|
||||||
|
return self.alpha3 == other.alpha3
|
||||||
|
|
||||||
|
if isinstance(other, basestring):
|
||||||
|
try:
|
||||||
|
return self == Country(other)
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not self == other
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.english_name
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return unicode(self).encode('utf-8')
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'Country(%s)' % self.english_name
|
||||||
|
|
||||||
@@ -51,8 +51,8 @@ def split_path(path):
|
|||||||
if head == '/' and tail == '':
|
if head == '/' and tail == '':
|
||||||
return ['/'] + result
|
return ['/'] + result
|
||||||
|
|
||||||
# on Windows, the root folder is a drive letter (eg: 'C:\')
|
# on Windows, the root folder is a drive letter (eg: 'C:\') or for shares \\
|
||||||
if len(head) == 3 and head[1:] == ':\\' and tail == '':
|
if ((len(head) == 3 and head[1:] == ':\\') or (len(head) == 2 and head == '\\\\')) and tail == '':
|
||||||
return [head] + result
|
return [head] + result
|
||||||
|
|
||||||
if head == '' and tail == '':
|
if head == '' and tail == '':
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import json
|
|||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger("guessit.guess")
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Guess(dict):
|
class Guess(dict):
|
||||||
|
|||||||
+132
-26
@@ -18,10 +18,18 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
from guessit import fileutils
|
from guessit import fileutils
|
||||||
|
from guessit.country import Country
|
||||||
|
import re
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger('guessit.language')
|
__all__ = [ 'is_iso_language', 'is_language', 'lang_set', 'Language',
|
||||||
|
'ALL_LANGUAGES', 'ALL_LANGUAGES_NAMES', 'search_language' ]
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# downloaded from http://www.loc.gov/standards/iso639-2/ISO-639-2_utf-8.txt
|
# downloaded from http://www.loc.gov/standards/iso639-2/ISO-639-2_utf-8.txt
|
||||||
#
|
#
|
||||||
@@ -30,9 +38,23 @@ log = logging.getLogger('guessit.language')
|
|||||||
# an alpha-2 code (when given), an English name, and a French name of a language
|
# an alpha-2 code (when given), an English name, and a French name of a language
|
||||||
# are all separated by pipe (|) characters."
|
# are all separated by pipe (|) characters."
|
||||||
_iso639_contents = fileutils.load_file_in_same_dir(__file__,
|
_iso639_contents = fileutils.load_file_in_same_dir(__file__,
|
||||||
'ISO-639-2_utf-8.txt')
|
'ISO-639-2_utf-8.txt').decode('utf-8')
|
||||||
language_matrix = [ l.strip().decode('utf-8').split('|')
|
|
||||||
for l in _iso639_contents.split('\n') ]
|
# drop the BOM from the beginning of the file
|
||||||
|
_iso639_contents = _iso639_contents[1:]
|
||||||
|
|
||||||
|
language_matrix = [ l.strip().split('|')
|
||||||
|
for l in _iso639_contents.strip().split('\n') ]
|
||||||
|
|
||||||
|
language_matrix += [ [ 'unk', '', 'un', 'Unknown', 'inconnu' ] ]
|
||||||
|
|
||||||
|
|
||||||
|
# remove unused languages that shadow other common ones with a non-official form
|
||||||
|
for lang in language_matrix:
|
||||||
|
if (lang[2] == 'se' or # Northern Sami shadows Swedish
|
||||||
|
lang[2] == 'br'): # Breton shadows Brazilian
|
||||||
|
language_matrix.remove(lang)
|
||||||
|
|
||||||
|
|
||||||
lng3 = frozenset(l[0] for l in language_matrix if l[0])
|
lng3 = frozenset(l[0] for l in language_matrix if l[0])
|
||||||
lng3term = frozenset(l[1] for l in language_matrix if l[1])
|
lng3term = frozenset(l[1] for l in language_matrix if l[1])
|
||||||
@@ -63,54 +85,126 @@ lng_fr_name_to_lng3 = dict((fr_name.lower(), l[0])
|
|||||||
for l in language_matrix if l[4]
|
for l in language_matrix if l[4]
|
||||||
for fr_name in l[4].split('; '))
|
for fr_name in l[4].split('; '))
|
||||||
|
|
||||||
|
# contains a list of exceptions: strings that should be parsed as a language
|
||||||
|
# but which are not in an ISO form
|
||||||
|
lng_exceptions = { 'gr': ('gre', None),
|
||||||
|
'greek': ('gre', None),
|
||||||
|
'esp': ('spa', None),
|
||||||
|
'español': ('spa', None),
|
||||||
|
'se': ('swe', None),
|
||||||
|
'po': ('pt', 'br'),
|
||||||
|
'pob': ('pt', 'br'),
|
||||||
|
'br': ('pt', 'br'),
|
||||||
|
'brazilian': ('pt', 'br'),
|
||||||
|
'català': ('cat', None),
|
||||||
|
'cz': ('cze', None),
|
||||||
|
'ua': ('ukr', None),
|
||||||
|
'cn': ('chi', None),
|
||||||
|
'chs': ('chi', None),
|
||||||
|
'jp': ('jpn', None)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def is_iso_language(language):
|
||||||
|
return language.lower() in lng_all_names
|
||||||
|
|
||||||
def is_language(language):
|
def is_language(language):
|
||||||
return language.lower() in lng_all_names
|
return is_iso_language(language) or language in lng_exceptions
|
||||||
|
|
||||||
|
def lang_set(languages, strict=False):
|
||||||
|
"""Return a set of guessit.Language created from their given string
|
||||||
|
representation.
|
||||||
|
|
||||||
|
if strict is True, then this will raise an exception if any language
|
||||||
|
could not be identified.
|
||||||
|
"""
|
||||||
|
return set(Language(l, strict=strict) for l in languages)
|
||||||
|
|
||||||
|
|
||||||
class Language(object):
|
class Language(object):
|
||||||
"""This class represents a human language.
|
"""This class represents a human language.
|
||||||
|
|
||||||
You can initialize it with pretty much everything, as it knows conversion
|
You can initialize it with pretty much anything, as it knows conversion
|
||||||
from ISO-639 2-letter and 3-letter codes, English and French names.
|
from ISO-639 2-letter and 3-letter codes, English and French names.
|
||||||
|
|
||||||
|
You can also distinguish languages for specific countries, such as
|
||||||
|
Portuguese and Brazilian Portuguese.
|
||||||
|
|
||||||
>>> Language('fr')
|
>>> Language('fr')
|
||||||
Language(French)
|
Language(French)
|
||||||
|
|
||||||
>>> Language('eng').french_name()
|
>>> Language('eng').french_name
|
||||||
u'anglais'
|
u'anglais'
|
||||||
|
|
||||||
|
>>> Language('pt(br)').country.english_name
|
||||||
|
u'Brazil'
|
||||||
|
|
||||||
|
>>> Language('Español (Latinoamérica)').country.english_name
|
||||||
|
u'Latin America'
|
||||||
|
|
||||||
|
>>> Language('Spanish (Latin America)') == Language('Español (Latinoamérica)')
|
||||||
|
True
|
||||||
|
|
||||||
|
>>> Language('zz', strict=False).english_name
|
||||||
|
u'Unknown'
|
||||||
"""
|
"""
|
||||||
def __init__(self, language):
|
|
||||||
lang = None
|
_with_country_regexp = re.compile('(.*)\((.*)\)')
|
||||||
language = language.lower()
|
|
||||||
|
def __init__(self, language, country=None, strict=False):
|
||||||
|
language = language.strip().lower()
|
||||||
|
if isinstance(language, str):
|
||||||
|
language = language.decode('utf-8')
|
||||||
|
with_country = Language._with_country_regexp.match(language)
|
||||||
|
if with_country:
|
||||||
|
self.lang = Language(with_country.group(1)).lang
|
||||||
|
self.country = Country(with_country.group(2))
|
||||||
|
return
|
||||||
|
|
||||||
|
self.lang = None
|
||||||
|
self.country = Country(country) if country else None
|
||||||
|
|
||||||
if len(language) == 2:
|
if len(language) == 2:
|
||||||
lang = lng2_to_lng3.get(language)
|
self.lang = lng2_to_lng3.get(language)
|
||||||
elif len(language) == 3:
|
elif len(language) == 3:
|
||||||
lang = (language
|
self.lang = (language
|
||||||
if language in lng3
|
if language in lng3
|
||||||
else lng3term_to_lng3.get(language))
|
else lng3term_to_lng3.get(language))
|
||||||
else:
|
else:
|
||||||
lang = (lng_en_name_to_lng3.get(language) or
|
self.lang = (lng_en_name_to_lng3.get(language) or
|
||||||
lng_fr_name_to_lng3.get(language))
|
lng_fr_name_to_lng3.get(language))
|
||||||
|
|
||||||
if lang is None:
|
if self.lang is None and language in lng_exceptions:
|
||||||
msg = 'The given string "%s" could not be identified as a language'
|
lang, country = lng_exceptions[language]
|
||||||
raise ValueError(msg % language)
|
self.lang = Language(lang).alpha3
|
||||||
|
self.country = Country(country) if country else None
|
||||||
|
|
||||||
self.lang = lang
|
msg = 'The given string "%s" could not be identified as a language' % language
|
||||||
|
|
||||||
def lng2(self):
|
if self.lang is None and strict:
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
if self.lang is None:
|
||||||
|
log.debug(msg)
|
||||||
|
self.lang = 'unk'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def alpha2(self):
|
||||||
return lng3_to_lng2[self.lang]
|
return lng3_to_lng2[self.lang]
|
||||||
|
|
||||||
def lng3(self):
|
@property
|
||||||
|
def alpha3(self):
|
||||||
return self.lang
|
return self.lang
|
||||||
|
|
||||||
def lng3term(self):
|
@property
|
||||||
|
def alpha3term(self):
|
||||||
return lng3_to_lng3term[self.lang]
|
return lng3_to_lng3term[self.lang]
|
||||||
|
|
||||||
|
@property
|
||||||
def english_name(self):
|
def english_name(self):
|
||||||
return lng3_to_lng_en_name[self.lang]
|
return lng3_to_lng_en_name[self.lang]
|
||||||
|
|
||||||
|
@property
|
||||||
def french_name(self):
|
def french_name(self):
|
||||||
return lng3_to_lng_fr_name[self.lang]
|
return lng3_to_lng_fr_name[self.lang]
|
||||||
|
|
||||||
@@ -132,16 +226,28 @@ class Language(object):
|
|||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
return not self == other
|
return not self == other
|
||||||
|
|
||||||
|
def __nonzero__(self):
|
||||||
|
return self.lang != 'unk'
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return lng3_to_lng_en_name[self.lang]
|
if self.country:
|
||||||
|
return '%s(%s)' % (self.english_name, self.country.alpha2)
|
||||||
|
else:
|
||||||
|
return self.english_name
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return unicode(self).encode('utf-8')
|
return unicode(self).encode('utf-8')
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'Language(%s)' % self
|
if self.country:
|
||||||
|
return 'Language(%s, country=%s)' % (self.english_name, self.country)
|
||||||
|
else:
|
||||||
|
return 'Language(%s)' % self.english_name
|
||||||
|
|
||||||
|
|
||||||
|
ALL_LANGUAGES = frozenset(Language(lng) for lng in lng_all_names) - frozenset([Language('unk')])
|
||||||
|
ALL_LANGUAGES_NAMES = lng_all_names
|
||||||
|
|
||||||
def search_language(string, lang_filter=None):
|
def search_language(string, lang_filter=None):
|
||||||
"""Looks for language patterns, and if found return the language object,
|
"""Looks for language patterns, and if found return the language object,
|
||||||
its group span and an associated confidence.
|
its group span and an associated confidence.
|
||||||
@@ -177,7 +283,7 @@ def search_language(string, lang_filter=None):
|
|||||||
sep = r'[](){} \._-+'
|
sep = r'[](){} \._-+'
|
||||||
|
|
||||||
if lang_filter:
|
if lang_filter:
|
||||||
lang_filter = set(Language(l) for l in lang_filter)
|
lang_filter = lang_set(lang_filter)
|
||||||
|
|
||||||
slow = ' %s ' % string.lower()
|
slow = ' %s ' % string.lower()
|
||||||
confidence = 1.0 # for all of them
|
confidence = 1.0 # for all of them
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ from guessit.guess import (merge_similar_guesses, merge_all,
|
|||||||
import copy
|
import copy
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger("guessit.matcher")
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class IterativeMatcher(object):
|
class IterativeMatcher(object):
|
||||||
@@ -105,7 +105,7 @@ class IterativeMatcher(object):
|
|||||||
'guess_release_group', 'guess_properties',
|
'guess_release_group', 'guess_properties',
|
||||||
'guess_weak_episodes_rexps', 'guess_language']
|
'guess_weak_episodes_rexps', 'guess_language']
|
||||||
else:
|
else:
|
||||||
strategy = ['guess_date', 'guess_year', 'guess_video_rexps',
|
strategy = ['guess_date', 'guess_video_rexps',
|
||||||
'guess_website', 'guess_release_group',
|
'guess_website', 'guess_release_group',
|
||||||
'guess_properties', 'guess_language']
|
'guess_properties', 'guess_language']
|
||||||
|
|
||||||
@@ -125,6 +125,7 @@ class IterativeMatcher(object):
|
|||||||
if mtree.guess['type'] in ('episode', 'episodesubtitle'):
|
if mtree.guess['type'] in ('episode', 'episodesubtitle'):
|
||||||
apply_transfo('guess_episode_info_from_position')
|
apply_transfo('guess_episode_info_from_position')
|
||||||
else:
|
else:
|
||||||
|
apply_transfo('guess_year')
|
||||||
apply_transfo('guess_movie_title_from_position')
|
apply_transfo('guess_movie_title_from_position')
|
||||||
|
|
||||||
# 6- perform some post-processing steps
|
# 6- perform some post-processing steps
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ from guessit.textutils import clean_string, str_fill, to_utf8
|
|||||||
from guessit.patterns import group_delimiters
|
from guessit.patterns import group_delimiters
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger("guessit.matchtree")
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class BaseMatchTree(object):
|
class BaseMatchTree(object):
|
||||||
|
|||||||
@@ -22,8 +22,9 @@
|
|||||||
|
|
||||||
subtitle_exts = [ 'srt', 'idx', 'sub', 'ssa', 'txt' ]
|
subtitle_exts = [ 'srt', 'idx', 'sub', 'ssa', 'txt' ]
|
||||||
|
|
||||||
video_exts = [ 'avi', 'mkv', 'mpg', 'mp4', 'm4v', 'mov', 'ogg', 'ogm', 'ogv',
|
video_exts = ['3g2', '3gp', '3gp2', 'asf', 'avi', 'divx', 'flv', 'm4v', 'mk2',
|
||||||
'wmv', 'divx' ]
|
'mka', 'mkv', 'mov', 'mp4', 'mp4a', 'mpeg', 'mpg', 'ogg', 'ogm',
|
||||||
|
'ogv', 'qt', 'ra', 'ram', 'rm', 'ts', 'wav', 'webm', 'wma', 'wmv']
|
||||||
|
|
||||||
group_delimiters = [ '()', '[]', '{}' ]
|
group_delimiters = [ '()', '[]', '{}' ]
|
||||||
|
|
||||||
@@ -62,6 +63,8 @@ weak_episode_rexps = [ # ... 213 or 0106 ...
|
|||||||
# ... 2x13 ...
|
# ... 2x13 ...
|
||||||
(sep + r'[^0-9](?P<season>[0-9]{1,2})\.(?P<episodeNumber>[0-9]{2})[^0-9]' + sep, (1, -1)),
|
(sep + r'[^0-9](?P<season>[0-9]{1,2})\.(?P<episodeNumber>[0-9]{2})[^0-9]' + sep, (1, -1)),
|
||||||
|
|
||||||
|
# ... e13 ... for a mini-series without a season number
|
||||||
|
(r'e(?P<episodeNumber>[0-9]{1,4})[^0-9]', (0, -1)),
|
||||||
]
|
]
|
||||||
|
|
||||||
non_episode_title = [ 'extras', 'rip' ]
|
non_episode_title = [ 'extras', 'rip' ]
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ from guessit.patterns import canonical_form
|
|||||||
from guessit.textutils import clean_string
|
from guessit.textutils import clean_string
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger('guessit.transfo')
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def found_property(node, name, confidence):
|
def found_property(node, name, confidence):
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
from guessit.transfo import found_property
|
from guessit.transfo import found_property
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger("guessit.transfo.guess_bonus_features")
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def process(mtree):
|
def process(mtree):
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from guessit.transfo import SingleNodeGuesser
|
|||||||
from guessit.date import search_date
|
from guessit.date import search_date
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger("guessit.transfo.guess_date")
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def guess_date(string):
|
def guess_date(string):
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from guessit.transfo import found_property
|
|||||||
from guessit.patterns import non_episode_title, unlikely_series
|
from guessit.patterns import non_episode_title, unlikely_series
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger("guessit.transfo.guess_episode_info_from_position")
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def match_from_epnum_position(mtree, node):
|
def match_from_epnum_position(mtree, node):
|
||||||
@@ -112,6 +112,9 @@ def process(mtree):
|
|||||||
if len(title_candidates) >= 2:
|
if len(title_candidates) >= 2:
|
||||||
found_property(title_candidates[0], 'series', 0.4)
|
found_property(title_candidates[0], 'series', 0.4)
|
||||||
found_property(title_candidates[1], 'title', 0.4)
|
found_property(title_candidates[1], 'title', 0.4)
|
||||||
|
elif len(title_candidates) == 1:
|
||||||
|
# but if there's only one candidate, it's probably the series name
|
||||||
|
found_property(title_candidates[0], 'series', 0.4)
|
||||||
|
|
||||||
# if we only have 1 remaining valid group in the folder containing the
|
# if we only have 1 remaining valid group in the folder containing the
|
||||||
# file, then it's likely that it is the series name
|
# file, then it's likely that it is the series name
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from guessit.patterns import episode_rexps
|
|||||||
import re
|
import re
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger("guessit.transfo.guess_episodes_rexps")
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def guess_episodes_rexps(string):
|
def guess_episodes_rexps(string):
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import re
|
|||||||
import mimetypes
|
import mimetypes
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger("guessit.transfo.guess_filetype")
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def guess_filetype(filename, filetype):
|
def guess_filetype(filename, filetype):
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from guessit.language import search_language
|
|||||||
from guessit.textutils import clean_string
|
from guessit.textutils import clean_string
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger("guessit.transfo.guess_language")
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def guess_language(string):
|
def guess_language(string):
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
from guessit import Guess
|
from guessit import Guess
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger("guessit.transfo.guess_movie_title_from_position")
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def process(mtree):
|
def process(mtree):
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from guessit.transfo import SingleNodeGuesser
|
|||||||
from guessit.patterns import find_properties
|
from guessit.patterns import find_properties
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger("guessit.transfo.guess_properties")
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def guess_properties(string):
|
def guess_properties(string):
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from guessit.transfo import SingleNodeGuesser
|
|||||||
import re
|
import re
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger("guessit.transfo.guess_release_group")
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def guess_release_group(string):
|
def guess_release_group(string):
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from guessit.patterns import video_rexps, sep
|
|||||||
import re
|
import re
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger("guessit.transfo.guess_video_rexps")
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def guess_video_rexps(string):
|
def guess_video_rexps(string):
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from guessit.patterns import weak_episode_rexps
|
|||||||
import re
|
import re
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger("guessit.transfo.guess_weak_episodes_rexps")
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def guess_weak_episodes_rexps(string, node):
|
def guess_weak_episodes_rexps(string, node):
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from guessit.transfo import SingleNodeGuesser
|
|||||||
from guessit.patterns import websites
|
from guessit.patterns import websites
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger("guessit.transfo.guess_website")
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def guess_website(string):
|
def guess_website(string):
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from guessit.transfo import SingleNodeGuesser
|
|||||||
from guessit.date import search_year
|
from guessit.date import search_year
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger("guessit.transfo.guess_year")
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def guess_year(string):
|
def guess_year(string):
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
from guessit.patterns import subtitle_exts
|
from guessit.patterns import subtitle_exts
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger("guessit.transfo.post_process")
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def process(mtree):
|
def process(mtree):
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from guessit.textutils import find_first_level_groups
|
|||||||
from guessit.patterns import group_delimiters
|
from guessit.patterns import group_delimiters
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger("guessit.transfo.split_explicit_groups")
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def process(mtree):
|
def process(mtree):
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from guessit.patterns import sep
|
|||||||
import re
|
import re
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger("guessit.transfo.split_on_dash")
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def process(mtree):
|
def process(mtree):
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from guessit import fileutils
|
|||||||
import os.path
|
import os.path
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger("guessit.transfo.split_path_components")
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def process(mtree):
|
def process(mtree):
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class TemplatesNotFound(TemplateNotFound):
|
|||||||
|
|
||||||
def __init__(self, names=(), message=None):
|
def __init__(self, names=(), message=None):
|
||||||
if message is None:
|
if message is None:
|
||||||
message = u'non of the templates given were found: ' + \
|
message = u'none of the templates given were found: ' + \
|
||||||
u', '.join(map(unicode, names))
|
u', '.join(map(unicode, names))
|
||||||
TemplateNotFound.__init__(self, names and names[-1] or None, message)
|
TemplateNotFound.__init__(self, names and names[-1] or None, message)
|
||||||
self.templates = list(names)
|
self.templates = list(names)
|
||||||
|
|||||||
@@ -176,7 +176,12 @@ def do_title(s):
|
|||||||
"""Return a titlecased version of the value. I.e. words will start with
|
"""Return a titlecased version of the value. I.e. words will start with
|
||||||
uppercase letters, all remaining characters are lowercase.
|
uppercase letters, all remaining characters are lowercase.
|
||||||
"""
|
"""
|
||||||
return soft_unicode(s).title()
|
rv = []
|
||||||
|
for item in re.compile(r'([-\s]+)(?u)').split(s):
|
||||||
|
if not item:
|
||||||
|
continue
|
||||||
|
rv.append(item[0].upper() + item[1:])
|
||||||
|
return ''.join(rv)
|
||||||
|
|
||||||
|
|
||||||
def do_dictsort(value, case_sensitive=False, by='key'):
|
def do_dictsort(value, case_sensitive=False, by='key'):
|
||||||
@@ -578,7 +583,7 @@ def do_batch(value, linecount, fill_with=None):
|
|||||||
A filter that batches items. It works pretty much like `slice`
|
A filter that batches items. It works pretty much like `slice`
|
||||||
just the other way round. It returns a list of lists with the
|
just the other way round. It returns a list of lists with the
|
||||||
given number of items. If you provide a second parameter this
|
given number of items. If you provide a second parameter this
|
||||||
is used to fill missing items. See this example:
|
is used to fill up missing items. See this example:
|
||||||
|
|
||||||
.. sourcecode:: html+jinja
|
.. sourcecode:: html+jinja
|
||||||
|
|
||||||
|
|||||||
Executable
+95
@@ -0,0 +1,95 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja2.testsuite
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
All the unittests of Jinja2. These tests can be executed by
|
||||||
|
either running run-tests.py using multiple Python versions at
|
||||||
|
the same time.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
from traceback import format_exception
|
||||||
|
from jinja2 import loaders
|
||||||
|
|
||||||
|
|
||||||
|
here = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
dict_loader = loaders.DictLoader({
|
||||||
|
'justdict.html': 'FOO'
|
||||||
|
})
|
||||||
|
package_loader = loaders.PackageLoader('jinja2.testsuite.res', 'templates')
|
||||||
|
filesystem_loader = loaders.FileSystemLoader(here + '/res/templates')
|
||||||
|
function_loader = loaders.FunctionLoader({'justfunction.html': 'FOO'}.get)
|
||||||
|
choice_loader = loaders.ChoiceLoader([dict_loader, package_loader])
|
||||||
|
prefix_loader = loaders.PrefixLoader({
|
||||||
|
'a': filesystem_loader,
|
||||||
|
'b': dict_loader
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class JinjaTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
### use only these methods for testing. If you need standard
|
||||||
|
### unittest method, wrap them!
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def teardown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.setup()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.teardown()
|
||||||
|
|
||||||
|
def assert_equal(self, a, b):
|
||||||
|
return self.assertEqual(a, b)
|
||||||
|
|
||||||
|
def assert_raises(self, *args, **kwargs):
|
||||||
|
return self.assertRaises(*args, **kwargs)
|
||||||
|
|
||||||
|
def assert_traceback_matches(self, callback, expected_tb):
|
||||||
|
try:
|
||||||
|
callback()
|
||||||
|
except Exception, e:
|
||||||
|
tb = format_exception(*sys.exc_info())
|
||||||
|
if re.search(expected_tb.strip(), ''.join(tb)) is None:
|
||||||
|
raise self.fail('Traceback did not match:\n\n%s\nexpected:\n%s'
|
||||||
|
% (''.join(tb), expected_tb))
|
||||||
|
else:
|
||||||
|
self.fail('Expected exception')
|
||||||
|
|
||||||
|
|
||||||
|
def suite():
|
||||||
|
from jinja2.testsuite import ext, filters, tests, core_tags, \
|
||||||
|
loader, inheritance, imports, lexnparse, security, api, \
|
||||||
|
regression, debug, utils, doctests
|
||||||
|
suite = unittest.TestSuite()
|
||||||
|
suite.addTest(ext.suite())
|
||||||
|
suite.addTest(filters.suite())
|
||||||
|
suite.addTest(tests.suite())
|
||||||
|
suite.addTest(core_tags.suite())
|
||||||
|
suite.addTest(loader.suite())
|
||||||
|
suite.addTest(inheritance.suite())
|
||||||
|
suite.addTest(imports.suite())
|
||||||
|
suite.addTest(lexnparse.suite())
|
||||||
|
suite.addTest(security.suite())
|
||||||
|
suite.addTest(api.suite())
|
||||||
|
suite.addTest(regression.suite())
|
||||||
|
suite.addTest(debug.suite())
|
||||||
|
suite.addTest(utils.suite())
|
||||||
|
|
||||||
|
# doctests will not run on python 3 currently. Too many issues
|
||||||
|
# with that, do not test that on that platform.
|
||||||
|
if sys.version_info < (3, 0):
|
||||||
|
suite.addTest(doctests.suite())
|
||||||
|
|
||||||
|
return suite
|
||||||
Executable
+245
@@ -0,0 +1,245 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja2.testsuite.api
|
||||||
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tests the public API and related stuff.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from jinja2.testsuite import JinjaTestCase
|
||||||
|
|
||||||
|
from jinja2 import Environment, Undefined, DebugUndefined, \
|
||||||
|
StrictUndefined, UndefinedError, meta, \
|
||||||
|
is_undefined, Template, DictLoader
|
||||||
|
from jinja2.utils import Cycler
|
||||||
|
|
||||||
|
env = Environment()
|
||||||
|
|
||||||
|
|
||||||
|
class ExtendedAPITestCase(JinjaTestCase):
|
||||||
|
|
||||||
|
def test_item_and_attribute(self):
|
||||||
|
from jinja2.sandbox import SandboxedEnvironment
|
||||||
|
|
||||||
|
for env in Environment(), SandboxedEnvironment():
|
||||||
|
# the |list is necessary for python3
|
||||||
|
tmpl = env.from_string('{{ foo.items()|list }}')
|
||||||
|
assert tmpl.render(foo={'items': 42}) == "[('items', 42)]"
|
||||||
|
tmpl = env.from_string('{{ foo|attr("items")()|list }}')
|
||||||
|
assert tmpl.render(foo={'items': 42}) == "[('items', 42)]"
|
||||||
|
tmpl = env.from_string('{{ foo["items"] }}')
|
||||||
|
assert tmpl.render(foo={'items': 42}) == '42'
|
||||||
|
|
||||||
|
def test_finalizer(self):
|
||||||
|
def finalize_none_empty(value):
|
||||||
|
if value is None:
|
||||||
|
value = u''
|
||||||
|
return value
|
||||||
|
env = Environment(finalize=finalize_none_empty)
|
||||||
|
tmpl = env.from_string('{% for item in seq %}|{{ item }}{% endfor %}')
|
||||||
|
assert tmpl.render(seq=(None, 1, "foo")) == '||1|foo'
|
||||||
|
tmpl = env.from_string('<{{ none }}>')
|
||||||
|
assert tmpl.render() == '<>'
|
||||||
|
|
||||||
|
def test_cycler(self):
|
||||||
|
items = 1, 2, 3
|
||||||
|
c = Cycler(*items)
|
||||||
|
for item in items + items:
|
||||||
|
assert c.current == item
|
||||||
|
assert c.next() == item
|
||||||
|
c.next()
|
||||||
|
assert c.current == 2
|
||||||
|
c.reset()
|
||||||
|
assert c.current == 1
|
||||||
|
|
||||||
|
def test_expressions(self):
|
||||||
|
expr = env.compile_expression("foo")
|
||||||
|
assert expr() is None
|
||||||
|
assert expr(foo=42) == 42
|
||||||
|
expr2 = env.compile_expression("foo", undefined_to_none=False)
|
||||||
|
assert is_undefined(expr2())
|
||||||
|
|
||||||
|
expr = env.compile_expression("42 + foo")
|
||||||
|
assert expr(foo=42) == 84
|
||||||
|
|
||||||
|
def test_template_passthrough(self):
|
||||||
|
t = Template('Content')
|
||||||
|
assert env.get_template(t) is t
|
||||||
|
assert env.select_template([t]) is t
|
||||||
|
assert env.get_or_select_template([t]) is t
|
||||||
|
assert env.get_or_select_template(t) is t
|
||||||
|
|
||||||
|
def test_autoescape_autoselect(self):
|
||||||
|
def select_autoescape(name):
|
||||||
|
if name is None or '.' not in name:
|
||||||
|
return False
|
||||||
|
return name.endswith('.html')
|
||||||
|
env = Environment(autoescape=select_autoescape,
|
||||||
|
loader=DictLoader({
|
||||||
|
'test.txt': '{{ foo }}',
|
||||||
|
'test.html': '{{ foo }}'
|
||||||
|
}))
|
||||||
|
t = env.get_template('test.txt')
|
||||||
|
assert t.render(foo='<foo>') == '<foo>'
|
||||||
|
t = env.get_template('test.html')
|
||||||
|
assert t.render(foo='<foo>') == '<foo>'
|
||||||
|
t = env.from_string('{{ foo }}')
|
||||||
|
assert t.render(foo='<foo>') == '<foo>'
|
||||||
|
|
||||||
|
|
||||||
|
class MetaTestCase(JinjaTestCase):
|
||||||
|
|
||||||
|
def test_find_undeclared_variables(self):
|
||||||
|
ast = env.parse('{% set foo = 42 %}{{ bar + foo }}')
|
||||||
|
x = meta.find_undeclared_variables(ast)
|
||||||
|
assert x == set(['bar'])
|
||||||
|
|
||||||
|
ast = env.parse('{% set foo = 42 %}{{ bar + foo }}'
|
||||||
|
'{% macro meh(x) %}{{ x }}{% endmacro %}'
|
||||||
|
'{% for item in seq %}{{ muh(item) + meh(seq) }}{% endfor %}')
|
||||||
|
x = meta.find_undeclared_variables(ast)
|
||||||
|
assert x == set(['bar', 'seq', 'muh'])
|
||||||
|
|
||||||
|
def test_find_refererenced_templates(self):
|
||||||
|
ast = env.parse('{% extends "layout.html" %}{% include helper %}')
|
||||||
|
i = meta.find_referenced_templates(ast)
|
||||||
|
assert i.next() == 'layout.html'
|
||||||
|
assert i.next() is None
|
||||||
|
assert list(i) == []
|
||||||
|
|
||||||
|
ast = env.parse('{% extends "layout.html" %}'
|
||||||
|
'{% from "test.html" import a, b as c %}'
|
||||||
|
'{% import "meh.html" as meh %}'
|
||||||
|
'{% include "muh.html" %}')
|
||||||
|
i = meta.find_referenced_templates(ast)
|
||||||
|
assert list(i) == ['layout.html', 'test.html', 'meh.html', 'muh.html']
|
||||||
|
|
||||||
|
def test_find_included_templates(self):
|
||||||
|
ast = env.parse('{% include ["foo.html", "bar.html"] %}')
|
||||||
|
i = meta.find_referenced_templates(ast)
|
||||||
|
assert list(i) == ['foo.html', 'bar.html']
|
||||||
|
|
||||||
|
ast = env.parse('{% include ("foo.html", "bar.html") %}')
|
||||||
|
i = meta.find_referenced_templates(ast)
|
||||||
|
assert list(i) == ['foo.html', 'bar.html']
|
||||||
|
|
||||||
|
ast = env.parse('{% include ["foo.html", "bar.html", foo] %}')
|
||||||
|
i = meta.find_referenced_templates(ast)
|
||||||
|
assert list(i) == ['foo.html', 'bar.html', None]
|
||||||
|
|
||||||
|
ast = env.parse('{% include ("foo.html", "bar.html", foo) %}')
|
||||||
|
i = meta.find_referenced_templates(ast)
|
||||||
|
assert list(i) == ['foo.html', 'bar.html', None]
|
||||||
|
|
||||||
|
|
||||||
|
class StreamingTestCase(JinjaTestCase):
|
||||||
|
|
||||||
|
def test_basic_streaming(self):
|
||||||
|
tmpl = env.from_string("<ul>{% for item in seq %}<li>{{ loop.index "
|
||||||
|
"}} - {{ item }}</li>{%- endfor %}</ul>")
|
||||||
|
stream = tmpl.stream(seq=range(4))
|
||||||
|
self.assert_equal(stream.next(), '<ul>')
|
||||||
|
self.assert_equal(stream.next(), '<li>1 - 0</li>')
|
||||||
|
self.assert_equal(stream.next(), '<li>2 - 1</li>')
|
||||||
|
self.assert_equal(stream.next(), '<li>3 - 2</li>')
|
||||||
|
self.assert_equal(stream.next(), '<li>4 - 3</li>')
|
||||||
|
self.assert_equal(stream.next(), '</ul>')
|
||||||
|
|
||||||
|
def test_buffered_streaming(self):
|
||||||
|
tmpl = env.from_string("<ul>{% for item in seq %}<li>{{ loop.index "
|
||||||
|
"}} - {{ item }}</li>{%- endfor %}</ul>")
|
||||||
|
stream = tmpl.stream(seq=range(4))
|
||||||
|
stream.enable_buffering(size=3)
|
||||||
|
self.assert_equal(stream.next(), u'<ul><li>1 - 0</li><li>2 - 1</li>')
|
||||||
|
self.assert_equal(stream.next(), u'<li>3 - 2</li><li>4 - 3</li></ul>')
|
||||||
|
|
||||||
|
def test_streaming_behavior(self):
|
||||||
|
tmpl = env.from_string("")
|
||||||
|
stream = tmpl.stream()
|
||||||
|
assert not stream.buffered
|
||||||
|
stream.enable_buffering(20)
|
||||||
|
assert stream.buffered
|
||||||
|
stream.disable_buffering()
|
||||||
|
assert not stream.buffered
|
||||||
|
|
||||||
|
|
||||||
|
class UndefinedTestCase(JinjaTestCase):
|
||||||
|
|
||||||
|
def test_stopiteration_is_undefined(self):
|
||||||
|
def test():
|
||||||
|
raise StopIteration()
|
||||||
|
t = Template('A{{ test() }}B')
|
||||||
|
assert t.render(test=test) == 'AB'
|
||||||
|
t = Template('A{{ test().missingattribute }}B')
|
||||||
|
self.assert_raises(UndefinedError, t.render, test=test)
|
||||||
|
|
||||||
|
def test_undefined_and_special_attributes(self):
|
||||||
|
try:
|
||||||
|
Undefined('Foo').__dict__
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
assert False, "Expected actual attribute error"
|
||||||
|
|
||||||
|
def test_default_undefined(self):
|
||||||
|
env = Environment(undefined=Undefined)
|
||||||
|
self.assert_equal(env.from_string('{{ missing }}').render(), u'')
|
||||||
|
self.assert_raises(UndefinedError,
|
||||||
|
env.from_string('{{ missing.attribute }}').render)
|
||||||
|
self.assert_equal(env.from_string('{{ missing|list }}').render(), '[]')
|
||||||
|
self.assert_equal(env.from_string('{{ missing is not defined }}').render(), 'True')
|
||||||
|
self.assert_equal(env.from_string('{{ foo.missing }}').render(foo=42), '')
|
||||||
|
self.assert_equal(env.from_string('{{ not missing }}').render(), 'True')
|
||||||
|
|
||||||
|
def test_debug_undefined(self):
|
||||||
|
env = Environment(undefined=DebugUndefined)
|
||||||
|
self.assert_equal(env.from_string('{{ missing }}').render(), '{{ missing }}')
|
||||||
|
self.assert_raises(UndefinedError,
|
||||||
|
env.from_string('{{ missing.attribute }}').render)
|
||||||
|
self.assert_equal(env.from_string('{{ missing|list }}').render(), '[]')
|
||||||
|
self.assert_equal(env.from_string('{{ missing is not defined }}').render(), 'True')
|
||||||
|
self.assert_equal(env.from_string('{{ foo.missing }}').render(foo=42),
|
||||||
|
u"{{ no such element: int object['missing'] }}")
|
||||||
|
self.assert_equal(env.from_string('{{ not missing }}').render(), 'True')
|
||||||
|
|
||||||
|
def test_strict_undefined(self):
|
||||||
|
env = Environment(undefined=StrictUndefined)
|
||||||
|
self.assert_raises(UndefinedError, env.from_string('{{ missing }}').render)
|
||||||
|
self.assert_raises(UndefinedError, env.from_string('{{ missing.attribute }}').render)
|
||||||
|
self.assert_raises(UndefinedError, env.from_string('{{ missing|list }}').render)
|
||||||
|
self.assert_equal(env.from_string('{{ missing is not defined }}').render(), 'True')
|
||||||
|
self.assert_raises(UndefinedError, env.from_string('{{ foo.missing }}').render, foo=42)
|
||||||
|
self.assert_raises(UndefinedError, env.from_string('{{ not missing }}').render)
|
||||||
|
|
||||||
|
def test_indexing_gives_undefined(self):
|
||||||
|
t = Template("{{ var[42].foo }}")
|
||||||
|
self.assert_raises(UndefinedError, t.render, var=0)
|
||||||
|
|
||||||
|
def test_none_gives_proper_error(self):
|
||||||
|
try:
|
||||||
|
Environment().getattr(None, 'split')()
|
||||||
|
except UndefinedError, e:
|
||||||
|
assert e.message == "'None' has no attribute 'split'"
|
||||||
|
else:
|
||||||
|
assert False, 'expected exception'
|
||||||
|
|
||||||
|
def test_object_repr(self):
|
||||||
|
try:
|
||||||
|
Undefined(obj=42, name='upper')()
|
||||||
|
except UndefinedError, e:
|
||||||
|
assert e.message == "'int object' has no attribute 'upper'"
|
||||||
|
else:
|
||||||
|
assert False, 'expected exception'
|
||||||
|
|
||||||
|
|
||||||
|
def suite():
|
||||||
|
suite = unittest.TestSuite()
|
||||||
|
suite.addTest(unittest.makeSuite(ExtendedAPITestCase))
|
||||||
|
suite.addTest(unittest.makeSuite(MetaTestCase))
|
||||||
|
suite.addTest(unittest.makeSuite(StreamingTestCase))
|
||||||
|
suite.addTest(unittest.makeSuite(UndefinedTestCase))
|
||||||
|
return suite
|
||||||
Executable
+285
@@ -0,0 +1,285 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja2.testsuite.core_tags
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Test the core tags like for and if.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from jinja2.testsuite import JinjaTestCase
|
||||||
|
|
||||||
|
from jinja2 import Environment, TemplateSyntaxError, UndefinedError, \
|
||||||
|
DictLoader
|
||||||
|
|
||||||
|
env = Environment()
|
||||||
|
|
||||||
|
|
||||||
|
class ForLoopTestCase(JinjaTestCase):
|
||||||
|
|
||||||
|
def test_simple(self):
|
||||||
|
tmpl = env.from_string('{% for item in seq %}{{ item }}{% endfor %}')
|
||||||
|
assert tmpl.render(seq=range(10)) == '0123456789'
|
||||||
|
|
||||||
|
def test_else(self):
|
||||||
|
tmpl = env.from_string('{% for item in seq %}XXX{% else %}...{% endfor %}')
|
||||||
|
assert tmpl.render() == '...'
|
||||||
|
|
||||||
|
def test_empty_blocks(self):
|
||||||
|
tmpl = env.from_string('<{% for item in seq %}{% else %}{% endfor %}>')
|
||||||
|
assert tmpl.render() == '<>'
|
||||||
|
|
||||||
|
def test_context_vars(self):
|
||||||
|
tmpl = env.from_string('''{% for item in seq -%}
|
||||||
|
{{ loop.index }}|{{ loop.index0 }}|{{ loop.revindex }}|{{
|
||||||
|
loop.revindex0 }}|{{ loop.first }}|{{ loop.last }}|{{
|
||||||
|
loop.length }}###{% endfor %}''')
|
||||||
|
one, two, _ = tmpl.render(seq=[0, 1]).split('###')
|
||||||
|
(one_index, one_index0, one_revindex, one_revindex0, one_first,
|
||||||
|
one_last, one_length) = one.split('|')
|
||||||
|
(two_index, two_index0, two_revindex, two_revindex0, two_first,
|
||||||
|
two_last, two_length) = two.split('|')
|
||||||
|
|
||||||
|
assert int(one_index) == 1 and int(two_index) == 2
|
||||||
|
assert int(one_index0) == 0 and int(two_index0) == 1
|
||||||
|
assert int(one_revindex) == 2 and int(two_revindex) == 1
|
||||||
|
assert int(one_revindex0) == 1 and int(two_revindex0) == 0
|
||||||
|
assert one_first == 'True' and two_first == 'False'
|
||||||
|
assert one_last == 'False' and two_last == 'True'
|
||||||
|
assert one_length == two_length == '2'
|
||||||
|
|
||||||
|
def test_cycling(self):
|
||||||
|
tmpl = env.from_string('''{% for item in seq %}{{
|
||||||
|
loop.cycle('<1>', '<2>') }}{% endfor %}{%
|
||||||
|
for item in seq %}{{ loop.cycle(*through) }}{% endfor %}''')
|
||||||
|
output = tmpl.render(seq=range(4), through=('<1>', '<2>'))
|
||||||
|
assert output == '<1><2>' * 4
|
||||||
|
|
||||||
|
def test_scope(self):
|
||||||
|
tmpl = env.from_string('{% for item in seq %}{% endfor %}{{ item }}')
|
||||||
|
output = tmpl.render(seq=range(10))
|
||||||
|
assert not output
|
||||||
|
|
||||||
|
def test_varlen(self):
|
||||||
|
def inner():
|
||||||
|
for item in range(5):
|
||||||
|
yield item
|
||||||
|
tmpl = env.from_string('{% for item in iter %}{{ item }}{% endfor %}')
|
||||||
|
output = tmpl.render(iter=inner())
|
||||||
|
assert output == '01234'
|
||||||
|
|
||||||
|
def test_noniter(self):
|
||||||
|
tmpl = env.from_string('{% for item in none %}...{% endfor %}')
|
||||||
|
self.assert_raises(TypeError, tmpl.render)
|
||||||
|
|
||||||
|
def test_recursive(self):
|
||||||
|
tmpl = env.from_string('''{% for item in seq recursive -%}
|
||||||
|
[{{ item.a }}{% if item.b %}<{{ loop(item.b) }}>{% endif %}]
|
||||||
|
{%- endfor %}''')
|
||||||
|
assert tmpl.render(seq=[
|
||||||
|
dict(a=1, b=[dict(a=1), dict(a=2)]),
|
||||||
|
dict(a=2, b=[dict(a=1), dict(a=2)]),
|
||||||
|
dict(a=3, b=[dict(a='a')])
|
||||||
|
]) == '[1<[1][2]>][2<[1][2]>][3<[a]>]'
|
||||||
|
|
||||||
|
def test_looploop(self):
|
||||||
|
tmpl = env.from_string('''{% for row in table %}
|
||||||
|
{%- set rowloop = loop -%}
|
||||||
|
{% for cell in row -%}
|
||||||
|
[{{ rowloop.index }}|{{ loop.index }}]
|
||||||
|
{%- endfor %}
|
||||||
|
{%- endfor %}''')
|
||||||
|
assert tmpl.render(table=['ab', 'cd']) == '[1|1][1|2][2|1][2|2]'
|
||||||
|
|
||||||
|
def test_reversed_bug(self):
|
||||||
|
tmpl = env.from_string('{% for i in items %}{{ i }}'
|
||||||
|
'{% if not loop.last %}'
|
||||||
|
',{% endif %}{% endfor %}')
|
||||||
|
assert tmpl.render(items=reversed([3, 2, 1])) == '1,2,3'
|
||||||
|
|
||||||
|
def test_loop_errors(self):
|
||||||
|
tmpl = env.from_string('''{% for item in [1] if loop.index
|
||||||
|
== 0 %}...{% endfor %}''')
|
||||||
|
self.assert_raises(UndefinedError, tmpl.render)
|
||||||
|
tmpl = env.from_string('''{% for item in [] %}...{% else
|
||||||
|
%}{{ loop }}{% endfor %}''')
|
||||||
|
assert tmpl.render() == ''
|
||||||
|
|
||||||
|
def test_loop_filter(self):
|
||||||
|
tmpl = env.from_string('{% for item in range(10) if item '
|
||||||
|
'is even %}[{{ item }}]{% endfor %}')
|
||||||
|
assert tmpl.render() == '[0][2][4][6][8]'
|
||||||
|
tmpl = env.from_string('''
|
||||||
|
{%- for item in range(10) if item is even %}[{{
|
||||||
|
loop.index }}:{{ item }}]{% endfor %}''')
|
||||||
|
assert tmpl.render() == '[1:0][2:2][3:4][4:6][5:8]'
|
||||||
|
|
||||||
|
def test_loop_unassignable(self):
|
||||||
|
self.assert_raises(TemplateSyntaxError, env.from_string,
|
||||||
|
'{% for loop in seq %}...{% endfor %}')
|
||||||
|
|
||||||
|
def test_scoped_special_var(self):
|
||||||
|
t = env.from_string('{% for s in seq %}[{{ loop.first }}{% for c in s %}'
|
||||||
|
'|{{ loop.first }}{% endfor %}]{% endfor %}')
|
||||||
|
assert t.render(seq=('ab', 'cd')) == '[True|True|False][False|True|False]'
|
||||||
|
|
||||||
|
def test_scoped_loop_var(self):
|
||||||
|
t = env.from_string('{% for x in seq %}{{ loop.first }}'
|
||||||
|
'{% for y in seq %}{% endfor %}{% endfor %}')
|
||||||
|
assert t.render(seq='ab') == 'TrueFalse'
|
||||||
|
t = env.from_string('{% for x in seq %}{% for y in seq %}'
|
||||||
|
'{{ loop.first }}{% endfor %}{% endfor %}')
|
||||||
|
assert t.render(seq='ab') == 'TrueFalseTrueFalse'
|
||||||
|
|
||||||
|
def test_recursive_empty_loop_iter(self):
|
||||||
|
t = env.from_string('''
|
||||||
|
{%- for item in foo recursive -%}{%- endfor -%}
|
||||||
|
''')
|
||||||
|
assert t.render(dict(foo=[])) == ''
|
||||||
|
|
||||||
|
def test_call_in_loop(self):
|
||||||
|
t = env.from_string('''
|
||||||
|
{%- macro do_something() -%}
|
||||||
|
[{{ caller() }}]
|
||||||
|
{%- endmacro %}
|
||||||
|
|
||||||
|
{%- for i in [1, 2, 3] %}
|
||||||
|
{%- call do_something() -%}
|
||||||
|
{{ i }}
|
||||||
|
{%- endcall %}
|
||||||
|
{%- endfor -%}
|
||||||
|
''')
|
||||||
|
assert t.render() == '[1][2][3]'
|
||||||
|
|
||||||
|
def test_scoping_bug(self):
|
||||||
|
t = env.from_string('''
|
||||||
|
{%- for item in foo %}...{{ item }}...{% endfor %}
|
||||||
|
{%- macro item(a) %}...{{ a }}...{% endmacro %}
|
||||||
|
{{- item(2) -}}
|
||||||
|
''')
|
||||||
|
assert t.render(foo=(1,)) == '...1......2...'
|
||||||
|
|
||||||
|
def test_unpacking(self):
|
||||||
|
tmpl = env.from_string('{% for a, b, c in [[1, 2, 3]] %}'
|
||||||
|
'{{ a }}|{{ b }}|{{ c }}{% endfor %}')
|
||||||
|
assert tmpl.render() == '1|2|3'
|
||||||
|
|
||||||
|
|
||||||
|
class IfConditionTestCase(JinjaTestCase):
|
||||||
|
|
||||||
|
def test_simple(self):
|
||||||
|
tmpl = env.from_string('''{% if true %}...{% endif %}''')
|
||||||
|
assert tmpl.render() == '...'
|
||||||
|
|
||||||
|
def test_elif(self):
|
||||||
|
tmpl = env.from_string('''{% if false %}XXX{% elif true
|
||||||
|
%}...{% else %}XXX{% endif %}''')
|
||||||
|
assert tmpl.render() == '...'
|
||||||
|
|
||||||
|
def test_else(self):
|
||||||
|
tmpl = env.from_string('{% if false %}XXX{% else %}...{% endif %}')
|
||||||
|
assert tmpl.render() == '...'
|
||||||
|
|
||||||
|
def test_empty(self):
|
||||||
|
tmpl = env.from_string('[{% if true %}{% else %}{% endif %}]')
|
||||||
|
assert tmpl.render() == '[]'
|
||||||
|
|
||||||
|
def test_complete(self):
|
||||||
|
tmpl = env.from_string('{% if a %}A{% elif b %}B{% elif c == d %}'
|
||||||
|
'C{% else %}D{% endif %}')
|
||||||
|
assert tmpl.render(a=0, b=False, c=42, d=42.0) == 'C'
|
||||||
|
|
||||||
|
def test_no_scope(self):
|
||||||
|
tmpl = env.from_string('{% if a %}{% set foo = 1 %}{% endif %}{{ foo }}')
|
||||||
|
assert tmpl.render(a=True) == '1'
|
||||||
|
tmpl = env.from_string('{% if true %}{% set foo = 1 %}{% endif %}{{ foo }}')
|
||||||
|
assert tmpl.render() == '1'
|
||||||
|
|
||||||
|
|
||||||
|
class MacrosTestCase(JinjaTestCase):
|
||||||
|
env = Environment(trim_blocks=True)
|
||||||
|
|
||||||
|
def test_simple(self):
|
||||||
|
tmpl = self.env.from_string('''\
|
||||||
|
{% macro say_hello(name) %}Hello {{ name }}!{% endmacro %}
|
||||||
|
{{ say_hello('Peter') }}''')
|
||||||
|
assert tmpl.render() == 'Hello Peter!'
|
||||||
|
|
||||||
|
def test_scoping(self):
|
||||||
|
tmpl = self.env.from_string('''\
|
||||||
|
{% macro level1(data1) %}
|
||||||
|
{% macro level2(data2) %}{{ data1 }}|{{ data2 }}{% endmacro %}
|
||||||
|
{{ level2('bar') }}{% endmacro %}
|
||||||
|
{{ level1('foo') }}''')
|
||||||
|
assert tmpl.render() == 'foo|bar'
|
||||||
|
|
||||||
|
def test_arguments(self):
|
||||||
|
tmpl = self.env.from_string('''\
|
||||||
|
{% macro m(a, b, c='c', d='d') %}{{ a }}|{{ b }}|{{ c }}|{{ d }}{% endmacro %}
|
||||||
|
{{ m() }}|{{ m('a') }}|{{ m('a', 'b') }}|{{ m(1, 2, 3) }}''')
|
||||||
|
assert tmpl.render() == '||c|d|a||c|d|a|b|c|d|1|2|3|d'
|
||||||
|
|
||||||
|
def test_varargs(self):
|
||||||
|
tmpl = self.env.from_string('''\
|
||||||
|
{% macro test() %}{{ varargs|join('|') }}{% endmacro %}\
|
||||||
|
{{ test(1, 2, 3) }}''')
|
||||||
|
assert tmpl.render() == '1|2|3'
|
||||||
|
|
||||||
|
def test_simple_call(self):
|
||||||
|
tmpl = self.env.from_string('''\
|
||||||
|
{% macro test() %}[[{{ caller() }}]]{% endmacro %}\
|
||||||
|
{% call test() %}data{% endcall %}''')
|
||||||
|
assert tmpl.render() == '[[data]]'
|
||||||
|
|
||||||
|
def test_complex_call(self):
|
||||||
|
tmpl = self.env.from_string('''\
|
||||||
|
{% macro test() %}[[{{ caller('data') }}]]{% endmacro %}\
|
||||||
|
{% call(data) test() %}{{ data }}{% endcall %}''')
|
||||||
|
assert tmpl.render() == '[[data]]'
|
||||||
|
|
||||||
|
def test_caller_undefined(self):
|
||||||
|
tmpl = self.env.from_string('''\
|
||||||
|
{% set caller = 42 %}\
|
||||||
|
{% macro test() %}{{ caller is not defined }}{% endmacro %}\
|
||||||
|
{{ test() }}''')
|
||||||
|
assert tmpl.render() == 'True'
|
||||||
|
|
||||||
|
def test_include(self):
|
||||||
|
self.env = Environment(loader=DictLoader({'include':
|
||||||
|
'{% macro test(foo) %}[{{ foo }}]{% endmacro %}'}))
|
||||||
|
tmpl = self.env.from_string('{% from "include" import test %}{{ test("foo") }}')
|
||||||
|
assert tmpl.render() == '[foo]'
|
||||||
|
|
||||||
|
def test_macro_api(self):
|
||||||
|
tmpl = self.env.from_string('{% macro foo(a, b) %}{% endmacro %}'
|
||||||
|
'{% macro bar() %}{{ varargs }}{{ kwargs }}{% endmacro %}'
|
||||||
|
'{% macro baz() %}{{ caller() }}{% endmacro %}')
|
||||||
|
assert tmpl.module.foo.arguments == ('a', 'b')
|
||||||
|
assert tmpl.module.foo.defaults == ()
|
||||||
|
assert tmpl.module.foo.name == 'foo'
|
||||||
|
assert not tmpl.module.foo.caller
|
||||||
|
assert not tmpl.module.foo.catch_kwargs
|
||||||
|
assert not tmpl.module.foo.catch_varargs
|
||||||
|
assert tmpl.module.bar.arguments == ()
|
||||||
|
assert tmpl.module.bar.defaults == ()
|
||||||
|
assert not tmpl.module.bar.caller
|
||||||
|
assert tmpl.module.bar.catch_kwargs
|
||||||
|
assert tmpl.module.bar.catch_varargs
|
||||||
|
assert tmpl.module.baz.caller
|
||||||
|
|
||||||
|
def test_callself(self):
|
||||||
|
tmpl = self.env.from_string('{% macro foo(x) %}{{ x }}{% if x > 1 %}|'
|
||||||
|
'{{ foo(x - 1) }}{% endif %}{% endmacro %}'
|
||||||
|
'{{ foo(5) }}')
|
||||||
|
assert tmpl.render() == '5|4|3|2|1'
|
||||||
|
|
||||||
|
|
||||||
|
def suite():
|
||||||
|
suite = unittest.TestSuite()
|
||||||
|
suite.addTest(unittest.makeSuite(ForLoopTestCase))
|
||||||
|
suite.addTest(unittest.makeSuite(IfConditionTestCase))
|
||||||
|
suite.addTest(unittest.makeSuite(MacrosTestCase))
|
||||||
|
return suite
|
||||||
Executable
+60
@@ -0,0 +1,60 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja2.testsuite.debug
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tests the debug system.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from jinja2.testsuite import JinjaTestCase, filesystem_loader
|
||||||
|
|
||||||
|
from jinja2 import Environment, TemplateSyntaxError
|
||||||
|
|
||||||
|
env = Environment(loader=filesystem_loader)
|
||||||
|
|
||||||
|
|
||||||
|
class DebugTestCase(JinjaTestCase):
|
||||||
|
|
||||||
|
if sys.version_info[:2] != (2, 4):
|
||||||
|
def test_runtime_error(self):
|
||||||
|
def test():
|
||||||
|
tmpl.render(fail=lambda: 1 / 0)
|
||||||
|
tmpl = env.get_template('broken.html')
|
||||||
|
self.assert_traceback_matches(test, r'''
|
||||||
|
File ".*?broken.html", line 2, in (top-level template code|<module>)
|
||||||
|
\{\{ fail\(\) \}\}
|
||||||
|
File ".*?debug.pyc?", line \d+, in <lambda>
|
||||||
|
tmpl\.render\(fail=lambda: 1 / 0\)
|
||||||
|
ZeroDivisionError: (int(eger)? )?division (or modulo )?by zero
|
||||||
|
''')
|
||||||
|
|
||||||
|
def test_syntax_error(self):
|
||||||
|
# XXX: the .*? is necessary for python3 which does not hide
|
||||||
|
# some of the stack frames we don't want to show. Not sure
|
||||||
|
# what's up with that, but that is not that critical. Should
|
||||||
|
# be fixed though.
|
||||||
|
self.assert_traceback_matches(lambda: env.get_template('syntaxerror.html'), r'''(?sm)
|
||||||
|
File ".*?syntaxerror.html", line 4, in (template|<module>)
|
||||||
|
\{% endif %\}.*?
|
||||||
|
(jinja2\.exceptions\.)?TemplateSyntaxError: Encountered unknown tag 'endif'. Jinja was looking for the following tags: 'endfor' or 'else'. The innermost block that needs to be closed is 'for'.
|
||||||
|
''')
|
||||||
|
|
||||||
|
def test_regular_syntax_error(self):
|
||||||
|
def test():
|
||||||
|
raise TemplateSyntaxError('wtf', 42)
|
||||||
|
self.assert_traceback_matches(test, r'''
|
||||||
|
File ".*debug.pyc?", line \d+, in test
|
||||||
|
raise TemplateSyntaxError\('wtf', 42\)
|
||||||
|
(jinja2\.exceptions\.)?TemplateSyntaxError: wtf
|
||||||
|
line 42''')
|
||||||
|
|
||||||
|
|
||||||
|
def suite():
|
||||||
|
suite = unittest.TestSuite()
|
||||||
|
suite.addTest(unittest.makeSuite(DebugTestCase))
|
||||||
|
return suite
|
||||||
Executable
+29
@@ -0,0 +1,29 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja2.testsuite.doctests
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The doctests. Collects all tests we want to test from
|
||||||
|
the Jinja modules.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
import unittest
|
||||||
|
import doctest
|
||||||
|
|
||||||
|
|
||||||
|
def suite():
|
||||||
|
from jinja2 import utils, sandbox, runtime, meta, loaders, \
|
||||||
|
ext, environment, bccache, nodes
|
||||||
|
suite = unittest.TestSuite()
|
||||||
|
suite.addTest(doctest.DocTestSuite(utils))
|
||||||
|
suite.addTest(doctest.DocTestSuite(sandbox))
|
||||||
|
suite.addTest(doctest.DocTestSuite(runtime))
|
||||||
|
suite.addTest(doctest.DocTestSuite(meta))
|
||||||
|
suite.addTest(doctest.DocTestSuite(loaders))
|
||||||
|
suite.addTest(doctest.DocTestSuite(ext))
|
||||||
|
suite.addTest(doctest.DocTestSuite(environment))
|
||||||
|
suite.addTest(doctest.DocTestSuite(bccache))
|
||||||
|
suite.addTest(doctest.DocTestSuite(nodes))
|
||||||
|
return suite
|
||||||
Executable
+455
@@ -0,0 +1,455 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja2.testsuite.ext
|
||||||
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tests for the extensions.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from jinja2.testsuite import JinjaTestCase
|
||||||
|
|
||||||
|
from jinja2 import Environment, DictLoader, contextfunction, nodes
|
||||||
|
from jinja2.exceptions import TemplateAssertionError
|
||||||
|
from jinja2.ext import Extension
|
||||||
|
from jinja2.lexer import Token, count_newlines
|
||||||
|
from jinja2.utils import next
|
||||||
|
|
||||||
|
# 2.x / 3.x
|
||||||
|
try:
|
||||||
|
from io import BytesIO
|
||||||
|
except ImportError:
|
||||||
|
from StringIO import StringIO as BytesIO
|
||||||
|
|
||||||
|
|
||||||
|
importable_object = 23
|
||||||
|
|
||||||
|
_gettext_re = re.compile(r'_\((.*?)\)(?s)')
|
||||||
|
|
||||||
|
|
||||||
|
i18n_templates = {
|
||||||
|
'master.html': '<title>{{ page_title|default(_("missing")) }}</title>'
|
||||||
|
'{% block body %}{% endblock %}',
|
||||||
|
'child.html': '{% extends "master.html" %}{% block body %}'
|
||||||
|
'{% trans %}watch out{% endtrans %}{% endblock %}',
|
||||||
|
'plural.html': '{% trans user_count %}One user online{% pluralize %}'
|
||||||
|
'{{ user_count }} users online{% endtrans %}',
|
||||||
|
'stringformat.html': '{{ _("User: %(num)s")|format(num=user_count) }}'
|
||||||
|
}
|
||||||
|
|
||||||
|
newstyle_i18n_templates = {
|
||||||
|
'master.html': '<title>{{ page_title|default(_("missing")) }}</title>'
|
||||||
|
'{% block body %}{% endblock %}',
|
||||||
|
'child.html': '{% extends "master.html" %}{% block body %}'
|
||||||
|
'{% trans %}watch out{% endtrans %}{% endblock %}',
|
||||||
|
'plural.html': '{% trans user_count %}One user online{% pluralize %}'
|
||||||
|
'{{ user_count }} users online{% endtrans %}',
|
||||||
|
'stringformat.html': '{{ _("User: %(num)s", num=user_count) }}',
|
||||||
|
'ngettext.html': '{{ ngettext("%(num)s apple", "%(num)s apples", apples) }}',
|
||||||
|
'ngettext_long.html': '{% trans num=apples %}{{ num }} apple{% pluralize %}'
|
||||||
|
'{{ num }} apples{% endtrans %}',
|
||||||
|
'transvars1.html': '{% trans %}User: {{ num }}{% endtrans %}',
|
||||||
|
'transvars2.html': '{% trans num=count %}User: {{ num }}{% endtrans %}',
|
||||||
|
'transvars3.html': '{% trans count=num %}User: {{ count }}{% endtrans %}',
|
||||||
|
'novars.html': '{% trans %}%(hello)s{% endtrans %}',
|
||||||
|
'vars.html': '{% trans %}{{ foo }}%(foo)s{% endtrans %}',
|
||||||
|
'explicitvars.html': '{% trans foo="42" %}%(foo)s{% endtrans %}'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
languages = {
|
||||||
|
'de': {
|
||||||
|
'missing': u'fehlend',
|
||||||
|
'watch out': u'pass auf',
|
||||||
|
'One user online': u'Ein Benutzer online',
|
||||||
|
'%(user_count)s users online': u'%(user_count)s Benutzer online',
|
||||||
|
'User: %(num)s': u'Benutzer: %(num)s',
|
||||||
|
'User: %(count)s': u'Benutzer: %(count)s',
|
||||||
|
'%(num)s apple': u'%(num)s Apfel',
|
||||||
|
'%(num)s apples': u'%(num)s Äpfel'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@contextfunction
|
||||||
|
def gettext(context, string):
|
||||||
|
language = context.get('LANGUAGE', 'en')
|
||||||
|
return languages.get(language, {}).get(string, string)
|
||||||
|
|
||||||
|
|
||||||
|
@contextfunction
|
||||||
|
def ngettext(context, s, p, n):
|
||||||
|
language = context.get('LANGUAGE', 'en')
|
||||||
|
if n != 1:
|
||||||
|
return languages.get(language, {}).get(p, p)
|
||||||
|
return languages.get(language, {}).get(s, s)
|
||||||
|
|
||||||
|
|
||||||
|
i18n_env = Environment(
|
||||||
|
loader=DictLoader(i18n_templates),
|
||||||
|
extensions=['jinja2.ext.i18n']
|
||||||
|
)
|
||||||
|
i18n_env.globals.update({
|
||||||
|
'_': gettext,
|
||||||
|
'gettext': gettext,
|
||||||
|
'ngettext': ngettext
|
||||||
|
})
|
||||||
|
|
||||||
|
newstyle_i18n_env = Environment(
|
||||||
|
loader=DictLoader(newstyle_i18n_templates),
|
||||||
|
extensions=['jinja2.ext.i18n']
|
||||||
|
)
|
||||||
|
newstyle_i18n_env.install_gettext_callables(gettext, ngettext, newstyle=True)
|
||||||
|
|
||||||
|
class TestExtension(Extension):
|
||||||
|
tags = set(['test'])
|
||||||
|
ext_attr = 42
|
||||||
|
|
||||||
|
def parse(self, parser):
|
||||||
|
return nodes.Output([self.call_method('_dump', [
|
||||||
|
nodes.EnvironmentAttribute('sandboxed'),
|
||||||
|
self.attr('ext_attr'),
|
||||||
|
nodes.ImportedName(__name__ + '.importable_object'),
|
||||||
|
nodes.ContextReference()
|
||||||
|
])]).set_lineno(next(parser.stream).lineno)
|
||||||
|
|
||||||
|
def _dump(self, sandboxed, ext_attr, imported_object, context):
|
||||||
|
return '%s|%s|%s|%s' % (
|
||||||
|
sandboxed,
|
||||||
|
ext_attr,
|
||||||
|
imported_object,
|
||||||
|
context.blocks
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PreprocessorExtension(Extension):
|
||||||
|
|
||||||
|
def preprocess(self, source, name, filename=None):
|
||||||
|
return source.replace('[[TEST]]', '({{ foo }})')
|
||||||
|
|
||||||
|
|
||||||
|
class StreamFilterExtension(Extension):
|
||||||
|
|
||||||
|
def filter_stream(self, stream):
|
||||||
|
for token in stream:
|
||||||
|
if token.type == 'data':
|
||||||
|
for t in self.interpolate(token):
|
||||||
|
yield t
|
||||||
|
else:
|
||||||
|
yield token
|
||||||
|
|
||||||
|
def interpolate(self, token):
|
||||||
|
pos = 0
|
||||||
|
end = len(token.value)
|
||||||
|
lineno = token.lineno
|
||||||
|
while 1:
|
||||||
|
match = _gettext_re.search(token.value, pos)
|
||||||
|
if match is None:
|
||||||
|
break
|
||||||
|
value = token.value[pos:match.start()]
|
||||||
|
if value:
|
||||||
|
yield Token(lineno, 'data', value)
|
||||||
|
lineno += count_newlines(token.value)
|
||||||
|
yield Token(lineno, 'variable_begin', None)
|
||||||
|
yield Token(lineno, 'name', 'gettext')
|
||||||
|
yield Token(lineno, 'lparen', None)
|
||||||
|
yield Token(lineno, 'string', match.group(1))
|
||||||
|
yield Token(lineno, 'rparen', None)
|
||||||
|
yield Token(lineno, 'variable_end', None)
|
||||||
|
pos = match.end()
|
||||||
|
if pos < end:
|
||||||
|
yield Token(lineno, 'data', token.value[pos:])
|
||||||
|
|
||||||
|
|
||||||
|
class ExtensionsTestCase(JinjaTestCase):
|
||||||
|
|
||||||
|
def test_extend_late(self):
|
||||||
|
env = Environment()
|
||||||
|
env.add_extension('jinja2.ext.autoescape')
|
||||||
|
t = env.from_string('{% autoescape true %}{{ "<test>" }}{% endautoescape %}')
|
||||||
|
assert t.render() == '<test>'
|
||||||
|
|
||||||
|
def test_loop_controls(self):
|
||||||
|
env = Environment(extensions=['jinja2.ext.loopcontrols'])
|
||||||
|
|
||||||
|
tmpl = env.from_string('''
|
||||||
|
{%- for item in [1, 2, 3, 4] %}
|
||||||
|
{%- if item % 2 == 0 %}{% continue %}{% endif -%}
|
||||||
|
{{ item }}
|
||||||
|
{%- endfor %}''')
|
||||||
|
assert tmpl.render() == '13'
|
||||||
|
|
||||||
|
tmpl = env.from_string('''
|
||||||
|
{%- for item in [1, 2, 3, 4] %}
|
||||||
|
{%- if item > 2 %}{% break %}{% endif -%}
|
||||||
|
{{ item }}
|
||||||
|
{%- endfor %}''')
|
||||||
|
assert tmpl.render() == '12'
|
||||||
|
|
||||||
|
def test_do(self):
|
||||||
|
env = Environment(extensions=['jinja2.ext.do'])
|
||||||
|
tmpl = env.from_string('''
|
||||||
|
{%- set items = [] %}
|
||||||
|
{%- for char in "foo" %}
|
||||||
|
{%- do items.append(loop.index0 ~ char) %}
|
||||||
|
{%- endfor %}{{ items|join(', ') }}''')
|
||||||
|
assert tmpl.render() == '0f, 1o, 2o'
|
||||||
|
|
||||||
|
def test_with(self):
|
||||||
|
env = Environment(extensions=['jinja2.ext.with_'])
|
||||||
|
tmpl = env.from_string('''\
|
||||||
|
{% with a=42, b=23 -%}
|
||||||
|
{{ a }} = {{ b }}
|
||||||
|
{% endwith -%}
|
||||||
|
{{ a }} = {{ b }}\
|
||||||
|
''')
|
||||||
|
assert [x.strip() for x in tmpl.render(a=1, b=2).splitlines()] \
|
||||||
|
== ['42 = 23', '1 = 2']
|
||||||
|
|
||||||
|
def test_extension_nodes(self):
|
||||||
|
env = Environment(extensions=[TestExtension])
|
||||||
|
tmpl = env.from_string('{% test %}')
|
||||||
|
assert tmpl.render() == 'False|42|23|{}'
|
||||||
|
|
||||||
|
def test_identifier(self):
|
||||||
|
assert TestExtension.identifier == __name__ + '.TestExtension'
|
||||||
|
|
||||||
|
def test_rebinding(self):
|
||||||
|
original = Environment(extensions=[TestExtension])
|
||||||
|
overlay = original.overlay()
|
||||||
|
for env in original, overlay:
|
||||||
|
for ext in env.extensions.itervalues():
|
||||||
|
assert ext.environment is env
|
||||||
|
|
||||||
|
def test_preprocessor_extension(self):
|
||||||
|
env = Environment(extensions=[PreprocessorExtension])
|
||||||
|
tmpl = env.from_string('{[[TEST]]}')
|
||||||
|
assert tmpl.render(foo=42) == '{(42)}'
|
||||||
|
|
||||||
|
def test_streamfilter_extension(self):
|
||||||
|
env = Environment(extensions=[StreamFilterExtension])
|
||||||
|
env.globals['gettext'] = lambda x: x.upper()
|
||||||
|
tmpl = env.from_string('Foo _(bar) Baz')
|
||||||
|
out = tmpl.render()
|
||||||
|
assert out == 'Foo BAR Baz'
|
||||||
|
|
||||||
|
def test_extension_ordering(self):
|
||||||
|
class T1(Extension):
|
||||||
|
priority = 1
|
||||||
|
class T2(Extension):
|
||||||
|
priority = 2
|
||||||
|
env = Environment(extensions=[T1, T2])
|
||||||
|
ext = list(env.iter_extensions())
|
||||||
|
assert ext[0].__class__ is T1
|
||||||
|
assert ext[1].__class__ is T2
|
||||||
|
|
||||||
|
|
||||||
|
class InternationalizationTestCase(JinjaTestCase):
|
||||||
|
|
||||||
|
def test_trans(self):
|
||||||
|
tmpl = i18n_env.get_template('child.html')
|
||||||
|
assert tmpl.render(LANGUAGE='de') == '<title>fehlend</title>pass auf'
|
||||||
|
|
||||||
|
def test_trans_plural(self):
|
||||||
|
tmpl = i18n_env.get_template('plural.html')
|
||||||
|
assert tmpl.render(LANGUAGE='de', user_count=1) == 'Ein Benutzer online'
|
||||||
|
assert tmpl.render(LANGUAGE='de', user_count=2) == '2 Benutzer online'
|
||||||
|
|
||||||
|
def test_complex_plural(self):
|
||||||
|
tmpl = i18n_env.from_string('{% trans foo=42, count=2 %}{{ count }} item{% '
|
||||||
|
'pluralize count %}{{ count }} items{% endtrans %}')
|
||||||
|
assert tmpl.render() == '2 items'
|
||||||
|
self.assert_raises(TemplateAssertionError, i18n_env.from_string,
|
||||||
|
'{% trans foo %}...{% pluralize bar %}...{% endtrans %}')
|
||||||
|
|
||||||
|
def test_trans_stringformatting(self):
|
||||||
|
tmpl = i18n_env.get_template('stringformat.html')
|
||||||
|
assert tmpl.render(LANGUAGE='de', user_count=5) == 'Benutzer: 5'
|
||||||
|
|
||||||
|
def test_extract(self):
|
||||||
|
from jinja2.ext import babel_extract
|
||||||
|
source = BytesIO('''
|
||||||
|
{{ gettext('Hello World') }}
|
||||||
|
{% trans %}Hello World{% endtrans %}
|
||||||
|
{% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %}
|
||||||
|
'''.encode('ascii')) # make python 3 happy
|
||||||
|
assert list(babel_extract(source, ('gettext', 'ngettext', '_'), [], {})) == [
|
||||||
|
(2, 'gettext', u'Hello World', []),
|
||||||
|
(3, 'gettext', u'Hello World', []),
|
||||||
|
(4, 'ngettext', (u'%(users)s user', u'%(users)s users', None), [])
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_comment_extract(self):
|
||||||
|
from jinja2.ext import babel_extract
|
||||||
|
source = BytesIO('''
|
||||||
|
{# trans first #}
|
||||||
|
{{ gettext('Hello World') }}
|
||||||
|
{% trans %}Hello World{% endtrans %}{# trans second #}
|
||||||
|
{#: third #}
|
||||||
|
{% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %}
|
||||||
|
'''.encode('utf-8')) # make python 3 happy
|
||||||
|
assert list(babel_extract(source, ('gettext', 'ngettext', '_'), ['trans', ':'], {})) == [
|
||||||
|
(3, 'gettext', u'Hello World', ['first']),
|
||||||
|
(4, 'gettext', u'Hello World', ['second']),
|
||||||
|
(6, 'ngettext', (u'%(users)s user', u'%(users)s users', None), ['third'])
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class NewstyleInternationalizationTestCase(JinjaTestCase):
|
||||||
|
|
||||||
|
def test_trans(self):
|
||||||
|
tmpl = newstyle_i18n_env.get_template('child.html')
|
||||||
|
assert tmpl.render(LANGUAGE='de') == '<title>fehlend</title>pass auf'
|
||||||
|
|
||||||
|
def test_trans_plural(self):
|
||||||
|
tmpl = newstyle_i18n_env.get_template('plural.html')
|
||||||
|
assert tmpl.render(LANGUAGE='de', user_count=1) == 'Ein Benutzer online'
|
||||||
|
assert tmpl.render(LANGUAGE='de', user_count=2) == '2 Benutzer online'
|
||||||
|
|
||||||
|
def test_complex_plural(self):
|
||||||
|
tmpl = newstyle_i18n_env.from_string('{% trans foo=42, count=2 %}{{ count }} item{% '
|
||||||
|
'pluralize count %}{{ count }} items{% endtrans %}')
|
||||||
|
assert tmpl.render() == '2 items'
|
||||||
|
self.assert_raises(TemplateAssertionError, i18n_env.from_string,
|
||||||
|
'{% trans foo %}...{% pluralize bar %}...{% endtrans %}')
|
||||||
|
|
||||||
|
def test_trans_stringformatting(self):
|
||||||
|
tmpl = newstyle_i18n_env.get_template('stringformat.html')
|
||||||
|
assert tmpl.render(LANGUAGE='de', user_count=5) == 'Benutzer: 5'
|
||||||
|
|
||||||
|
def test_newstyle_plural(self):
|
||||||
|
tmpl = newstyle_i18n_env.get_template('ngettext.html')
|
||||||
|
assert tmpl.render(LANGUAGE='de', apples=1) == '1 Apfel'
|
||||||
|
assert tmpl.render(LANGUAGE='de', apples=5) == u'5 Äpfel'
|
||||||
|
|
||||||
|
def test_autoescape_support(self):
|
||||||
|
env = Environment(extensions=['jinja2.ext.autoescape',
|
||||||
|
'jinja2.ext.i18n'])
|
||||||
|
env.install_gettext_callables(lambda x: u'<strong>Wert: %(name)s</strong>',
|
||||||
|
lambda s, p, n: s, newstyle=True)
|
||||||
|
t = env.from_string('{% autoescape ae %}{{ gettext("foo", name='
|
||||||
|
'"<test>") }}{% endautoescape %}')
|
||||||
|
assert t.render(ae=True) == '<strong>Wert: <test></strong>'
|
||||||
|
assert t.render(ae=False) == '<strong>Wert: <test></strong>'
|
||||||
|
|
||||||
|
def test_num_used_twice(self):
|
||||||
|
tmpl = newstyle_i18n_env.get_template('ngettext_long.html')
|
||||||
|
assert tmpl.render(apples=5, LANGUAGE='de') == u'5 Äpfel'
|
||||||
|
|
||||||
|
def test_num_called_num(self):
|
||||||
|
source = newstyle_i18n_env.compile('''
|
||||||
|
{% trans num=3 %}{{ num }} apple{% pluralize
|
||||||
|
%}{{ num }} apples{% endtrans %}
|
||||||
|
''', raw=True)
|
||||||
|
# quite hacky, but the only way to properly test that. The idea is
|
||||||
|
# that the generated code does not pass num twice (although that
|
||||||
|
# would work) for better performance. This only works on the
|
||||||
|
# newstyle gettext of course
|
||||||
|
assert re.search(r"l_ngettext, u?'\%\(num\)s apple', u?'\%\(num\)s "
|
||||||
|
r"apples', 3", source) is not None
|
||||||
|
|
||||||
|
def test_trans_vars(self):
|
||||||
|
t1 = newstyle_i18n_env.get_template('transvars1.html')
|
||||||
|
t2 = newstyle_i18n_env.get_template('transvars2.html')
|
||||||
|
t3 = newstyle_i18n_env.get_template('transvars3.html')
|
||||||
|
assert t1.render(num=1, LANGUAGE='de') == 'Benutzer: 1'
|
||||||
|
assert t2.render(count=23, LANGUAGE='de') == 'Benutzer: 23'
|
||||||
|
assert t3.render(num=42, LANGUAGE='de') == 'Benutzer: 42'
|
||||||
|
|
||||||
|
def test_novars_vars_escaping(self):
|
||||||
|
t = newstyle_i18n_env.get_template('novars.html')
|
||||||
|
assert t.render() == '%(hello)s'
|
||||||
|
t = newstyle_i18n_env.get_template('vars.html')
|
||||||
|
assert t.render(foo='42') == '42%(foo)s'
|
||||||
|
t = newstyle_i18n_env.get_template('explicitvars.html')
|
||||||
|
assert t.render() == '%(foo)s'
|
||||||
|
|
||||||
|
|
||||||
|
class AutoEscapeTestCase(JinjaTestCase):
|
||||||
|
|
||||||
|
def test_scoped_setting(self):
|
||||||
|
env = Environment(extensions=['jinja2.ext.autoescape'],
|
||||||
|
autoescape=True)
|
||||||
|
tmpl = env.from_string('''
|
||||||
|
{{ "<HelloWorld>" }}
|
||||||
|
{% autoescape false %}
|
||||||
|
{{ "<HelloWorld>" }}
|
||||||
|
{% endautoescape %}
|
||||||
|
{{ "<HelloWorld>" }}
|
||||||
|
''')
|
||||||
|
assert tmpl.render().split() == \
|
||||||
|
[u'<HelloWorld>', u'<HelloWorld>', u'<HelloWorld>']
|
||||||
|
|
||||||
|
env = Environment(extensions=['jinja2.ext.autoescape'],
|
||||||
|
autoescape=False)
|
||||||
|
tmpl = env.from_string('''
|
||||||
|
{{ "<HelloWorld>" }}
|
||||||
|
{% autoescape true %}
|
||||||
|
{{ "<HelloWorld>" }}
|
||||||
|
{% endautoescape %}
|
||||||
|
{{ "<HelloWorld>" }}
|
||||||
|
''')
|
||||||
|
assert tmpl.render().split() == \
|
||||||
|
[u'<HelloWorld>', u'<HelloWorld>', u'<HelloWorld>']
|
||||||
|
|
||||||
|
def test_nonvolatile(self):
|
||||||
|
env = Environment(extensions=['jinja2.ext.autoescape'],
|
||||||
|
autoescape=True)
|
||||||
|
tmpl = env.from_string('{{ {"foo": "<test>"}|xmlattr|escape }}')
|
||||||
|
assert tmpl.render() == ' foo="<test>"'
|
||||||
|
tmpl = env.from_string('{% autoescape false %}{{ {"foo": "<test>"}'
|
||||||
|
'|xmlattr|escape }}{% endautoescape %}')
|
||||||
|
assert tmpl.render() == ' foo="&lt;test&gt;"'
|
||||||
|
|
||||||
|
def test_volatile(self):
|
||||||
|
env = Environment(extensions=['jinja2.ext.autoescape'],
|
||||||
|
autoescape=True)
|
||||||
|
tmpl = env.from_string('{% autoescape foo %}{{ {"foo": "<test>"}'
|
||||||
|
'|xmlattr|escape }}{% endautoescape %}')
|
||||||
|
assert tmpl.render(foo=False) == ' foo="&lt;test&gt;"'
|
||||||
|
assert tmpl.render(foo=True) == ' foo="<test>"'
|
||||||
|
|
||||||
|
def test_scoping(self):
|
||||||
|
env = Environment(extensions=['jinja2.ext.autoescape'])
|
||||||
|
tmpl = env.from_string('{% autoescape true %}{% set x = "<x>" %}{{ x }}'
|
||||||
|
'{% endautoescape %}{{ x }}{{ "<y>" }}')
|
||||||
|
assert tmpl.render(x=1) == '<x>1<y>'
|
||||||
|
|
||||||
|
def test_volatile_scoping(self):
|
||||||
|
env = Environment(extensions=['jinja2.ext.autoescape'])
|
||||||
|
tmplsource = '''
|
||||||
|
{% autoescape val %}
|
||||||
|
{% macro foo(x) %}
|
||||||
|
[{{ x }}]
|
||||||
|
{% endmacro %}
|
||||||
|
{{ foo().__class__.__name__ }}
|
||||||
|
{% endautoescape %}
|
||||||
|
{{ '<testing>' }}
|
||||||
|
'''
|
||||||
|
tmpl = env.from_string(tmplsource)
|
||||||
|
assert tmpl.render(val=True).split()[0] == 'Markup'
|
||||||
|
assert tmpl.render(val=False).split()[0] == unicode.__name__
|
||||||
|
|
||||||
|
# looking at the source we should see <testing> there in raw
|
||||||
|
# (and then escaped as well)
|
||||||
|
env = Environment(extensions=['jinja2.ext.autoescape'])
|
||||||
|
pysource = env.compile(tmplsource, raw=True)
|
||||||
|
assert '<testing>\\n' in pysource
|
||||||
|
|
||||||
|
env = Environment(extensions=['jinja2.ext.autoescape'],
|
||||||
|
autoescape=True)
|
||||||
|
pysource = env.compile(tmplsource, raw=True)
|
||||||
|
assert '<testing>\\n' in pysource
|
||||||
|
|
||||||
|
|
||||||
|
def suite():
|
||||||
|
suite = unittest.TestSuite()
|
||||||
|
suite.addTest(unittest.makeSuite(ExtensionsTestCase))
|
||||||
|
suite.addTest(unittest.makeSuite(InternationalizationTestCase))
|
||||||
|
suite.addTest(unittest.makeSuite(NewstyleInternationalizationTestCase))
|
||||||
|
suite.addTest(unittest.makeSuite(AutoEscapeTestCase))
|
||||||
|
return suite
|
||||||
Executable
+396
@@ -0,0 +1,396 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja2.testsuite.filters
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tests for the jinja filters.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
import unittest
|
||||||
|
from jinja2.testsuite import JinjaTestCase
|
||||||
|
|
||||||
|
from jinja2 import Markup, Environment
|
||||||
|
|
||||||
|
env = Environment()
|
||||||
|
|
||||||
|
|
||||||
|
class FilterTestCase(JinjaTestCase):
|
||||||
|
|
||||||
|
def test_capitalize(self):
|
||||||
|
tmpl = env.from_string('{{ "foo bar"|capitalize }}')
|
||||||
|
assert tmpl.render() == 'Foo bar'
|
||||||
|
|
||||||
|
def test_center(self):
|
||||||
|
tmpl = env.from_string('{{ "foo"|center(9) }}')
|
||||||
|
assert tmpl.render() == ' foo '
|
||||||
|
|
||||||
|
def test_default(self):
|
||||||
|
tmpl = env.from_string(
|
||||||
|
"{{ missing|default('no') }}|{{ false|default('no') }}|"
|
||||||
|
"{{ false|default('no', true) }}|{{ given|default('no') }}"
|
||||||
|
)
|
||||||
|
assert tmpl.render(given='yes') == 'no|False|no|yes'
|
||||||
|
|
||||||
|
def test_dictsort(self):
|
||||||
|
tmpl = env.from_string(
|
||||||
|
'{{ foo|dictsort }}|'
|
||||||
|
'{{ foo|dictsort(true) }}|'
|
||||||
|
'{{ foo|dictsort(false, "value") }}'
|
||||||
|
)
|
||||||
|
out = tmpl.render(foo={"aa": 0, "b": 1, "c": 2, "AB": 3})
|
||||||
|
assert out == ("[('aa', 0), ('AB', 3), ('b', 1), ('c', 2)]|"
|
||||||
|
"[('AB', 3), ('aa', 0), ('b', 1), ('c', 2)]|"
|
||||||
|
"[('aa', 0), ('b', 1), ('c', 2), ('AB', 3)]")
|
||||||
|
|
||||||
|
def test_batch(self):
|
||||||
|
tmpl = env.from_string("{{ foo|batch(3)|list }}|"
|
||||||
|
"{{ foo|batch(3, 'X')|list }}")
|
||||||
|
out = tmpl.render(foo=range(10))
|
||||||
|
assert out == ("[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]|"
|
||||||
|
"[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 'X', 'X']]")
|
||||||
|
|
||||||
|
def test_slice(self):
|
||||||
|
tmpl = env.from_string('{{ foo|slice(3)|list }}|'
|
||||||
|
'{{ foo|slice(3, "X")|list }}')
|
||||||
|
out = tmpl.render(foo=range(10))
|
||||||
|
assert out == ("[[0, 1, 2, 3], [4, 5, 6], [7, 8, 9]]|"
|
||||||
|
"[[0, 1, 2, 3], [4, 5, 6, 'X'], [7, 8, 9, 'X']]")
|
||||||
|
|
||||||
|
def test_escape(self):
|
||||||
|
tmpl = env.from_string('''{{ '<">&'|escape }}''')
|
||||||
|
out = tmpl.render()
|
||||||
|
assert out == '<">&'
|
||||||
|
|
||||||
|
def test_striptags(self):
|
||||||
|
tmpl = env.from_string('''{{ foo|striptags }}''')
|
||||||
|
out = tmpl.render(foo=' <p>just a small \n <a href="#">'
|
||||||
|
'example</a> link</p>\n<p>to a webpage</p> '
|
||||||
|
'<!-- <p>and some commented stuff</p> -->')
|
||||||
|
assert out == 'just a small example link to a webpage'
|
||||||
|
|
||||||
|
def test_filesizeformat(self):
|
||||||
|
tmpl = env.from_string(
|
||||||
|
'{{ 100|filesizeformat }}|'
|
||||||
|
'{{ 1000|filesizeformat }}|'
|
||||||
|
'{{ 1000000|filesizeformat }}|'
|
||||||
|
'{{ 1000000000|filesizeformat }}|'
|
||||||
|
'{{ 1000000000000|filesizeformat }}|'
|
||||||
|
'{{ 100|filesizeformat(true) }}|'
|
||||||
|
'{{ 1000|filesizeformat(true) }}|'
|
||||||
|
'{{ 1000000|filesizeformat(true) }}|'
|
||||||
|
'{{ 1000000000|filesizeformat(true) }}|'
|
||||||
|
'{{ 1000000000000|filesizeformat(true) }}'
|
||||||
|
)
|
||||||
|
out = tmpl.render()
|
||||||
|
self.assert_equal(out, (
|
||||||
|
'100 Bytes|1.0 kB|1.0 MB|1.0 GB|1.0 TB|100 Bytes|'
|
||||||
|
'1000 Bytes|976.6 KiB|953.7 MiB|931.3 GiB'
|
||||||
|
))
|
||||||
|
|
||||||
|
def test_filesizeformat_issue59(self):
|
||||||
|
tmpl = env.from_string(
|
||||||
|
'{{ 300|filesizeformat }}|'
|
||||||
|
'{{ 3000|filesizeformat }}|'
|
||||||
|
'{{ 3000000|filesizeformat }}|'
|
||||||
|
'{{ 3000000000|filesizeformat }}|'
|
||||||
|
'{{ 3000000000000|filesizeformat }}|'
|
||||||
|
'{{ 300|filesizeformat(true) }}|'
|
||||||
|
'{{ 3000|filesizeformat(true) }}|'
|
||||||
|
'{{ 3000000|filesizeformat(true) }}'
|
||||||
|
)
|
||||||
|
out = tmpl.render()
|
||||||
|
self.assert_equal(out, (
|
||||||
|
'300 Bytes|3.0 kB|3.0 MB|3.0 GB|3.0 TB|300 Bytes|'
|
||||||
|
'2.9 KiB|2.9 MiB'
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
def test_first(self):
|
||||||
|
tmpl = env.from_string('{{ foo|first }}')
|
||||||
|
out = tmpl.render(foo=range(10))
|
||||||
|
assert out == '0'
|
||||||
|
|
||||||
|
def test_float(self):
|
||||||
|
tmpl = env.from_string('{{ "42"|float }}|'
|
||||||
|
'{{ "ajsghasjgd"|float }}|'
|
||||||
|
'{{ "32.32"|float }}')
|
||||||
|
out = tmpl.render()
|
||||||
|
assert out == '42.0|0.0|32.32'
|
||||||
|
|
||||||
|
def test_format(self):
|
||||||
|
tmpl = env.from_string('''{{ "%s|%s"|format("a", "b") }}''')
|
||||||
|
out = tmpl.render()
|
||||||
|
assert out == 'a|b'
|
||||||
|
|
||||||
|
def test_indent(self):
|
||||||
|
tmpl = env.from_string('{{ foo|indent(2) }}|{{ foo|indent(2, true) }}')
|
||||||
|
text = '\n'.join([' '.join(['foo', 'bar'] * 2)] * 2)
|
||||||
|
out = tmpl.render(foo=text)
|
||||||
|
assert out == ('foo bar foo bar\n foo bar foo bar| '
|
||||||
|
'foo bar foo bar\n foo bar foo bar')
|
||||||
|
|
||||||
|
def test_int(self):
|
||||||
|
tmpl = env.from_string('{{ "42"|int }}|{{ "ajsghasjgd"|int }}|'
|
||||||
|
'{{ "32.32"|int }}')
|
||||||
|
out = tmpl.render()
|
||||||
|
assert out == '42|0|32'
|
||||||
|
|
||||||
|
def test_join(self):
|
||||||
|
tmpl = env.from_string('{{ [1, 2, 3]|join("|") }}')
|
||||||
|
out = tmpl.render()
|
||||||
|
assert out == '1|2|3'
|
||||||
|
|
||||||
|
env2 = Environment(autoescape=True)
|
||||||
|
tmpl = env2.from_string('{{ ["<foo>", "<span>foo</span>"|safe]|join }}')
|
||||||
|
assert tmpl.render() == '<foo><span>foo</span>'
|
||||||
|
|
||||||
|
def test_join_attribute(self):
|
||||||
|
class User(object):
|
||||||
|
def __init__(self, username):
|
||||||
|
self.username = username
|
||||||
|
tmpl = env.from_string('''{{ users|join(', ', 'username') }}''')
|
||||||
|
assert tmpl.render(users=map(User, ['foo', 'bar'])) == 'foo, bar'
|
||||||
|
|
||||||
|
def test_last(self):
|
||||||
|
tmpl = env.from_string('''{{ foo|last }}''')
|
||||||
|
out = tmpl.render(foo=range(10))
|
||||||
|
assert out == '9'
|
||||||
|
|
||||||
|
def test_length(self):
|
||||||
|
tmpl = env.from_string('''{{ "hello world"|length }}''')
|
||||||
|
out = tmpl.render()
|
||||||
|
assert out == '11'
|
||||||
|
|
||||||
|
def test_lower(self):
|
||||||
|
tmpl = env.from_string('''{{ "FOO"|lower }}''')
|
||||||
|
out = tmpl.render()
|
||||||
|
assert out == 'foo'
|
||||||
|
|
||||||
|
def test_pprint(self):
|
||||||
|
from pprint import pformat
|
||||||
|
tmpl = env.from_string('''{{ data|pprint }}''')
|
||||||
|
data = range(1000)
|
||||||
|
assert tmpl.render(data=data) == pformat(data)
|
||||||
|
|
||||||
|
def test_random(self):
|
||||||
|
tmpl = env.from_string('''{{ seq|random }}''')
|
||||||
|
seq = range(100)
|
||||||
|
for _ in range(10):
|
||||||
|
assert int(tmpl.render(seq=seq)) in seq
|
||||||
|
|
||||||
|
def test_reverse(self):
|
||||||
|
tmpl = env.from_string('{{ "foobar"|reverse|join }}|'
|
||||||
|
'{{ [1, 2, 3]|reverse|list }}')
|
||||||
|
assert tmpl.render() == 'raboof|[3, 2, 1]'
|
||||||
|
|
||||||
|
def test_string(self):
|
||||||
|
x = [1, 2, 3, 4, 5]
|
||||||
|
tmpl = env.from_string('''{{ obj|string }}''')
|
||||||
|
assert tmpl.render(obj=x) == unicode(x)
|
||||||
|
|
||||||
|
def test_title(self):
|
||||||
|
tmpl = env.from_string('''{{ "foo bar"|title }}''')
|
||||||
|
assert tmpl.render() == "Foo Bar"
|
||||||
|
tmpl = env.from_string('''{{ "foo's bar"|title }}''')
|
||||||
|
assert tmpl.render() == "Foo's Bar"
|
||||||
|
tmpl = env.from_string('''{{ "foo bar"|title }}''')
|
||||||
|
assert tmpl.render() == "Foo Bar"
|
||||||
|
tmpl = env.from_string('''{{ "f bar f"|title }}''')
|
||||||
|
assert tmpl.render() == "F Bar F"
|
||||||
|
tmpl = env.from_string('''{{ "foo-bar"|title }}''')
|
||||||
|
assert tmpl.render() == "Foo-Bar"
|
||||||
|
tmpl = env.from_string('''{{ "foo\tbar"|title }}''')
|
||||||
|
assert tmpl.render() == "Foo\tBar"
|
||||||
|
|
||||||
|
def test_truncate(self):
|
||||||
|
tmpl = env.from_string(
|
||||||
|
'{{ data|truncate(15, true, ">>>") }}|'
|
||||||
|
'{{ data|truncate(15, false, ">>>") }}|'
|
||||||
|
'{{ smalldata|truncate(15) }}'
|
||||||
|
)
|
||||||
|
out = tmpl.render(data='foobar baz bar' * 1000,
|
||||||
|
smalldata='foobar baz bar')
|
||||||
|
assert out == 'foobar baz barf>>>|foobar baz >>>|foobar baz bar'
|
||||||
|
|
||||||
|
def test_upper(self):
|
||||||
|
tmpl = env.from_string('{{ "foo"|upper }}')
|
||||||
|
assert tmpl.render() == 'FOO'
|
||||||
|
|
||||||
|
def test_urlize(self):
|
||||||
|
tmpl = env.from_string('{{ "foo http://www.example.com/ bar"|urlize }}')
|
||||||
|
assert tmpl.render() == 'foo <a href="http://www.example.com/">'\
|
||||||
|
'http://www.example.com/</a> bar'
|
||||||
|
|
||||||
|
def test_wordcount(self):
|
||||||
|
tmpl = env.from_string('{{ "foo bar baz"|wordcount }}')
|
||||||
|
assert tmpl.render() == '3'
|
||||||
|
|
||||||
|
def test_block(self):
|
||||||
|
tmpl = env.from_string('{% filter lower|escape %}<HEHE>{% endfilter %}')
|
||||||
|
assert tmpl.render() == '<hehe>'
|
||||||
|
|
||||||
|
def test_chaining(self):
|
||||||
|
tmpl = env.from_string('''{{ ['<foo>', '<bar>']|first|upper|escape }}''')
|
||||||
|
assert tmpl.render() == '<FOO>'
|
||||||
|
|
||||||
|
def test_sum(self):
|
||||||
|
tmpl = env.from_string('''{{ [1, 2, 3, 4, 5, 6]|sum }}''')
|
||||||
|
assert tmpl.render() == '21'
|
||||||
|
|
||||||
|
def test_sum_attributes(self):
|
||||||
|
tmpl = env.from_string('''{{ values|sum('value') }}''')
|
||||||
|
assert tmpl.render(values=[
|
||||||
|
{'value': 23},
|
||||||
|
{'value': 1},
|
||||||
|
{'value': 18},
|
||||||
|
]) == '42'
|
||||||
|
|
||||||
|
def test_sum_attributes_nested(self):
|
||||||
|
tmpl = env.from_string('''{{ values|sum('real.value') }}''')
|
||||||
|
assert tmpl.render(values=[
|
||||||
|
{'real': {'value': 23}},
|
||||||
|
{'real': {'value': 1}},
|
||||||
|
{'real': {'value': 18}},
|
||||||
|
]) == '42'
|
||||||
|
|
||||||
|
def test_abs(self):
|
||||||
|
tmpl = env.from_string('''{{ -1|abs }}|{{ 1|abs }}''')
|
||||||
|
assert tmpl.render() == '1|1', tmpl.render()
|
||||||
|
|
||||||
|
def test_round_positive(self):
|
||||||
|
tmpl = env.from_string('{{ 2.7|round }}|{{ 2.1|round }}|'
|
||||||
|
"{{ 2.1234|round(3, 'floor') }}|"
|
||||||
|
"{{ 2.1|round(0, 'ceil') }}")
|
||||||
|
assert tmpl.render() == '3.0|2.0|2.123|3.0', tmpl.render()
|
||||||
|
|
||||||
|
def test_round_negative(self):
|
||||||
|
tmpl = env.from_string('{{ 21.3|round(-1)}}|'
|
||||||
|
"{{ 21.3|round(-1, 'ceil')}}|"
|
||||||
|
"{{ 21.3|round(-1, 'floor')}}")
|
||||||
|
assert tmpl.render() == '20.0|30.0|20.0',tmpl.render()
|
||||||
|
|
||||||
|
def test_xmlattr(self):
|
||||||
|
tmpl = env.from_string("{{ {'foo': 42, 'bar': 23, 'fish': none, "
|
||||||
|
"'spam': missing, 'blub:blub': '<?>'}|xmlattr }}")
|
||||||
|
out = tmpl.render().split()
|
||||||
|
assert len(out) == 3
|
||||||
|
assert 'foo="42"' in out
|
||||||
|
assert 'bar="23"' in out
|
||||||
|
assert 'blub:blub="<?>"' in out
|
||||||
|
|
||||||
|
def test_sort1(self):
|
||||||
|
tmpl = env.from_string('{{ [2, 3, 1]|sort }}|{{ [2, 3, 1]|sort(true) }}')
|
||||||
|
assert tmpl.render() == '[1, 2, 3]|[3, 2, 1]'
|
||||||
|
|
||||||
|
def test_sort2(self):
|
||||||
|
tmpl = env.from_string('{{ "".join(["c", "A", "b", "D"]|sort) }}')
|
||||||
|
assert tmpl.render() == 'AbcD'
|
||||||
|
|
||||||
|
def test_sort3(self):
|
||||||
|
tmpl = env.from_string('''{{ ['foo', 'Bar', 'blah']|sort }}''')
|
||||||
|
assert tmpl.render() == "['Bar', 'blah', 'foo']"
|
||||||
|
|
||||||
|
def test_sort4(self):
|
||||||
|
class Magic(object):
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
def __unicode__(self):
|
||||||
|
return unicode(self.value)
|
||||||
|
tmpl = env.from_string('''{{ items|sort(attribute='value')|join }}''')
|
||||||
|
assert tmpl.render(items=map(Magic, [3, 2, 4, 1])) == '1234'
|
||||||
|
|
||||||
|
def test_groupby(self):
|
||||||
|
tmpl = env.from_string('''
|
||||||
|
{%- for grouper, list in [{'foo': 1, 'bar': 2},
|
||||||
|
{'foo': 2, 'bar': 3},
|
||||||
|
{'foo': 1, 'bar': 1},
|
||||||
|
{'foo': 3, 'bar': 4}]|groupby('foo') -%}
|
||||||
|
{{ grouper }}{% for x in list %}: {{ x.foo }}, {{ x.bar }}{% endfor %}|
|
||||||
|
{%- endfor %}''')
|
||||||
|
assert tmpl.render().split('|') == [
|
||||||
|
"1: 1, 2: 1, 1",
|
||||||
|
"2: 2, 3",
|
||||||
|
"3: 3, 4",
|
||||||
|
""
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_groupby_tuple_index(self):
|
||||||
|
tmpl = env.from_string('''
|
||||||
|
{%- for grouper, list in [('a', 1), ('a', 2), ('b', 1)]|groupby(0) -%}
|
||||||
|
{{ grouper }}{% for x in list %}:{{ x.1 }}{% endfor %}|
|
||||||
|
{%- endfor %}''')
|
||||||
|
assert tmpl.render() == 'a:1:2|b:1|'
|
||||||
|
|
||||||
|
def test_groupby_multidot(self):
|
||||||
|
class Date(object):
|
||||||
|
def __init__(self, day, month, year):
|
||||||
|
self.day = day
|
||||||
|
self.month = month
|
||||||
|
self.year = year
|
||||||
|
class Article(object):
|
||||||
|
def __init__(self, title, *date):
|
||||||
|
self.date = Date(*date)
|
||||||
|
self.title = title
|
||||||
|
articles = [
|
||||||
|
Article('aha', 1, 1, 1970),
|
||||||
|
Article('interesting', 2, 1, 1970),
|
||||||
|
Article('really?', 3, 1, 1970),
|
||||||
|
Article('totally not', 1, 1, 1971)
|
||||||
|
]
|
||||||
|
tmpl = env.from_string('''
|
||||||
|
{%- for year, list in articles|groupby('date.year') -%}
|
||||||
|
{{ year }}{% for x in list %}[{{ x.title }}]{% endfor %}|
|
||||||
|
{%- endfor %}''')
|
||||||
|
assert tmpl.render(articles=articles).split('|') == [
|
||||||
|
'1970[aha][interesting][really?]',
|
||||||
|
'1971[totally not]',
|
||||||
|
''
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_filtertag(self):
|
||||||
|
tmpl = env.from_string("{% filter upper|replace('FOO', 'foo') %}"
|
||||||
|
"foobar{% endfilter %}")
|
||||||
|
assert tmpl.render() == 'fooBAR'
|
||||||
|
|
||||||
|
def test_replace(self):
|
||||||
|
env = Environment()
|
||||||
|
tmpl = env.from_string('{{ string|replace("o", 42) }}')
|
||||||
|
assert tmpl.render(string='<foo>') == '<f4242>'
|
||||||
|
env = Environment(autoescape=True)
|
||||||
|
tmpl = env.from_string('{{ string|replace("o", 42) }}')
|
||||||
|
assert tmpl.render(string='<foo>') == '<f4242>'
|
||||||
|
tmpl = env.from_string('{{ string|replace("<", 42) }}')
|
||||||
|
assert tmpl.render(string='<foo>') == '42foo>'
|
||||||
|
tmpl = env.from_string('{{ string|replace("o", ">x<") }}')
|
||||||
|
assert tmpl.render(string=Markup('foo')) == 'f>x<>x<'
|
||||||
|
|
||||||
|
def test_forceescape(self):
|
||||||
|
tmpl = env.from_string('{{ x|forceescape }}')
|
||||||
|
assert tmpl.render(x=Markup('<div />')) == u'<div />'
|
||||||
|
|
||||||
|
def test_safe(self):
|
||||||
|
env = Environment(autoescape=True)
|
||||||
|
tmpl = env.from_string('{{ "<div>foo</div>"|safe }}')
|
||||||
|
assert tmpl.render() == '<div>foo</div>'
|
||||||
|
tmpl = env.from_string('{{ "<div>foo</div>" }}')
|
||||||
|
assert tmpl.render() == '<div>foo</div>'
|
||||||
|
|
||||||
|
def test_urlencode(self):
|
||||||
|
env = Environment(autoescape=True)
|
||||||
|
tmpl = env.from_string('{{ "Hello, world!"|urlencode }}')
|
||||||
|
assert tmpl.render() == 'Hello%2C%20world%21'
|
||||||
|
tmpl = env.from_string('{{ o|urlencode }}')
|
||||||
|
assert tmpl.render(o=u"Hello, world\u203d") == "Hello%2C%20world%E2%80%BD"
|
||||||
|
assert tmpl.render(o=(("f", 1),)) == "f=1"
|
||||||
|
assert tmpl.render(o=(('f', 1), ("z", 2))) == "f=1&z=2"
|
||||||
|
assert tmpl.render(o=((u"\u203d", 1),)) == "%E2%80%BD=1"
|
||||||
|
assert tmpl.render(o={u"\u203d": 1}) == "%E2%80%BD=1"
|
||||||
|
assert tmpl.render(o={0: 1}) == "0=1"
|
||||||
|
|
||||||
|
|
||||||
|
def suite():
|
||||||
|
suite = unittest.TestSuite()
|
||||||
|
suite.addTest(unittest.makeSuite(FilterTestCase))
|
||||||
|
return suite
|
||||||
Executable
+141
@@ -0,0 +1,141 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja2.testsuite.imports
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tests the import features (with includes).
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from jinja2.testsuite import JinjaTestCase
|
||||||
|
|
||||||
|
from jinja2 import Environment, DictLoader
|
||||||
|
from jinja2.exceptions import TemplateNotFound, TemplatesNotFound
|
||||||
|
|
||||||
|
|
||||||
|
test_env = Environment(loader=DictLoader(dict(
|
||||||
|
module='{% macro test() %}[{{ foo }}|{{ bar }}]{% endmacro %}',
|
||||||
|
header='[{{ foo }}|{{ 23 }}]',
|
||||||
|
o_printer='({{ o }})'
|
||||||
|
)))
|
||||||
|
test_env.globals['bar'] = 23
|
||||||
|
|
||||||
|
|
||||||
|
class ImportsTestCase(JinjaTestCase):
|
||||||
|
|
||||||
|
def test_context_imports(self):
|
||||||
|
t = test_env.from_string('{% import "module" as m %}{{ m.test() }}')
|
||||||
|
assert t.render(foo=42) == '[|23]'
|
||||||
|
t = test_env.from_string('{% import "module" as m without context %}{{ m.test() }}')
|
||||||
|
assert t.render(foo=42) == '[|23]'
|
||||||
|
t = test_env.from_string('{% import "module" as m with context %}{{ m.test() }}')
|
||||||
|
assert t.render(foo=42) == '[42|23]'
|
||||||
|
t = test_env.from_string('{% from "module" import test %}{{ test() }}')
|
||||||
|
assert t.render(foo=42) == '[|23]'
|
||||||
|
t = test_env.from_string('{% from "module" import test without context %}{{ test() }}')
|
||||||
|
assert t.render(foo=42) == '[|23]'
|
||||||
|
t = test_env.from_string('{% from "module" import test with context %}{{ test() }}')
|
||||||
|
assert t.render(foo=42) == '[42|23]'
|
||||||
|
|
||||||
|
def test_trailing_comma(self):
|
||||||
|
test_env.from_string('{% from "foo" import bar, baz with context %}')
|
||||||
|
test_env.from_string('{% from "foo" import bar, baz, with context %}')
|
||||||
|
test_env.from_string('{% from "foo" import bar, with context %}')
|
||||||
|
test_env.from_string('{% from "foo" import bar, with, context %}')
|
||||||
|
test_env.from_string('{% from "foo" import bar, with with context %}')
|
||||||
|
|
||||||
|
def test_exports(self):
|
||||||
|
m = test_env.from_string('''
|
||||||
|
{% macro toplevel() %}...{% endmacro %}
|
||||||
|
{% macro __private() %}...{% endmacro %}
|
||||||
|
{% set variable = 42 %}
|
||||||
|
{% for item in [1] %}
|
||||||
|
{% macro notthere() %}{% endmacro %}
|
||||||
|
{% endfor %}
|
||||||
|
''').module
|
||||||
|
assert m.toplevel() == '...'
|
||||||
|
assert not hasattr(m, '__missing')
|
||||||
|
assert m.variable == 42
|
||||||
|
assert not hasattr(m, 'notthere')
|
||||||
|
|
||||||
|
|
||||||
|
class IncludesTestCase(JinjaTestCase):
|
||||||
|
|
||||||
|
def test_context_include(self):
|
||||||
|
t = test_env.from_string('{% include "header" %}')
|
||||||
|
assert t.render(foo=42) == '[42|23]'
|
||||||
|
t = test_env.from_string('{% include "header" with context %}')
|
||||||
|
assert t.render(foo=42) == '[42|23]'
|
||||||
|
t = test_env.from_string('{% include "header" without context %}')
|
||||||
|
assert t.render(foo=42) == '[|23]'
|
||||||
|
|
||||||
|
def test_choice_includes(self):
|
||||||
|
t = test_env.from_string('{% include ["missing", "header"] %}')
|
||||||
|
assert t.render(foo=42) == '[42|23]'
|
||||||
|
|
||||||
|
t = test_env.from_string('{% include ["missing", "missing2"] ignore missing %}')
|
||||||
|
assert t.render(foo=42) == ''
|
||||||
|
|
||||||
|
t = test_env.from_string('{% include ["missing", "missing2"] %}')
|
||||||
|
self.assert_raises(TemplateNotFound, t.render)
|
||||||
|
try:
|
||||||
|
t.render()
|
||||||
|
except TemplatesNotFound, e:
|
||||||
|
assert e.templates == ['missing', 'missing2']
|
||||||
|
assert e.name == 'missing2'
|
||||||
|
else:
|
||||||
|
assert False, 'thou shalt raise'
|
||||||
|
|
||||||
|
def test_includes(t, **ctx):
|
||||||
|
ctx['foo'] = 42
|
||||||
|
assert t.render(ctx) == '[42|23]'
|
||||||
|
|
||||||
|
t = test_env.from_string('{% include ["missing", "header"] %}')
|
||||||
|
test_includes(t)
|
||||||
|
t = test_env.from_string('{% include x %}')
|
||||||
|
test_includes(t, x=['missing', 'header'])
|
||||||
|
t = test_env.from_string('{% include [x, "header"] %}')
|
||||||
|
test_includes(t, x='missing')
|
||||||
|
t = test_env.from_string('{% include x %}')
|
||||||
|
test_includes(t, x='header')
|
||||||
|
t = test_env.from_string('{% include x %}')
|
||||||
|
test_includes(t, x='header')
|
||||||
|
t = test_env.from_string('{% include [x] %}')
|
||||||
|
test_includes(t, x='header')
|
||||||
|
|
||||||
|
def test_include_ignoring_missing(self):
|
||||||
|
t = test_env.from_string('{% include "missing" %}')
|
||||||
|
self.assert_raises(TemplateNotFound, t.render)
|
||||||
|
for extra in '', 'with context', 'without context':
|
||||||
|
t = test_env.from_string('{% include "missing" ignore missing ' +
|
||||||
|
extra + ' %}')
|
||||||
|
assert t.render() == ''
|
||||||
|
|
||||||
|
def test_context_include_with_overrides(self):
|
||||||
|
env = Environment(loader=DictLoader(dict(
|
||||||
|
main="{% for item in [1, 2, 3] %}{% include 'item' %}{% endfor %}",
|
||||||
|
item="{{ item }}"
|
||||||
|
)))
|
||||||
|
assert env.get_template("main").render() == "123"
|
||||||
|
|
||||||
|
def test_unoptimized_scopes(self):
|
||||||
|
t = test_env.from_string("""
|
||||||
|
{% macro outer(o) %}
|
||||||
|
{% macro inner() %}
|
||||||
|
{% include "o_printer" %}
|
||||||
|
{% endmacro %}
|
||||||
|
{{ inner() }}
|
||||||
|
{% endmacro %}
|
||||||
|
{{ outer("FOO") }}
|
||||||
|
""")
|
||||||
|
assert t.render().strip() == '(FOO)'
|
||||||
|
|
||||||
|
|
||||||
|
def suite():
|
||||||
|
suite = unittest.TestSuite()
|
||||||
|
suite.addTest(unittest.makeSuite(ImportsTestCase))
|
||||||
|
suite.addTest(unittest.makeSuite(IncludesTestCase))
|
||||||
|
return suite
|
||||||
Executable
+227
@@ -0,0 +1,227 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja2.testsuite.inheritance
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tests the template inheritance feature.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from jinja2.testsuite import JinjaTestCase
|
||||||
|
|
||||||
|
from jinja2 import Environment, DictLoader
|
||||||
|
|
||||||
|
|
||||||
|
LAYOUTTEMPLATE = '''\
|
||||||
|
|{% block block1 %}block 1 from layout{% endblock %}
|
||||||
|
|{% block block2 %}block 2 from layout{% endblock %}
|
||||||
|
|{% block block3 %}
|
||||||
|
{% block block4 %}nested block 4 from layout{% endblock %}
|
||||||
|
{% endblock %}|'''
|
||||||
|
|
||||||
|
LEVEL1TEMPLATE = '''\
|
||||||
|
{% extends "layout" %}
|
||||||
|
{% block block1 %}block 1 from level1{% endblock %}'''
|
||||||
|
|
||||||
|
LEVEL2TEMPLATE = '''\
|
||||||
|
{% extends "level1" %}
|
||||||
|
{% block block2 %}{% block block5 %}nested block 5 from level2{%
|
||||||
|
endblock %}{% endblock %}'''
|
||||||
|
|
||||||
|
LEVEL3TEMPLATE = '''\
|
||||||
|
{% extends "level2" %}
|
||||||
|
{% block block5 %}block 5 from level3{% endblock %}
|
||||||
|
{% block block4 %}block 4 from level3{% endblock %}
|
||||||
|
'''
|
||||||
|
|
||||||
|
LEVEL4TEMPLATE = '''\
|
||||||
|
{% extends "level3" %}
|
||||||
|
{% block block3 %}block 3 from level4{% endblock %}
|
||||||
|
'''
|
||||||
|
|
||||||
|
WORKINGTEMPLATE = '''\
|
||||||
|
{% extends "layout" %}
|
||||||
|
{% block block1 %}
|
||||||
|
{% if false %}
|
||||||
|
{% block block2 %}
|
||||||
|
this should workd
|
||||||
|
{% endblock %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
'''
|
||||||
|
|
||||||
|
env = Environment(loader=DictLoader({
|
||||||
|
'layout': LAYOUTTEMPLATE,
|
||||||
|
'level1': LEVEL1TEMPLATE,
|
||||||
|
'level2': LEVEL2TEMPLATE,
|
||||||
|
'level3': LEVEL3TEMPLATE,
|
||||||
|
'level4': LEVEL4TEMPLATE,
|
||||||
|
'working': WORKINGTEMPLATE
|
||||||
|
}), trim_blocks=True)
|
||||||
|
|
||||||
|
|
||||||
|
class InheritanceTestCase(JinjaTestCase):
|
||||||
|
|
||||||
|
def test_layout(self):
|
||||||
|
tmpl = env.get_template('layout')
|
||||||
|
assert tmpl.render() == ('|block 1 from layout|block 2 from '
|
||||||
|
'layout|nested block 4 from layout|')
|
||||||
|
|
||||||
|
def test_level1(self):
|
||||||
|
tmpl = env.get_template('level1')
|
||||||
|
assert tmpl.render() == ('|block 1 from level1|block 2 from '
|
||||||
|
'layout|nested block 4 from layout|')
|
||||||
|
|
||||||
|
def test_level2(self):
|
||||||
|
tmpl = env.get_template('level2')
|
||||||
|
assert tmpl.render() == ('|block 1 from level1|nested block 5 from '
|
||||||
|
'level2|nested block 4 from layout|')
|
||||||
|
|
||||||
|
def test_level3(self):
|
||||||
|
tmpl = env.get_template('level3')
|
||||||
|
assert tmpl.render() == ('|block 1 from level1|block 5 from level3|'
|
||||||
|
'block 4 from level3|')
|
||||||
|
|
||||||
|
def test_level4(sel):
|
||||||
|
tmpl = env.get_template('level4')
|
||||||
|
assert tmpl.render() == ('|block 1 from level1|block 5 from '
|
||||||
|
'level3|block 3 from level4|')
|
||||||
|
|
||||||
|
def test_super(self):
|
||||||
|
env = Environment(loader=DictLoader({
|
||||||
|
'a': '{% block intro %}INTRO{% endblock %}|'
|
||||||
|
'BEFORE|{% block data %}INNER{% endblock %}|AFTER',
|
||||||
|
'b': '{% extends "a" %}{% block data %}({{ '
|
||||||
|
'super() }}){% endblock %}',
|
||||||
|
'c': '{% extends "b" %}{% block intro %}--{{ '
|
||||||
|
'super() }}--{% endblock %}\n{% block data '
|
||||||
|
'%}[{{ super() }}]{% endblock %}'
|
||||||
|
}))
|
||||||
|
tmpl = env.get_template('c')
|
||||||
|
assert tmpl.render() == '--INTRO--|BEFORE|[(INNER)]|AFTER'
|
||||||
|
|
||||||
|
def test_working(self):
|
||||||
|
tmpl = env.get_template('working')
|
||||||
|
|
||||||
|
def test_reuse_blocks(self):
|
||||||
|
tmpl = env.from_string('{{ self.foo() }}|{% block foo %}42'
|
||||||
|
'{% endblock %}|{{ self.foo() }}')
|
||||||
|
assert tmpl.render() == '42|42|42'
|
||||||
|
|
||||||
|
def test_preserve_blocks(self):
|
||||||
|
env = Environment(loader=DictLoader({
|
||||||
|
'a': '{% if false %}{% block x %}A{% endblock %}{% endif %}{{ self.x() }}',
|
||||||
|
'b': '{% extends "a" %}{% block x %}B{{ super() }}{% endblock %}'
|
||||||
|
}))
|
||||||
|
tmpl = env.get_template('b')
|
||||||
|
assert tmpl.render() == 'BA'
|
||||||
|
|
||||||
|
def test_dynamic_inheritance(self):
|
||||||
|
env = Environment(loader=DictLoader({
|
||||||
|
'master1': 'MASTER1{% block x %}{% endblock %}',
|
||||||
|
'master2': 'MASTER2{% block x %}{% endblock %}',
|
||||||
|
'child': '{% extends master %}{% block x %}CHILD{% endblock %}'
|
||||||
|
}))
|
||||||
|
tmpl = env.get_template('child')
|
||||||
|
for m in range(1, 3):
|
||||||
|
assert tmpl.render(master='master%d' % m) == 'MASTER%dCHILD' % m
|
||||||
|
|
||||||
|
def test_multi_inheritance(self):
|
||||||
|
env = Environment(loader=DictLoader({
|
||||||
|
'master1': 'MASTER1{% block x %}{% endblock %}',
|
||||||
|
'master2': 'MASTER2{% block x %}{% endblock %}',
|
||||||
|
'child': '''{% if master %}{% extends master %}{% else %}{% extends
|
||||||
|
'master1' %}{% endif %}{% block x %}CHILD{% endblock %}'''
|
||||||
|
}))
|
||||||
|
tmpl = env.get_template('child')
|
||||||
|
assert tmpl.render(master='master2') == 'MASTER2CHILD'
|
||||||
|
assert tmpl.render(master='master1') == 'MASTER1CHILD'
|
||||||
|
assert tmpl.render() == 'MASTER1CHILD'
|
||||||
|
|
||||||
|
def test_scoped_block(self):
|
||||||
|
env = Environment(loader=DictLoader({
|
||||||
|
'master.html': '{% for item in seq %}[{% block item scoped %}'
|
||||||
|
'{% endblock %}]{% endfor %}'
|
||||||
|
}))
|
||||||
|
t = env.from_string('{% extends "master.html" %}{% block item %}'
|
||||||
|
'{{ item }}{% endblock %}')
|
||||||
|
assert t.render(seq=range(5)) == '[0][1][2][3][4]'
|
||||||
|
|
||||||
|
def test_super_in_scoped_block(self):
|
||||||
|
env = Environment(loader=DictLoader({
|
||||||
|
'master.html': '{% for item in seq %}[{% block item scoped %}'
|
||||||
|
'{{ item }}{% endblock %}]{% endfor %}'
|
||||||
|
}))
|
||||||
|
t = env.from_string('{% extends "master.html" %}{% block item %}'
|
||||||
|
'{{ super() }}|{{ item * 2 }}{% endblock %}')
|
||||||
|
assert t.render(seq=range(5)) == '[0|0][1|2][2|4][3|6][4|8]'
|
||||||
|
|
||||||
|
def test_scoped_block_after_inheritance(self):
|
||||||
|
env = Environment(loader=DictLoader({
|
||||||
|
'layout.html': '''
|
||||||
|
{% block useless %}{% endblock %}
|
||||||
|
''',
|
||||||
|
'index.html': '''
|
||||||
|
{%- extends 'layout.html' %}
|
||||||
|
{% from 'helpers.html' import foo with context %}
|
||||||
|
{% block useless %}
|
||||||
|
{% for x in [1, 2, 3] %}
|
||||||
|
{% block testing scoped %}
|
||||||
|
{{ foo(x) }}
|
||||||
|
{% endblock %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
||||||
|
''',
|
||||||
|
'helpers.html': '''
|
||||||
|
{% macro foo(x) %}{{ the_foo + x }}{% endmacro %}
|
||||||
|
'''
|
||||||
|
}))
|
||||||
|
rv = env.get_template('index.html').render(the_foo=42).split()
|
||||||
|
assert rv == ['43', '44', '45']
|
||||||
|
|
||||||
|
|
||||||
|
class BugFixTestCase(JinjaTestCase):
|
||||||
|
|
||||||
|
def test_fixed_macro_scoping_bug(self):
|
||||||
|
assert Environment(loader=DictLoader({
|
||||||
|
'test.html': '''\
|
||||||
|
{% extends 'details.html' %}
|
||||||
|
|
||||||
|
{% macro my_macro() %}
|
||||||
|
my_macro
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% block inner_box %}
|
||||||
|
{{ my_macro() }}
|
||||||
|
{% endblock %}
|
||||||
|
''',
|
||||||
|
'details.html': '''\
|
||||||
|
{% extends 'standard.html' %}
|
||||||
|
|
||||||
|
{% macro my_macro() %}
|
||||||
|
my_macro
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% block outer_box %}
|
||||||
|
outer_box
|
||||||
|
{% block inner_box %}
|
||||||
|
inner_box
|
||||||
|
{% endblock %}
|
||||||
|
{% endblock %}
|
||||||
|
{% endblock %}
|
||||||
|
''',
|
||||||
|
'standard.html': '''
|
||||||
|
{% block content %} {% endblock %}
|
||||||
|
'''
|
||||||
|
})).get_template("test.html").render().split() == [u'outer_box', u'my_macro']
|
||||||
|
|
||||||
|
|
||||||
|
def suite():
|
||||||
|
suite = unittest.TestSuite()
|
||||||
|
suite.addTest(unittest.makeSuite(InheritanceTestCase))
|
||||||
|
suite.addTest(unittest.makeSuite(BugFixTestCase))
|
||||||
|
return suite
|
||||||
Executable
+387
@@ -0,0 +1,387 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja2.testsuite.lexnparse
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
All the unittests regarding lexing, parsing and syntax.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from jinja2.testsuite import JinjaTestCase
|
||||||
|
|
||||||
|
from jinja2 import Environment, Template, TemplateSyntaxError, \
|
||||||
|
UndefinedError, nodes
|
||||||
|
|
||||||
|
env = Environment()
|
||||||
|
|
||||||
|
|
||||||
|
# how does a string look like in jinja syntax?
|
||||||
|
if sys.version_info < (3, 0):
|
||||||
|
def jinja_string_repr(string):
|
||||||
|
return repr(string)[1:]
|
||||||
|
else:
|
||||||
|
jinja_string_repr = repr
|
||||||
|
|
||||||
|
|
||||||
|
class LexerTestCase(JinjaTestCase):
|
||||||
|
|
||||||
|
def test_raw1(self):
|
||||||
|
tmpl = env.from_string('{% raw %}foo{% endraw %}|'
|
||||||
|
'{%raw%}{{ bar }}|{% baz %}{% endraw %}')
|
||||||
|
assert tmpl.render() == 'foo|{{ bar }}|{% baz %}'
|
||||||
|
|
||||||
|
def test_raw2(self):
|
||||||
|
tmpl = env.from_string('1 {%- raw -%} 2 {%- endraw -%} 3')
|
||||||
|
assert tmpl.render() == '123'
|
||||||
|
|
||||||
|
def test_balancing(self):
|
||||||
|
env = Environment('{%', '%}', '${', '}')
|
||||||
|
tmpl = env.from_string('''{% for item in seq
|
||||||
|
%}${{'foo': item}|upper}{% endfor %}''')
|
||||||
|
assert tmpl.render(seq=range(3)) == "{'FOO': 0}{'FOO': 1}{'FOO': 2}"
|
||||||
|
|
||||||
|
def test_comments(self):
|
||||||
|
env = Environment('<!--', '-->', '{', '}')
|
||||||
|
tmpl = env.from_string('''\
|
||||||
|
<ul>
|
||||||
|
<!--- for item in seq -->
|
||||||
|
<li>{item}</li>
|
||||||
|
<!--- endfor -->
|
||||||
|
</ul>''')
|
||||||
|
assert tmpl.render(seq=range(3)) == ("<ul>\n <li>0</li>\n "
|
||||||
|
"<li>1</li>\n <li>2</li>\n</ul>")
|
||||||
|
|
||||||
|
def test_string_escapes(self):
|
||||||
|
for char in u'\0', u'\u2668', u'\xe4', u'\t', u'\r', u'\n':
|
||||||
|
tmpl = env.from_string('{{ %s }}' % jinja_string_repr(char))
|
||||||
|
assert tmpl.render() == char
|
||||||
|
assert env.from_string('{{ "\N{HOT SPRINGS}" }}').render() == u'\u2668'
|
||||||
|
|
||||||
|
def test_bytefallback(self):
|
||||||
|
from pprint import pformat
|
||||||
|
tmpl = env.from_string(u'''{{ 'foo'|pprint }}|{{ 'bär'|pprint }}''')
|
||||||
|
assert tmpl.render() == pformat('foo') + '|' + pformat(u'bär')
|
||||||
|
|
||||||
|
def test_operators(self):
|
||||||
|
from jinja2.lexer import operators
|
||||||
|
for test, expect in operators.iteritems():
|
||||||
|
if test in '([{}])':
|
||||||
|
continue
|
||||||
|
stream = env.lexer.tokenize('{{ %s }}' % test)
|
||||||
|
stream.next()
|
||||||
|
assert stream.current.type == expect
|
||||||
|
|
||||||
|
def test_normalizing(self):
|
||||||
|
for seq in '\r', '\r\n', '\n':
|
||||||
|
env = Environment(newline_sequence=seq)
|
||||||
|
tmpl = env.from_string('1\n2\r\n3\n4\n')
|
||||||
|
result = tmpl.render()
|
||||||
|
assert result.replace(seq, 'X') == '1X2X3X4'
|
||||||
|
|
||||||
|
|
||||||
|
class ParserTestCase(JinjaTestCase):
|
||||||
|
|
||||||
|
def test_php_syntax(self):
|
||||||
|
env = Environment('<?', '?>', '<?=', '?>', '<!--', '-->')
|
||||||
|
tmpl = env.from_string('''\
|
||||||
|
<!-- I'm a comment, I'm not interesting -->\
|
||||||
|
<? for item in seq -?>
|
||||||
|
<?= item ?>
|
||||||
|
<?- endfor ?>''')
|
||||||
|
assert tmpl.render(seq=range(5)) == '01234'
|
||||||
|
|
||||||
|
def test_erb_syntax(self):
|
||||||
|
env = Environment('<%', '%>', '<%=', '%>', '<%#', '%>')
|
||||||
|
tmpl = env.from_string('''\
|
||||||
|
<%# I'm a comment, I'm not interesting %>\
|
||||||
|
<% for item in seq -%>
|
||||||
|
<%= item %>
|
||||||
|
<%- endfor %>''')
|
||||||
|
assert tmpl.render(seq=range(5)) == '01234'
|
||||||
|
|
||||||
|
def test_comment_syntax(self):
|
||||||
|
env = Environment('<!--', '-->', '${', '}', '<!--#', '-->')
|
||||||
|
tmpl = env.from_string('''\
|
||||||
|
<!--# I'm a comment, I'm not interesting -->\
|
||||||
|
<!-- for item in seq --->
|
||||||
|
${item}
|
||||||
|
<!--- endfor -->''')
|
||||||
|
assert tmpl.render(seq=range(5)) == '01234'
|
||||||
|
|
||||||
|
def test_balancing(self):
|
||||||
|
tmpl = env.from_string('''{{{'foo':'bar'}.foo}}''')
|
||||||
|
assert tmpl.render() == 'bar'
|
||||||
|
|
||||||
|
def test_start_comment(self):
|
||||||
|
tmpl = env.from_string('''{# foo comment
|
||||||
|
and bar comment #}
|
||||||
|
{% macro blub() %}foo{% endmacro %}
|
||||||
|
{{ blub() }}''')
|
||||||
|
assert tmpl.render().strip() == 'foo'
|
||||||
|
|
||||||
|
def test_line_syntax(self):
|
||||||
|
env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%')
|
||||||
|
tmpl = env.from_string('''\
|
||||||
|
<%# regular comment %>
|
||||||
|
% for item in seq:
|
||||||
|
${item}
|
||||||
|
% endfor''')
|
||||||
|
assert [int(x.strip()) for x in tmpl.render(seq=range(5)).split()] == \
|
||||||
|
range(5)
|
||||||
|
|
||||||
|
env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%', '##')
|
||||||
|
tmpl = env.from_string('''\
|
||||||
|
<%# regular comment %>
|
||||||
|
% for item in seq:
|
||||||
|
${item} ## the rest of the stuff
|
||||||
|
% endfor''')
|
||||||
|
assert [int(x.strip()) for x in tmpl.render(seq=range(5)).split()] == \
|
||||||
|
range(5)
|
||||||
|
|
||||||
|
def test_line_syntax_priority(self):
|
||||||
|
# XXX: why is the whitespace there in front of the newline?
|
||||||
|
env = Environment('{%', '%}', '${', '}', '/*', '*/', '##', '#')
|
||||||
|
tmpl = env.from_string('''\
|
||||||
|
/* ignore me.
|
||||||
|
I'm a multiline comment */
|
||||||
|
## for item in seq:
|
||||||
|
* ${item} # this is just extra stuff
|
||||||
|
## endfor''')
|
||||||
|
assert tmpl.render(seq=[1, 2]).strip() == '* 1\n* 2'
|
||||||
|
env = Environment('{%', '%}', '${', '}', '/*', '*/', '#', '##')
|
||||||
|
tmpl = env.from_string('''\
|
||||||
|
/* ignore me.
|
||||||
|
I'm a multiline comment */
|
||||||
|
# for item in seq:
|
||||||
|
* ${item} ## this is just extra stuff
|
||||||
|
## extra stuff i just want to ignore
|
||||||
|
# endfor''')
|
||||||
|
assert tmpl.render(seq=[1, 2]).strip() == '* 1\n\n* 2'
|
||||||
|
|
||||||
|
def test_error_messages(self):
|
||||||
|
def assert_error(code, expected):
|
||||||
|
try:
|
||||||
|
Template(code)
|
||||||
|
except TemplateSyntaxError, e:
|
||||||
|
assert str(e) == expected, 'unexpected error message'
|
||||||
|
else:
|
||||||
|
assert False, 'that was supposed to be an error'
|
||||||
|
|
||||||
|
assert_error('{% for item in seq %}...{% endif %}',
|
||||||
|
"Encountered unknown tag 'endif'. Jinja was looking "
|
||||||
|
"for the following tags: 'endfor' or 'else'. The "
|
||||||
|
"innermost block that needs to be closed is 'for'.")
|
||||||
|
assert_error('{% if foo %}{% for item in seq %}...{% endfor %}{% endfor %}',
|
||||||
|
"Encountered unknown tag 'endfor'. Jinja was looking for "
|
||||||
|
"the following tags: 'elif' or 'else' or 'endif'. The "
|
||||||
|
"innermost block that needs to be closed is 'if'.")
|
||||||
|
assert_error('{% if foo %}',
|
||||||
|
"Unexpected end of template. Jinja was looking for the "
|
||||||
|
"following tags: 'elif' or 'else' or 'endif'. The "
|
||||||
|
"innermost block that needs to be closed is 'if'.")
|
||||||
|
assert_error('{% for item in seq %}',
|
||||||
|
"Unexpected end of template. Jinja was looking for the "
|
||||||
|
"following tags: 'endfor' or 'else'. The innermost block "
|
||||||
|
"that needs to be closed is 'for'.")
|
||||||
|
assert_error('{% block foo-bar-baz %}',
|
||||||
|
"Block names in Jinja have to be valid Python identifiers "
|
||||||
|
"and may not contain hyphens, use an underscore instead.")
|
||||||
|
assert_error('{% unknown_tag %}',
|
||||||
|
"Encountered unknown tag 'unknown_tag'.")
|
||||||
|
|
||||||
|
|
||||||
|
class SyntaxTestCase(JinjaTestCase):
|
||||||
|
|
||||||
|
def test_call(self):
|
||||||
|
env = Environment()
|
||||||
|
env.globals['foo'] = lambda a, b, c, e, g: a + b + c + e + g
|
||||||
|
tmpl = env.from_string("{{ foo('a', c='d', e='f', *['b'], **{'g': 'h'}) }}")
|
||||||
|
assert tmpl.render() == 'abdfh'
|
||||||
|
|
||||||
|
def test_slicing(self):
|
||||||
|
tmpl = env.from_string('{{ [1, 2, 3][:] }}|{{ [1, 2, 3][::-1] }}')
|
||||||
|
assert tmpl.render() == '[1, 2, 3]|[3, 2, 1]'
|
||||||
|
|
||||||
|
def test_attr(self):
|
||||||
|
tmpl = env.from_string("{{ foo.bar }}|{{ foo['bar'] }}")
|
||||||
|
assert tmpl.render(foo={'bar': 42}) == '42|42'
|
||||||
|
|
||||||
|
def test_subscript(self):
|
||||||
|
tmpl = env.from_string("{{ foo[0] }}|{{ foo[-1] }}")
|
||||||
|
assert tmpl.render(foo=[0, 1, 2]) == '0|2'
|
||||||
|
|
||||||
|
def test_tuple(self):
|
||||||
|
tmpl = env.from_string('{{ () }}|{{ (1,) }}|{{ (1, 2) }}')
|
||||||
|
assert tmpl.render() == '()|(1,)|(1, 2)'
|
||||||
|
|
||||||
|
def test_math(self):
|
||||||
|
tmpl = env.from_string('{{ (1 + 1 * 2) - 3 / 2 }}|{{ 2**3 }}')
|
||||||
|
assert tmpl.render() == '1.5|8'
|
||||||
|
|
||||||
|
def test_div(self):
|
||||||
|
tmpl = env.from_string('{{ 3 // 2 }}|{{ 3 / 2 }}|{{ 3 % 2 }}')
|
||||||
|
assert tmpl.render() == '1|1.5|1'
|
||||||
|
|
||||||
|
def test_unary(self):
|
||||||
|
tmpl = env.from_string('{{ +3 }}|{{ -3 }}')
|
||||||
|
assert tmpl.render() == '3|-3'
|
||||||
|
|
||||||
|
def test_concat(self):
|
||||||
|
tmpl = env.from_string("{{ [1, 2] ~ 'foo' }}")
|
||||||
|
assert tmpl.render() == '[1, 2]foo'
|
||||||
|
|
||||||
|
def test_compare(self):
|
||||||
|
tmpl = env.from_string('{{ 1 > 0 }}|{{ 1 >= 1 }}|{{ 2 < 3 }}|'
|
||||||
|
'{{ 2 == 2 }}|{{ 1 <= 1 }}')
|
||||||
|
assert tmpl.render() == 'True|True|True|True|True'
|
||||||
|
|
||||||
|
def test_inop(self):
|
||||||
|
tmpl = env.from_string('{{ 1 in [1, 2, 3] }}|{{ 1 not in [1, 2, 3] }}')
|
||||||
|
assert tmpl.render() == 'True|False'
|
||||||
|
|
||||||
|
def test_literals(self):
|
||||||
|
tmpl = env.from_string('{{ [] }}|{{ {} }}|{{ () }}')
|
||||||
|
assert tmpl.render().lower() == '[]|{}|()'
|
||||||
|
|
||||||
|
def test_bool(self):
|
||||||
|
tmpl = env.from_string('{{ true and false }}|{{ false '
|
||||||
|
'or true }}|{{ not false }}')
|
||||||
|
assert tmpl.render() == 'False|True|True'
|
||||||
|
|
||||||
|
def test_grouping(self):
|
||||||
|
tmpl = env.from_string('{{ (true and false) or (false and true) and not false }}')
|
||||||
|
assert tmpl.render() == 'False'
|
||||||
|
|
||||||
|
def test_django_attr(self):
|
||||||
|
tmpl = env.from_string('{{ [1, 2, 3].0 }}|{{ [[1]].0.0 }}')
|
||||||
|
assert tmpl.render() == '1|1'
|
||||||
|
|
||||||
|
def test_conditional_expression(self):
|
||||||
|
tmpl = env.from_string('''{{ 0 if true else 1 }}''')
|
||||||
|
assert tmpl.render() == '0'
|
||||||
|
|
||||||
|
def test_short_conditional_expression(self):
|
||||||
|
tmpl = env.from_string('<{{ 1 if false }}>')
|
||||||
|
assert tmpl.render() == '<>'
|
||||||
|
|
||||||
|
tmpl = env.from_string('<{{ (1 if false).bar }}>')
|
||||||
|
self.assert_raises(UndefinedError, tmpl.render)
|
||||||
|
|
||||||
|
def test_filter_priority(self):
|
||||||
|
tmpl = env.from_string('{{ "foo"|upper + "bar"|upper }}')
|
||||||
|
assert tmpl.render() == 'FOOBAR'
|
||||||
|
|
||||||
|
def test_function_calls(self):
|
||||||
|
tests = [
|
||||||
|
(True, '*foo, bar'),
|
||||||
|
(True, '*foo, *bar'),
|
||||||
|
(True, '*foo, bar=42'),
|
||||||
|
(True, '**foo, *bar'),
|
||||||
|
(True, '**foo, bar'),
|
||||||
|
(False, 'foo, bar'),
|
||||||
|
(False, 'foo, bar=42'),
|
||||||
|
(False, 'foo, bar=23, *args'),
|
||||||
|
(False, 'a, b=c, *d, **e'),
|
||||||
|
(False, '*foo, **bar')
|
||||||
|
]
|
||||||
|
for should_fail, sig in tests:
|
||||||
|
if should_fail:
|
||||||
|
self.assert_raises(TemplateSyntaxError,
|
||||||
|
env.from_string, '{{ foo(%s) }}' % sig)
|
||||||
|
else:
|
||||||
|
env.from_string('foo(%s)' % sig)
|
||||||
|
|
||||||
|
def test_tuple_expr(self):
|
||||||
|
for tmpl in [
|
||||||
|
'{{ () }}',
|
||||||
|
'{{ (1, 2) }}',
|
||||||
|
'{{ (1, 2,) }}',
|
||||||
|
'{{ 1, }}',
|
||||||
|
'{{ 1, 2 }}',
|
||||||
|
'{% for foo, bar in seq %}...{% endfor %}',
|
||||||
|
'{% for x in foo, bar %}...{% endfor %}',
|
||||||
|
'{% for x in foo, %}...{% endfor %}'
|
||||||
|
]:
|
||||||
|
assert env.from_string(tmpl)
|
||||||
|
|
||||||
|
def test_trailing_comma(self):
|
||||||
|
tmpl = env.from_string('{{ (1, 2,) }}|{{ [1, 2,] }}|{{ {1: 2,} }}')
|
||||||
|
assert tmpl.render().lower() == '(1, 2)|[1, 2]|{1: 2}'
|
||||||
|
|
||||||
|
def test_block_end_name(self):
|
||||||
|
env.from_string('{% block foo %}...{% endblock foo %}')
|
||||||
|
self.assert_raises(TemplateSyntaxError, env.from_string,
|
||||||
|
'{% block x %}{% endblock y %}')
|
||||||
|
|
||||||
|
def test_constant_casing(self):
|
||||||
|
for const in True, False, None:
|
||||||
|
tmpl = env.from_string('{{ %s }}|{{ %s }}|{{ %s }}' % (
|
||||||
|
str(const), str(const).lower(), str(const).upper()
|
||||||
|
))
|
||||||
|
assert tmpl.render() == '%s|%s|' % (const, const)
|
||||||
|
|
||||||
|
def test_test_chaining(self):
|
||||||
|
self.assert_raises(TemplateSyntaxError, env.from_string,
|
||||||
|
'{{ foo is string is sequence }}')
|
||||||
|
assert env.from_string('{{ 42 is string or 42 is number }}'
|
||||||
|
).render() == 'True'
|
||||||
|
|
||||||
|
def test_string_concatenation(self):
|
||||||
|
tmpl = env.from_string('{{ "foo" "bar" "baz" }}')
|
||||||
|
assert tmpl.render() == 'foobarbaz'
|
||||||
|
|
||||||
|
def test_notin(self):
|
||||||
|
bar = xrange(100)
|
||||||
|
tmpl = env.from_string('''{{ not 42 in bar }}''')
|
||||||
|
assert tmpl.render(bar=bar) == unicode(not 42 in bar)
|
||||||
|
|
||||||
|
def test_implicit_subscribed_tuple(self):
|
||||||
|
class Foo(object):
|
||||||
|
def __getitem__(self, x):
|
||||||
|
return x
|
||||||
|
t = env.from_string('{{ foo[1, 2] }}')
|
||||||
|
assert t.render(foo=Foo()) == u'(1, 2)'
|
||||||
|
|
||||||
|
def test_raw2(self):
|
||||||
|
tmpl = env.from_string('{% raw %}{{ FOO }} and {% BAR %}{% endraw %}')
|
||||||
|
assert tmpl.render() == '{{ FOO }} and {% BAR %}'
|
||||||
|
|
||||||
|
def test_const(self):
|
||||||
|
tmpl = env.from_string('{{ true }}|{{ false }}|{{ none }}|'
|
||||||
|
'{{ none is defined }}|{{ missing is defined }}')
|
||||||
|
assert tmpl.render() == 'True|False|None|True|False'
|
||||||
|
|
||||||
|
def test_neg_filter_priority(self):
|
||||||
|
node = env.parse('{{ -1|foo }}')
|
||||||
|
assert isinstance(node.body[0].nodes[0], nodes.Filter)
|
||||||
|
assert isinstance(node.body[0].nodes[0].node, nodes.Neg)
|
||||||
|
|
||||||
|
def test_const_assign(self):
|
||||||
|
constass1 = '''{% set true = 42 %}'''
|
||||||
|
constass2 = '''{% for none in seq %}{% endfor %}'''
|
||||||
|
for tmpl in constass1, constass2:
|
||||||
|
self.assert_raises(TemplateSyntaxError, env.from_string, tmpl)
|
||||||
|
|
||||||
|
def test_localset(self):
|
||||||
|
tmpl = env.from_string('''{% set foo = 0 %}\
|
||||||
|
{% for item in [1, 2] %}{% set foo = 1 %}{% endfor %}\
|
||||||
|
{{ foo }}''')
|
||||||
|
assert tmpl.render() == '0'
|
||||||
|
|
||||||
|
def test_parse_unary(self):
|
||||||
|
tmpl = env.from_string('{{ -foo["bar"] }}')
|
||||||
|
assert tmpl.render(foo={'bar': 42}) == '-42'
|
||||||
|
tmpl = env.from_string('{{ -foo["bar"]|abs }}')
|
||||||
|
assert tmpl.render(foo={'bar': 42}) == '42'
|
||||||
|
|
||||||
|
|
||||||
|
def suite():
|
||||||
|
suite = unittest.TestSuite()
|
||||||
|
suite.addTest(unittest.makeSuite(LexerTestCase))
|
||||||
|
suite.addTest(unittest.makeSuite(ParserTestCase))
|
||||||
|
suite.addTest(unittest.makeSuite(SyntaxTestCase))
|
||||||
|
return suite
|
||||||
Executable
+218
@@ -0,0 +1,218 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja2.testsuite.loader
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Test the loaders.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import shutil
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from jinja2.testsuite import JinjaTestCase, dict_loader, \
|
||||||
|
package_loader, filesystem_loader, function_loader, \
|
||||||
|
choice_loader, prefix_loader
|
||||||
|
|
||||||
|
from jinja2 import Environment, loaders
|
||||||
|
from jinja2.loaders import split_template_path
|
||||||
|
from jinja2.exceptions import TemplateNotFound
|
||||||
|
|
||||||
|
|
||||||
|
class LoaderTestCase(JinjaTestCase):
|
||||||
|
|
||||||
|
def test_dict_loader(self):
|
||||||
|
env = Environment(loader=dict_loader)
|
||||||
|
tmpl = env.get_template('justdict.html')
|
||||||
|
assert tmpl.render().strip() == 'FOO'
|
||||||
|
self.assert_raises(TemplateNotFound, env.get_template, 'missing.html')
|
||||||
|
|
||||||
|
def test_package_loader(self):
|
||||||
|
env = Environment(loader=package_loader)
|
||||||
|
tmpl = env.get_template('test.html')
|
||||||
|
assert tmpl.render().strip() == 'BAR'
|
||||||
|
self.assert_raises(TemplateNotFound, env.get_template, 'missing.html')
|
||||||
|
|
||||||
|
def test_filesystem_loader(self):
|
||||||
|
env = Environment(loader=filesystem_loader)
|
||||||
|
tmpl = env.get_template('test.html')
|
||||||
|
assert tmpl.render().strip() == 'BAR'
|
||||||
|
tmpl = env.get_template('foo/test.html')
|
||||||
|
assert tmpl.render().strip() == 'FOO'
|
||||||
|
self.assert_raises(TemplateNotFound, env.get_template, 'missing.html')
|
||||||
|
|
||||||
|
def test_choice_loader(self):
|
||||||
|
env = Environment(loader=choice_loader)
|
||||||
|
tmpl = env.get_template('justdict.html')
|
||||||
|
assert tmpl.render().strip() == 'FOO'
|
||||||
|
tmpl = env.get_template('test.html')
|
||||||
|
assert tmpl.render().strip() == 'BAR'
|
||||||
|
self.assert_raises(TemplateNotFound, env.get_template, 'missing.html')
|
||||||
|
|
||||||
|
def test_function_loader(self):
|
||||||
|
env = Environment(loader=function_loader)
|
||||||
|
tmpl = env.get_template('justfunction.html')
|
||||||
|
assert tmpl.render().strip() == 'FOO'
|
||||||
|
self.assert_raises(TemplateNotFound, env.get_template, 'missing.html')
|
||||||
|
|
||||||
|
def test_prefix_loader(self):
|
||||||
|
env = Environment(loader=prefix_loader)
|
||||||
|
tmpl = env.get_template('a/test.html')
|
||||||
|
assert tmpl.render().strip() == 'BAR'
|
||||||
|
tmpl = env.get_template('b/justdict.html')
|
||||||
|
assert tmpl.render().strip() == 'FOO'
|
||||||
|
self.assert_raises(TemplateNotFound, env.get_template, 'missing')
|
||||||
|
|
||||||
|
def test_caching(self):
|
||||||
|
changed = False
|
||||||
|
class TestLoader(loaders.BaseLoader):
|
||||||
|
def get_source(self, environment, template):
|
||||||
|
return u'foo', None, lambda: not changed
|
||||||
|
env = Environment(loader=TestLoader(), cache_size=-1)
|
||||||
|
tmpl = env.get_template('template')
|
||||||
|
assert tmpl is env.get_template('template')
|
||||||
|
changed = True
|
||||||
|
assert tmpl is not env.get_template('template')
|
||||||
|
changed = False
|
||||||
|
|
||||||
|
env = Environment(loader=TestLoader(), cache_size=0)
|
||||||
|
assert env.get_template('template') \
|
||||||
|
is not env.get_template('template')
|
||||||
|
|
||||||
|
env = Environment(loader=TestLoader(), cache_size=2)
|
||||||
|
t1 = env.get_template('one')
|
||||||
|
t2 = env.get_template('two')
|
||||||
|
assert t2 is env.get_template('two')
|
||||||
|
assert t1 is env.get_template('one')
|
||||||
|
t3 = env.get_template('three')
|
||||||
|
assert 'one' in env.cache
|
||||||
|
assert 'two' not in env.cache
|
||||||
|
assert 'three' in env.cache
|
||||||
|
|
||||||
|
def test_split_template_path(self):
|
||||||
|
assert split_template_path('foo/bar') == ['foo', 'bar']
|
||||||
|
assert split_template_path('./foo/bar') == ['foo', 'bar']
|
||||||
|
self.assert_raises(TemplateNotFound, split_template_path, '../foo')
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleLoaderTestCase(JinjaTestCase):
|
||||||
|
archive = None
|
||||||
|
|
||||||
|
def compile_down(self, zip='deflated', py_compile=False):
|
||||||
|
super(ModuleLoaderTestCase, self).setup()
|
||||||
|
log = []
|
||||||
|
self.reg_env = Environment(loader=prefix_loader)
|
||||||
|
if zip is not None:
|
||||||
|
self.archive = tempfile.mkstemp(suffix='.zip')[1]
|
||||||
|
else:
|
||||||
|
self.archive = tempfile.mkdtemp()
|
||||||
|
self.reg_env.compile_templates(self.archive, zip=zip,
|
||||||
|
log_function=log.append,
|
||||||
|
py_compile=py_compile)
|
||||||
|
self.mod_env = Environment(loader=loaders.ModuleLoader(self.archive))
|
||||||
|
return ''.join(log)
|
||||||
|
|
||||||
|
def teardown(self):
|
||||||
|
super(ModuleLoaderTestCase, self).teardown()
|
||||||
|
if hasattr(self, 'mod_env'):
|
||||||
|
if os.path.isfile(self.archive):
|
||||||
|
os.remove(self.archive)
|
||||||
|
else:
|
||||||
|
shutil.rmtree(self.archive)
|
||||||
|
self.archive = None
|
||||||
|
|
||||||
|
def test_log(self):
|
||||||
|
log = self.compile_down()
|
||||||
|
assert 'Compiled "a/foo/test.html" as ' \
|
||||||
|
'tmpl_a790caf9d669e39ea4d280d597ec891c4ef0404a' in log
|
||||||
|
assert 'Finished compiling templates' in log
|
||||||
|
assert 'Could not compile "a/syntaxerror.html": ' \
|
||||||
|
'Encountered unknown tag \'endif\'' in log
|
||||||
|
|
||||||
|
def _test_common(self):
|
||||||
|
tmpl1 = self.reg_env.get_template('a/test.html')
|
||||||
|
tmpl2 = self.mod_env.get_template('a/test.html')
|
||||||
|
assert tmpl1.render() == tmpl2.render()
|
||||||
|
|
||||||
|
tmpl1 = self.reg_env.get_template('b/justdict.html')
|
||||||
|
tmpl2 = self.mod_env.get_template('b/justdict.html')
|
||||||
|
assert tmpl1.render() == tmpl2.render()
|
||||||
|
|
||||||
|
def test_deflated_zip_compile(self):
|
||||||
|
self.compile_down(zip='deflated')
|
||||||
|
self._test_common()
|
||||||
|
|
||||||
|
def test_stored_zip_compile(self):
|
||||||
|
self.compile_down(zip='stored')
|
||||||
|
self._test_common()
|
||||||
|
|
||||||
|
def test_filesystem_compile(self):
|
||||||
|
self.compile_down(zip=None)
|
||||||
|
self._test_common()
|
||||||
|
|
||||||
|
def test_weak_references(self):
|
||||||
|
self.compile_down()
|
||||||
|
tmpl = self.mod_env.get_template('a/test.html')
|
||||||
|
key = loaders.ModuleLoader.get_template_key('a/test.html')
|
||||||
|
name = self.mod_env.loader.module.__name__
|
||||||
|
|
||||||
|
assert hasattr(self.mod_env.loader.module, key)
|
||||||
|
assert name in sys.modules
|
||||||
|
|
||||||
|
# unset all, ensure the module is gone from sys.modules
|
||||||
|
self.mod_env = tmpl = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
import gc
|
||||||
|
gc.collect()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert name not in sys.modules
|
||||||
|
|
||||||
|
def test_byte_compilation(self):
|
||||||
|
log = self.compile_down(py_compile=True)
|
||||||
|
assert 'Byte-compiled "a/test.html"' in log
|
||||||
|
tmpl1 = self.mod_env.get_template('a/test.html')
|
||||||
|
mod = self.mod_env.loader.module. \
|
||||||
|
tmpl_3c4ddf650c1a73df961a6d3d2ce2752f1b8fd490
|
||||||
|
assert mod.__file__.endswith('.pyc')
|
||||||
|
|
||||||
|
def test_choice_loader(self):
|
||||||
|
log = self.compile_down(py_compile=True)
|
||||||
|
assert 'Byte-compiled "a/test.html"' in log
|
||||||
|
|
||||||
|
self.mod_env.loader = loaders.ChoiceLoader([
|
||||||
|
self.mod_env.loader,
|
||||||
|
loaders.DictLoader({'DICT_SOURCE': 'DICT_TEMPLATE'})
|
||||||
|
])
|
||||||
|
|
||||||
|
tmpl1 = self.mod_env.get_template('a/test.html')
|
||||||
|
self.assert_equal(tmpl1.render(), 'BAR')
|
||||||
|
tmpl2 = self.mod_env.get_template('DICT_SOURCE')
|
||||||
|
self.assert_equal(tmpl2.render(), 'DICT_TEMPLATE')
|
||||||
|
|
||||||
|
def test_prefix_loader(self):
|
||||||
|
log = self.compile_down(py_compile=True)
|
||||||
|
assert 'Byte-compiled "a/test.html"' in log
|
||||||
|
|
||||||
|
self.mod_env.loader = loaders.PrefixLoader({
|
||||||
|
'MOD': self.mod_env.loader,
|
||||||
|
'DICT': loaders.DictLoader({'test.html': 'DICT_TEMPLATE'})
|
||||||
|
})
|
||||||
|
|
||||||
|
tmpl1 = self.mod_env.get_template('MOD/a/test.html')
|
||||||
|
self.assert_equal(tmpl1.render(), 'BAR')
|
||||||
|
tmpl2 = self.mod_env.get_template('DICT/test.html')
|
||||||
|
self.assert_equal(tmpl2.render(), 'DICT_TEMPLATE')
|
||||||
|
|
||||||
|
|
||||||
|
def suite():
|
||||||
|
suite = unittest.TestSuite()
|
||||||
|
suite.addTest(unittest.makeSuite(LoaderTestCase))
|
||||||
|
suite.addTest(unittest.makeSuite(ModuleLoaderTestCase))
|
||||||
|
return suite
|
||||||
Executable
+255
@@ -0,0 +1,255 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja2.testsuite.regression
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tests corner cases and bugs.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from jinja2.testsuite import JinjaTestCase
|
||||||
|
|
||||||
|
from jinja2 import Template, Environment, DictLoader, TemplateSyntaxError, \
|
||||||
|
TemplateNotFound, PrefixLoader
|
||||||
|
|
||||||
|
env = Environment()
|
||||||
|
|
||||||
|
|
||||||
|
class CornerTestCase(JinjaTestCase):
|
||||||
|
|
||||||
|
def test_assigned_scoping(self):
|
||||||
|
t = env.from_string('''
|
||||||
|
{%- for item in (1, 2, 3, 4) -%}
|
||||||
|
[{{ item }}]
|
||||||
|
{%- endfor %}
|
||||||
|
{{- item -}}
|
||||||
|
''')
|
||||||
|
assert t.render(item=42) == '[1][2][3][4]42'
|
||||||
|
|
||||||
|
t = env.from_string('''
|
||||||
|
{%- for item in (1, 2, 3, 4) -%}
|
||||||
|
[{{ item }}]
|
||||||
|
{%- endfor %}
|
||||||
|
{%- set item = 42 %}
|
||||||
|
{{- item -}}
|
||||||
|
''')
|
||||||
|
assert t.render() == '[1][2][3][4]42'
|
||||||
|
|
||||||
|
t = env.from_string('''
|
||||||
|
{%- set item = 42 %}
|
||||||
|
{%- for item in (1, 2, 3, 4) -%}
|
||||||
|
[{{ item }}]
|
||||||
|
{%- endfor %}
|
||||||
|
{{- item -}}
|
||||||
|
''')
|
||||||
|
assert t.render() == '[1][2][3][4]42'
|
||||||
|
|
||||||
|
def test_closure_scoping(self):
|
||||||
|
t = env.from_string('''
|
||||||
|
{%- set wrapper = "<FOO>" %}
|
||||||
|
{%- for item in (1, 2, 3, 4) %}
|
||||||
|
{%- macro wrapper() %}[{{ item }}]{% endmacro %}
|
||||||
|
{{- wrapper() }}
|
||||||
|
{%- endfor %}
|
||||||
|
{{- wrapper -}}
|
||||||
|
''')
|
||||||
|
assert t.render() == '[1][2][3][4]<FOO>'
|
||||||
|
|
||||||
|
t = env.from_string('''
|
||||||
|
{%- for item in (1, 2, 3, 4) %}
|
||||||
|
{%- macro wrapper() %}[{{ item }}]{% endmacro %}
|
||||||
|
{{- wrapper() }}
|
||||||
|
{%- endfor %}
|
||||||
|
{%- set wrapper = "<FOO>" %}
|
||||||
|
{{- wrapper -}}
|
||||||
|
''')
|
||||||
|
assert t.render() == '[1][2][3][4]<FOO>'
|
||||||
|
|
||||||
|
t = env.from_string('''
|
||||||
|
{%- for item in (1, 2, 3, 4) %}
|
||||||
|
{%- macro wrapper() %}[{{ item }}]{% endmacro %}
|
||||||
|
{{- wrapper() }}
|
||||||
|
{%- endfor %}
|
||||||
|
{{- wrapper -}}
|
||||||
|
''')
|
||||||
|
assert t.render(wrapper=23) == '[1][2][3][4]23'
|
||||||
|
|
||||||
|
|
||||||
|
class BugTestCase(JinjaTestCase):
|
||||||
|
|
||||||
|
def test_keyword_folding(self):
|
||||||
|
env = Environment()
|
||||||
|
env.filters['testing'] = lambda value, some: value + some
|
||||||
|
assert env.from_string("{{ 'test'|testing(some='stuff') }}") \
|
||||||
|
.render() == 'teststuff'
|
||||||
|
|
||||||
|
def test_extends_output_bugs(self):
|
||||||
|
env = Environment(loader=DictLoader({
|
||||||
|
'parent.html': '(({% block title %}{% endblock %}))'
|
||||||
|
}))
|
||||||
|
|
||||||
|
t = env.from_string('{% if expr %}{% extends "parent.html" %}{% endif %}'
|
||||||
|
'[[{% block title %}title{% endblock %}]]'
|
||||||
|
'{% for item in [1, 2, 3] %}({{ item }}){% endfor %}')
|
||||||
|
assert t.render(expr=False) == '[[title]](1)(2)(3)'
|
||||||
|
assert t.render(expr=True) == '((title))'
|
||||||
|
|
||||||
|
def test_urlize_filter_escaping(self):
|
||||||
|
tmpl = env.from_string('{{ "http://www.example.org/<foo"|urlize }}')
|
||||||
|
assert tmpl.render() == '<a href="http://www.example.org/<foo">http://www.example.org/<foo</a>'
|
||||||
|
|
||||||
|
def test_loop_call_loop(self):
|
||||||
|
tmpl = env.from_string('''
|
||||||
|
|
||||||
|
{% macro test() %}
|
||||||
|
{{ caller() }}
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% for num1 in range(5) %}
|
||||||
|
{% call test() %}
|
||||||
|
{% for num2 in range(10) %}
|
||||||
|
{{ loop.index }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endcall %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
''')
|
||||||
|
|
||||||
|
assert tmpl.render().split() == map(unicode, range(1, 11)) * 5
|
||||||
|
|
||||||
|
def test_weird_inline_comment(self):
|
||||||
|
env = Environment(line_statement_prefix='%')
|
||||||
|
self.assert_raises(TemplateSyntaxError, env.from_string,
|
||||||
|
'% for item in seq {# missing #}\n...% endfor')
|
||||||
|
|
||||||
|
def test_old_macro_loop_scoping_bug(self):
|
||||||
|
tmpl = env.from_string('{% for i in (1, 2) %}{{ i }}{% endfor %}'
|
||||||
|
'{% macro i() %}3{% endmacro %}{{ i() }}')
|
||||||
|
assert tmpl.render() == '123'
|
||||||
|
|
||||||
|
def test_partial_conditional_assignments(self):
|
||||||
|
tmpl = env.from_string('{% if b %}{% set a = 42 %}{% endif %}{{ a }}')
|
||||||
|
assert tmpl.render(a=23) == '23'
|
||||||
|
assert tmpl.render(b=True) == '42'
|
||||||
|
|
||||||
|
def test_stacked_locals_scoping_bug(self):
|
||||||
|
env = Environment(line_statement_prefix='#')
|
||||||
|
t = env.from_string('''\
|
||||||
|
# for j in [1, 2]:
|
||||||
|
# set x = 1
|
||||||
|
# for i in [1, 2]:
|
||||||
|
# print x
|
||||||
|
# if i % 2 == 0:
|
||||||
|
# set x = x + 1
|
||||||
|
# endif
|
||||||
|
# endfor
|
||||||
|
# endfor
|
||||||
|
# if a
|
||||||
|
# print 'A'
|
||||||
|
# elif b
|
||||||
|
# print 'B'
|
||||||
|
# elif c == d
|
||||||
|
# print 'C'
|
||||||
|
# else
|
||||||
|
# print 'D'
|
||||||
|
# endif
|
||||||
|
''')
|
||||||
|
assert t.render(a=0, b=False, c=42, d=42.0) == '1111C'
|
||||||
|
|
||||||
|
def test_stacked_locals_scoping_bug_twoframe(self):
|
||||||
|
t = Template('''
|
||||||
|
{% set x = 1 %}
|
||||||
|
{% for item in foo %}
|
||||||
|
{% if item == 1 %}
|
||||||
|
{% set x = 2 %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{{ x }}
|
||||||
|
''')
|
||||||
|
rv = t.render(foo=[1]).strip()
|
||||||
|
assert rv == u'1'
|
||||||
|
|
||||||
|
def test_call_with_args(self):
|
||||||
|
t = Template("""{% macro dump_users(users) -%}
|
||||||
|
<ul>
|
||||||
|
{%- for user in users -%}
|
||||||
|
<li><p>{{ user.username|e }}</p>{{ caller(user) }}</li>
|
||||||
|
{%- endfor -%}
|
||||||
|
</ul>
|
||||||
|
{%- endmacro -%}
|
||||||
|
|
||||||
|
{% call(user) dump_users(list_of_user) -%}
|
||||||
|
<dl>
|
||||||
|
<dl>Realname</dl>
|
||||||
|
<dd>{{ user.realname|e }}</dd>
|
||||||
|
<dl>Description</dl>
|
||||||
|
<dd>{{ user.description }}</dd>
|
||||||
|
</dl>
|
||||||
|
{% endcall %}""")
|
||||||
|
|
||||||
|
assert [x.strip() for x in t.render(list_of_user=[{
|
||||||
|
'username':'apo',
|
||||||
|
'realname':'something else',
|
||||||
|
'description':'test'
|
||||||
|
}]).splitlines()] == [
|
||||||
|
u'<ul><li><p>apo</p><dl>',
|
||||||
|
u'<dl>Realname</dl>',
|
||||||
|
u'<dd>something else</dd>',
|
||||||
|
u'<dl>Description</dl>',
|
||||||
|
u'<dd>test</dd>',
|
||||||
|
u'</dl>',
|
||||||
|
u'</li></ul>'
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_empty_if_condition_fails(self):
|
||||||
|
self.assert_raises(TemplateSyntaxError, Template, '{% if %}....{% endif %}')
|
||||||
|
self.assert_raises(TemplateSyntaxError, Template, '{% if foo %}...{% elif %}...{% endif %}')
|
||||||
|
self.assert_raises(TemplateSyntaxError, Template, '{% for x in %}..{% endfor %}')
|
||||||
|
|
||||||
|
def test_recursive_loop_bug(self):
|
||||||
|
tpl1 = Template("""
|
||||||
|
{% for p in foo recursive%}
|
||||||
|
{{p.bar}}
|
||||||
|
{% for f in p.fields recursive%}
|
||||||
|
{{f.baz}}
|
||||||
|
{{p.bar}}
|
||||||
|
{% if f.rec %}
|
||||||
|
{{ loop(f.sub) }}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
""")
|
||||||
|
|
||||||
|
tpl2 = Template("""
|
||||||
|
{% for p in foo%}
|
||||||
|
{{p.bar}}
|
||||||
|
{% for f in p.fields recursive%}
|
||||||
|
{{f.baz}}
|
||||||
|
{{p.bar}}
|
||||||
|
{% if f.rec %}
|
||||||
|
{{ loop(f.sub) }}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
""")
|
||||||
|
|
||||||
|
def test_correct_prefix_loader_name(self):
|
||||||
|
env = Environment(loader=PrefixLoader({
|
||||||
|
'foo': DictLoader({})
|
||||||
|
}))
|
||||||
|
try:
|
||||||
|
env.get_template('foo/bar.html')
|
||||||
|
except TemplateNotFound, e:
|
||||||
|
assert e.name == 'foo/bar.html'
|
||||||
|
else:
|
||||||
|
assert False, 'expected error here'
|
||||||
|
|
||||||
|
|
||||||
|
def suite():
|
||||||
|
suite = unittest.TestSuite()
|
||||||
|
suite.addTest(unittest.makeSuite(CornerTestCase))
|
||||||
|
suite.addTest(unittest.makeSuite(BugTestCase))
|
||||||
|
return suite
|
||||||
Executable
+3
@@ -0,0 +1,3 @@
|
|||||||
|
Before
|
||||||
|
{{ fail() }}
|
||||||
|
After
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
FOO
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
Foo
|
||||||
|
{% for item in broken %}
|
||||||
|
...
|
||||||
|
{% endif %}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user