Remove Flask dependencies
This commit is contained in:
1 parent
1694ed7758
commit
6e0857c6c1
157 files changed
+757
-37916
No files matched your search
+46
-46
@@ -1,84 +1,84 @@
|
|||||||
from couchpotato.api import api_docs, api_docs_missing
|
from couchpotato.api import api_docs, api_docs_missing, api
|
||||||
from couchpotato.core.auth import requires_auth
|
from couchpotato.core.auth import requires_auth
|
||||||
from couchpotato.core.event import fireEvent
|
from couchpotato.core.event import fireEvent
|
||||||
from couchpotato.core.helpers.request import getParams, jsonified
|
|
||||||
from couchpotato.core.helpers.variable import md5
|
from couchpotato.core.helpers.variable import md5
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
from flask.app import Flask
|
|
||||||
from flask.blueprints import Blueprint
|
|
||||||
from flask.globals import request
|
|
||||||
from flask.helpers import url_for
|
|
||||||
from flask.templating import render_template
|
|
||||||
from sqlalchemy.engine import create_engine
|
from sqlalchemy.engine import create_engine
|
||||||
from sqlalchemy.orm import scoped_session
|
from sqlalchemy.orm import scoped_session
|
||||||
from sqlalchemy.orm.session import sessionmaker
|
from sqlalchemy.orm.session import sessionmaker
|
||||||
from werkzeug.utils import redirect
|
from tornado import template
|
||||||
|
from tornado.web import RequestHandler
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
app = Flask(__name__, static_folder = 'nope')
|
views = {}
|
||||||
web = Blueprint('web', __name__)
|
template_loader = template.Loader(os.path.join(os.path.dirname(__file__), 'templates'))
|
||||||
|
|
||||||
|
# Main web handler
|
||||||
|
@requires_auth
|
||||||
|
class WebHandler(RequestHandler):
|
||||||
|
def get(self, route):
|
||||||
|
if not views.get(route):
|
||||||
|
page_not_found(self)
|
||||||
|
return
|
||||||
|
self.write(views[route]())
|
||||||
|
|
||||||
|
def addView(route, func, static = False):
|
||||||
|
views[route] = func
|
||||||
|
|
||||||
def get_session(engine = None):
|
def get_session(engine = None):
|
||||||
return Env.getSession(engine)
|
return Env.getSession(engine)
|
||||||
|
|
||||||
def addView(route, func, static = False):
|
|
||||||
web.add_url_rule(route + ('' if static else '/'), endpoint = route if route else 'index', view_func = func)
|
|
||||||
|
|
||||||
""" Web view """
|
# Web view
|
||||||
@web.route('/')
|
|
||||||
@requires_auth
|
|
||||||
def index():
|
def index():
|
||||||
return render_template('index.html', sep = os.sep, fireEvent = fireEvent, env = Env)
|
return template_loader.load('index.html').generate(sep = os.sep, fireEvent = fireEvent, Env = Env)
|
||||||
|
addView('', index)
|
||||||
|
|
||||||
""" Api view """
|
# API docs
|
||||||
@web.route('docs/')
|
|
||||||
@requires_auth
|
|
||||||
def apiDocs():
|
def apiDocs():
|
||||||
from couchpotato import app
|
|
||||||
routes = []
|
routes = []
|
||||||
for route, x in sorted(app.view_functions.iteritems()):
|
|
||||||
if route[0:4] == 'api.':
|
for route in api.iterkeys():
|
||||||
routes += [route[4:].replace('::', '.')]
|
routes.append(route)
|
||||||
|
|
||||||
if api_docs.get(''):
|
if api_docs.get(''):
|
||||||
del api_docs['']
|
del api_docs['']
|
||||||
del api_docs_missing['']
|
del api_docs_missing['']
|
||||||
return render_template('api.html', fireEvent = fireEvent, routes = sorted(routes), api_docs = api_docs, api_docs_missing = sorted(api_docs_missing))
|
|
||||||
|
|
||||||
@web.route('getkey/')
|
return template_loader.load('api.html').generate(fireEvent = fireEvent, routes = sorted(routes), api_docs = api_docs, api_docs_missing = sorted(api_docs_missing), Env = Env)
|
||||||
def getApiKey():
|
|
||||||
|
|
||||||
api = None
|
addView('docs', apiDocs)
|
||||||
params = getParams()
|
|
||||||
username = Env.setting('username')
|
|
||||||
password = Env.setting('password')
|
|
||||||
|
|
||||||
if (params.get('u') == md5(username) or not username) and (params.get('p') == password or not password):
|
# Make non basic auth option to get api key
|
||||||
api = Env.setting('api_key')
|
class KeyHandler(RequestHandler):
|
||||||
|
def get(self):
|
||||||
|
api = None
|
||||||
|
username = Env.setting('username')
|
||||||
|
password = Env.setting('password')
|
||||||
|
|
||||||
return jsonified({
|
if (self.get_argument('u') == md5(username) or not username) and (self.get_argument('p') == password or not password):
|
||||||
'success': api is not None,
|
api = Env.setting('api_key')
|
||||||
'api_key': api
|
|
||||||
})
|
|
||||||
|
|
||||||
@app.errorhandler(404)
|
self.write({
|
||||||
def page_not_found(error):
|
'success': api is not None,
|
||||||
index_url = url_for('web.index')
|
'api_key': api
|
||||||
url = request.path[len(index_url):]
|
})
|
||||||
|
|
||||||
|
def page_not_found(rh):
|
||||||
|
index_url = Env.get('web_base')
|
||||||
|
url = rh.request.uri[len(index_url):]
|
||||||
|
|
||||||
if url[:3] != 'api':
|
if url[:3] != 'api':
|
||||||
if request.path != '/':
|
r = index_url + '#' + url.lstrip('/')
|
||||||
r = request.url.replace(request.path, index_url + '#' + url)
|
rh.redirect(r)
|
||||||
else:
|
|
||||||
r = '%s%s' % (request.url.rstrip('/'), index_url + '#' + url)
|
|
||||||
return redirect(r)
|
|
||||||
else:
|
else:
|
||||||
if not Env.get('dev'):
|
if not Env.get('dev'):
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
return 'Wrong API key used', 404
|
|
||||||
|
rh.set_status(404)
|
||||||
|
rh.write('Wrong API key used')
|
||||||
|
|
||||||
+40
-19
@@ -1,14 +1,14 @@
|
|||||||
from flask.blueprints import Blueprint
|
|
||||||
from flask.helpers import url_for
|
|
||||||
from tornado.web import RequestHandler, asynchronous
|
from tornado.web import RequestHandler, asynchronous
|
||||||
from werkzeug.utils import redirect
|
import json
|
||||||
|
import urllib
|
||||||
|
|
||||||
api = Blueprint('api', __name__)
|
api = {}
|
||||||
api_docs = {}
|
|
||||||
api_docs_missing = []
|
|
||||||
api_nonblock = {}
|
api_nonblock = {}
|
||||||
|
|
||||||
|
api_docs = {}
|
||||||
|
api_docs_missing = []
|
||||||
|
|
||||||
|
# NonBlock API handler
|
||||||
class NonBlockHandler(RequestHandler):
|
class NonBlockHandler(RequestHandler):
|
||||||
|
|
||||||
stoppers = []
|
stoppers = []
|
||||||
@@ -32,14 +32,6 @@ class NonBlockHandler(RequestHandler):
|
|||||||
|
|
||||||
self.stoppers = []
|
self.stoppers = []
|
||||||
|
|
||||||
|
|
||||||
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, **kwargs)
|
|
||||||
if docs:
|
|
||||||
api_docs[route[4:] if route[0:4] == 'api.' else route] = docs
|
|
||||||
else:
|
|
||||||
api_docs_missing.append(route)
|
|
||||||
|
|
||||||
def addNonBlockApiView(route, func_tuple, docs = None, **kwargs):
|
def addNonBlockApiView(route, func_tuple, docs = None, **kwargs):
|
||||||
api_nonblock[route] = func_tuple
|
api_nonblock[route] = func_tuple
|
||||||
|
|
||||||
@@ -48,9 +40,38 @@ def addNonBlockApiView(route, func_tuple, docs = None, **kwargs):
|
|||||||
else:
|
else:
|
||||||
api_docs_missing.append(route)
|
api_docs_missing.append(route)
|
||||||
|
|
||||||
""" Api view """
|
# Blocking API handler
|
||||||
def index():
|
class ApiHandler(RequestHandler):
|
||||||
index_url = url_for('web.index')
|
|
||||||
return redirect(index_url + 'docs/')
|
|
||||||
|
|
||||||
addApiView('', index)
|
def get(self, route):
|
||||||
|
if not api.get(route):
|
||||||
|
self.write('API call doesn\'t seem to exist')
|
||||||
|
|
||||||
|
kwargs = {}
|
||||||
|
for x in self.request.arguments:
|
||||||
|
kwargs[x] = urllib.unquote(self.get_argument(x))
|
||||||
|
|
||||||
|
# Remove t random string
|
||||||
|
try: del kwargs['t']
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
# Check JSONP callback
|
||||||
|
result = api[route](**kwargs)
|
||||||
|
jsonp_callback = self.get_argument('callback_func', default = None)
|
||||||
|
|
||||||
|
if jsonp_callback:
|
||||||
|
self.write(str(jsonp_callback) + '(' + json.dumps(result) + ')')
|
||||||
|
elif isinstance(result, (tuple)) and result[0] == 'redirect':
|
||||||
|
self.redirect(result[1])
|
||||||
|
else:
|
||||||
|
self.write(result)
|
||||||
|
|
||||||
|
def addApiView(route, func, static = False, docs = None, **kwargs):
|
||||||
|
|
||||||
|
if static: func(route)
|
||||||
|
else: api[route] = func
|
||||||
|
|
||||||
|
if docs:
|
||||||
|
api_docs[route[4:] if route[0:4] == 'api.' else route] = docs
|
||||||
|
else:
|
||||||
|
api_docs_missing.append(route)
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import fireEvent, addEvent
|
from couchpotato.core.event import fireEvent, addEvent
|
||||||
from couchpotato.core.helpers.request import jsonified
|
|
||||||
from couchpotato.core.helpers.variable import cleanHost, md5
|
from couchpotato.core.helpers.variable import cleanHost, md5
|
||||||
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
|
||||||
@@ -69,9 +68,9 @@ class Core(Plugin):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def available(self):
|
def available(self):
|
||||||
return jsonified({
|
return {
|
||||||
'success': True
|
'success': True
|
||||||
})
|
}
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
if self.shutdown_started:
|
if self.shutdown_started:
|
||||||
@@ -156,10 +155,10 @@ class Core(Plugin):
|
|||||||
host = 'localhost'
|
host = 'localhost'
|
||||||
port = Env.setting('port')
|
port = Env.setting('port')
|
||||||
|
|
||||||
return '%s:%d%s' % (cleanHost(host).rstrip('/'), int(port), '/' + Env.setting('url_base').lstrip('/') if Env.setting('url_base') else '')
|
return '%s:%d%s' % (cleanHost(host).rstrip('/'), int(port), Env.get('web_base'))
|
||||||
|
|
||||||
def createApiUrl(self):
|
def createApiUrl(self):
|
||||||
return '%s/api/%s' % (self.createBaseUrl(), Env.setting('api_key'))
|
return '%sapi/%s' % (self.createBaseUrl(), Env.setting('api_key'))
|
||||||
|
|
||||||
def version(self):
|
def version(self):
|
||||||
ver = fireEvent('updater.info', single = True)
|
ver = fireEvent('updater.info', single = True)
|
||||||
@@ -171,9 +170,9 @@ class Core(Plugin):
|
|||||||
return '%s - %s-%s - v2' % (platf, ver.get('version')['type'], ver.get('version')['hash'])
|
return '%s - %s-%s - v2' % (platf, ver.get('version')['type'], ver.get('version')['hash'])
|
||||||
|
|
||||||
def versionView(self):
|
def versionView(self):
|
||||||
return jsonified({
|
return {
|
||||||
'version': self.version()
|
'version': self.version()
|
||||||
})
|
}
|
||||||
|
|
||||||
def signalHandler(self):
|
def signalHandler(self):
|
||||||
if Env.get('daemonized'): return
|
if Env.get('daemonized'): return
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
|
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
|
||||||
from couchpotato.core.helpers.encoding import ss
|
from couchpotato.core.helpers.encoding import ss
|
||||||
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
|
from couchpotato.environment import Env
|
||||||
@@ -36,7 +35,7 @@ class Updater(Plugin):
|
|||||||
addEvent('app.load', self.setCrons)
|
addEvent('app.load', self.setCrons)
|
||||||
addEvent('updater.info', self.info)
|
addEvent('updater.info', self.info)
|
||||||
|
|
||||||
addApiView('updater.info', self.getInfo, docs = {
|
addApiView('updater.info', self.info, docs = {
|
||||||
'desc': 'Get updater information',
|
'desc': 'Get updater information',
|
||||||
'return': {
|
'return': {
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
@@ -92,17 +91,14 @@ class Updater(Plugin):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def info(self):
|
def info(self, **kwargs):
|
||||||
return self.updater.info()
|
return self.updater.info()
|
||||||
|
|
||||||
def getInfo(self):
|
|
||||||
return jsonified(self.updater.info())
|
|
||||||
|
|
||||||
def checkView(self):
|
def checkView(self):
|
||||||
return jsonified({
|
return {
|
||||||
'update_available': self.check(force = True),
|
'update_available': self.check(force = True),
|
||||||
'info': self.updater.info()
|
'info': self.updater.info()
|
||||||
})
|
}
|
||||||
|
|
||||||
def doUpdateView(self):
|
def doUpdateView(self):
|
||||||
|
|
||||||
@@ -119,9 +115,9 @@ class Updater(Plugin):
|
|||||||
if not success:
|
if not success:
|
||||||
success = True
|
success = True
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'success': success
|
'success': success
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class BaseUpdater(Plugin):
|
class BaseUpdater(Plugin):
|
||||||
@@ -138,9 +134,6 @@ class BaseUpdater(Plugin):
|
|||||||
def doUpdate(self):
|
def doUpdate(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def getInfo(self):
|
|
||||||
return jsonified(self.info())
|
|
||||||
|
|
||||||
def info(self):
|
def info(self):
|
||||||
return {
|
return {
|
||||||
'last_check': self.last_check,
|
'last_check': self.last_check,
|
||||||
|
|||||||
+41
-16
@@ -1,26 +1,51 @@
|
|||||||
from couchpotato.core.helpers.variable import md5
|
from couchpotato.core.helpers.variable import md5
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
from flask import request, Response
|
import base64
|
||||||
from functools import wraps
|
|
||||||
|
|
||||||
def check_auth(username, password):
|
def check_auth(username, password):
|
||||||
return username == Env.setting('username') and password == Env.setting('password')
|
return username == Env.setting('username') and password == Env.setting('password')
|
||||||
|
|
||||||
def authenticate():
|
def requires_auth(handler_class):
|
||||||
return Response(
|
|
||||||
'This is not the page you are looking for. *waves hand*', 401,
|
|
||||||
{'WWW-Authenticate': 'Basic realm="CouchPotato Login"'}
|
|
||||||
)
|
|
||||||
|
|
||||||
def requires_auth(f):
|
def wrap_execute(handler_execute):
|
||||||
|
|
||||||
@wraps(f)
|
def require_basic_auth(handler, kwargs):
|
||||||
def decorated(*args, **kwargs):
|
if Env.setting('username') and Env.setting('password'):
|
||||||
auth = getattr(request, 'authorization')
|
|
||||||
if Env.setting('username') and Env.setting('password'):
|
|
||||||
if (not auth or not check_auth(auth.username.decode('latin1'), md5(auth.password.decode('latin1').encode(Env.get('encoding'))))):
|
|
||||||
return authenticate()
|
|
||||||
|
|
||||||
return f(*args, **kwargs)
|
auth_header = handler.request.headers.get('Authorization')
|
||||||
|
auth_decoded = base64.decodestring(auth_header[6:]) if auth_header else None
|
||||||
|
if auth_decoded:
|
||||||
|
username, password = auth_decoded.split(':', 2)
|
||||||
|
|
||||||
return decorated
|
if auth_header is None or not auth_header.startswith('Basic ') or (not check_auth(username.decode('latin'), md5(password.decode('latin')))):
|
||||||
|
handler.set_status(401)
|
||||||
|
handler.set_header('WWW-Authenticate', 'Basic realm="CouchPotato Login"')
|
||||||
|
handler._transforms = []
|
||||||
|
handler.finish()
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _execute(self, transforms, *args, **kwargs):
|
||||||
|
|
||||||
|
if not require_basic_auth(self, kwargs):
|
||||||
|
return False
|
||||||
|
return handler_execute(self, transforms, *args, **kwargs)
|
||||||
|
|
||||||
|
return _execute
|
||||||
|
|
||||||
|
handler_class._execute = wrap_execute(handler_class._execute)
|
||||||
|
|
||||||
|
return handler_class
|
||||||
|
|
||||||
|
# @wraps(f)
|
||||||
|
# def decorated(*args, **kwargs):
|
||||||
|
# auth = getattr(request, 'authorization')
|
||||||
|
# if Env.setting('username') and Env.setting('password'):
|
||||||
|
# if (not auth or not check_auth(auth.username.decode('latin1'), md5(auth.password.decode('latin1').encode(Env.get('encoding'))))):
|
||||||
|
# return authenticate()
|
||||||
|
#
|
||||||
|
# return f(*args, **kwargs)
|
||||||
|
#
|
||||||
|
# return decorated
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
from couchpotato.core.helpers.encoding import toUnicode
|
|
||||||
from couchpotato.core.helpers.variable import natcmp
|
|
||||||
from flask.globals import current_app
|
|
||||||
from flask.helpers import json, make_response
|
|
||||||
from urllib import unquote
|
|
||||||
from werkzeug.urls import url_decode
|
|
||||||
import flask
|
|
||||||
import re
|
|
||||||
|
|
||||||
def getParams():
|
|
||||||
|
|
||||||
params = url_decode(getattr(flask.request, 'environ').get('QUERY_STRING', ''))
|
|
||||||
reg = re.compile('^[a-z0-9_\.]+$')
|
|
||||||
|
|
||||||
current = temp = {}
|
|
||||||
for param, value in sorted(params.iteritems()):
|
|
||||||
|
|
||||||
nest = re.split("([\[\]]+)", param)
|
|
||||||
if len(nest) > 1:
|
|
||||||
nested = []
|
|
||||||
for key in nest:
|
|
||||||
if reg.match(key):
|
|
||||||
nested.append(key)
|
|
||||||
|
|
||||||
current = temp
|
|
||||||
|
|
||||||
for item in nested:
|
|
||||||
if item is nested[-1]:
|
|
||||||
current[item] = toUnicode(unquote(value))
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
current[item]
|
|
||||||
except:
|
|
||||||
current[item] = {}
|
|
||||||
|
|
||||||
current = current[item]
|
|
||||||
else:
|
|
||||||
temp[param] = toUnicode(unquote(value))
|
|
||||||
|
|
||||||
return dictToList(temp)
|
|
||||||
|
|
||||||
def dictToList(params):
|
|
||||||
|
|
||||||
if type(params) is dict:
|
|
||||||
new = {}
|
|
||||||
for x, value in params.iteritems():
|
|
||||||
try:
|
|
||||||
new_value = [dictToList(value[k]) for k in sorted(value.iterkeys(), cmp = natcmp)]
|
|
||||||
except:
|
|
||||||
new_value = value
|
|
||||||
|
|
||||||
new[x] = new_value
|
|
||||||
else:
|
|
||||||
new = params
|
|
||||||
|
|
||||||
return new
|
|
||||||
|
|
||||||
def getParam(attr, default = None):
|
|
||||||
try:
|
|
||||||
return getParams().get(attr, default)
|
|
||||||
except:
|
|
||||||
return default
|
|
||||||
|
|
||||||
def padded_jsonify(callback, *args, **kwargs):
|
|
||||||
content = str(callback) + '(' + json.dumps(dict(*args, **kwargs)) + ')'
|
|
||||||
return getattr(current_app, 'response_class')(content, mimetype = 'text/javascript')
|
|
||||||
|
|
||||||
def jsonify(mimetype, *args, **kwargs):
|
|
||||||
content = json.dumps(dict(*args, **kwargs))
|
|
||||||
return getattr(current_app, 'response_class')(content, mimetype = mimetype)
|
|
||||||
|
|
||||||
def jsonified(*args, **kwargs):
|
|
||||||
callback = getParam('callback_func', None)
|
|
||||||
if callback:
|
|
||||||
content = padded_jsonify(callback, *args, **kwargs)
|
|
||||||
else:
|
|
||||||
content = jsonify('application/json', *args, **kwargs)
|
|
||||||
|
|
||||||
response = make_response(content)
|
|
||||||
response.cache_control.no_cache = True
|
|
||||||
|
|
||||||
return response
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import addEvent
|
from couchpotato.core.event import addEvent
|
||||||
from couchpotato.core.helpers.request import jsonified
|
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.base import Provider
|
from couchpotato.core.providers.base import Provider
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
@@ -62,7 +61,9 @@ class Notification(Provider):
|
|||||||
listener = 'test'
|
listener = 'test'
|
||||||
)
|
)
|
||||||
|
|
||||||
return jsonified({'success': success})
|
return {
|
||||||
|
'success': success
|
||||||
|
}
|
||||||
|
|
||||||
def testNotifyName(self):
|
def testNotifyName(self):
|
||||||
return 'notify.%s.test' % self.getName().lower()
|
return 'notify.%s.test' % self.getName().lower()
|
||||||
@@ -2,7 +2,6 @@ from couchpotato import get_session
|
|||||||
from couchpotato.api import addApiView, addNonBlockApiView
|
from couchpotato.api import addApiView, addNonBlockApiView
|
||||||
from couchpotato.core.event import addEvent, fireEvent
|
from couchpotato.core.event import addEvent, fireEvent
|
||||||
from couchpotato.core.helpers.encoding import toUnicode
|
from couchpotato.core.helpers.encoding import toUnicode
|
||||||
from couchpotato.core.helpers.request import jsonified, getParam
|
|
||||||
from couchpotato.core.helpers.variable import tryInt, splitString
|
from couchpotato.core.helpers.variable import tryInt, splitString
|
||||||
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
|
||||||
@@ -62,11 +61,9 @@ class CoreNotifier(Notification):
|
|||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
|
|
||||||
def markAsRead(self):
|
def markAsRead(self, ids = None):
|
||||||
|
|
||||||
ids = None
|
ids = splitString(ids) if ids else None
|
||||||
if getParam('ids'):
|
|
||||||
ids = splitString(getParam('ids'))
|
|
||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
|
|
||||||
@@ -79,14 +76,13 @@ class CoreNotifier(Notification):
|
|||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'success': True
|
'success': True
|
||||||
})
|
}
|
||||||
|
|
||||||
def listView(self):
|
def listView(self, limit_offset = None, **kwargs):
|
||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
limit_offset = getParam('limit_offset', None)
|
|
||||||
|
|
||||||
q = db.query(Notif)
|
q = db.query(Notif)
|
||||||
|
|
||||||
@@ -105,11 +101,11 @@ class CoreNotifier(Notification):
|
|||||||
ndict['type'] = 'notification'
|
ndict['type'] = 'notification'
|
||||||
notifications.append(ndict)
|
notifications.append(ndict)
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
'empty': len(notifications) == 0,
|
'empty': len(notifications) == 0,
|
||||||
'notifications': notifications
|
'notifications': notifications
|
||||||
})
|
}
|
||||||
|
|
||||||
def checkMessages(self):
|
def checkMessages(self):
|
||||||
|
|
||||||
@@ -219,12 +215,12 @@ class CoreNotifier(Notification):
|
|||||||
|
|
||||||
return recent or []
|
return recent or []
|
||||||
|
|
||||||
def listener(self):
|
def listener(self, init = False, **kwargs):
|
||||||
|
|
||||||
messages = []
|
messages = []
|
||||||
|
|
||||||
# Get unread
|
# Get unread
|
||||||
if getParam('init'):
|
if init:
|
||||||
db = get_session()
|
db = get_session()
|
||||||
|
|
||||||
notifications = db.query(Notif) \
|
notifications = db.query(Notif) \
|
||||||
@@ -235,7 +231,7 @@ class CoreNotifier(Notification):
|
|||||||
ndict['type'] = 'notification'
|
ndict['type'] = 'notification'
|
||||||
messages.append(ndict)
|
messages.append(ndict)
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
'result': messages,
|
'result': messages,
|
||||||
})
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import addEvent
|
from couchpotato.core.event import addEvent
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||||
from couchpotato.core.helpers.request import getParams, jsonified
|
|
||||||
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
|
||||||
import re
|
import re
|
||||||
@@ -22,10 +21,7 @@ class NMJ(Notification):
|
|||||||
addApiView(self.testNotifyName(), self.test)
|
addApiView(self.testNotifyName(), self.test)
|
||||||
addApiView('notify.nmj.auto_config', self.autoConfig)
|
addApiView('notify.nmj.auto_config', self.autoConfig)
|
||||||
|
|
||||||
def autoConfig(self):
|
def autoConfig(self, host = 'localhost', **kwargs):
|
||||||
|
|
||||||
params = getParams()
|
|
||||||
host = params.get('host', 'localhost')
|
|
||||||
|
|
||||||
database = ''
|
database = ''
|
||||||
mount = ''
|
mount = ''
|
||||||
@@ -63,11 +59,11 @@ class NMJ(Notification):
|
|||||||
log.error('Detected a network share on the Popcorn Hour, but could not get the mounting url')
|
log.error('Detected a network share on the Popcorn Hour, but could not get the mounting url')
|
||||||
return self.failed()
|
return self.failed()
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
'database': database,
|
'database': database,
|
||||||
'mount': mount,
|
'mount': mount,
|
||||||
})
|
}
|
||||||
|
|
||||||
def addToLibrary(self, message = None, group = {}):
|
def addToLibrary(self, message = None, group = {}):
|
||||||
if self.isDisabled(): return
|
if self.isDisabled(): return
|
||||||
@@ -113,9 +109,13 @@ class NMJ(Notification):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def failed(self):
|
def failed(self):
|
||||||
return jsonified({'success': False})
|
return {
|
||||||
|
'success': False
|
||||||
|
}
|
||||||
|
|
||||||
def test(self):
|
def test(self):
|
||||||
return jsonified({'success': self.addToLibrary()})
|
return {
|
||||||
|
'success': self.addToLibrary()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
from couchpotato.core.helpers.encoding import toUnicode
|
from couchpotato.core.helpers.encoding import toUnicode
|
||||||
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 flask.helpers import json
|
|
||||||
import base64
|
import base64
|
||||||
|
import json
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
from couchpotato.core.event import addEvent
|
from couchpotato.core.event import addEvent
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||||
from couchpotato.core.helpers.request import jsonified
|
|
||||||
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.notifications.base import Notification
|
from couchpotato.core.notifications.base import Notification
|
||||||
@@ -86,4 +85,6 @@ class Plex(Notification):
|
|||||||
)
|
)
|
||||||
success2 = self.addToLibrary()
|
success2 = self.addToLibrary()
|
||||||
|
|
||||||
return jsonified({'success': success or success2})
|
return {
|
||||||
|
'success': success or success2
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
from couchpotato.core.event import addEvent
|
from couchpotato.core.event import addEvent
|
||||||
from couchpotato.core.helpers.request import jsonified
|
|
||||||
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
|
||||||
import os
|
import os
|
||||||
@@ -33,4 +32,6 @@ class Synoindex(Notification):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def test(self):
|
def test(self):
|
||||||
return jsonified({'success': os.path.isfile(self.index_path)})
|
return {
|
||||||
|
'success': os.path.isfile(self.index_path)
|
||||||
|
}
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||||
from couchpotato.core.helpers.request import jsonified, getParam
|
|
||||||
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.notifications.base import Notification
|
from couchpotato.core.notifications.base import Notification
|
||||||
from flask.helpers import url_for
|
from couchpotato.environment import Env
|
||||||
from pytwitter import Api, parse_qsl
|
from pytwitter import Api, parse_qsl
|
||||||
from werkzeug.utils import redirect
|
|
||||||
import oauth2
|
import oauth2
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
@@ -70,10 +68,9 @@ class Twitter(Notification):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def getAuthorizationUrl(self):
|
def getAuthorizationUrl(self, host = None, **kwargs):
|
||||||
|
|
||||||
referer = getParam('host')
|
callback_url = cleanHost(host) + '%snotify.%s.credentials/' % (Env.get('api_base').lstrip('/'), self.getName().lower())
|
||||||
callback_url = cleanHost(referer) + '%snotify.%s.credentials/' % (url_for('api.index').lstrip('/'), self.getName().lower())
|
|
||||||
|
|
||||||
oauth_consumer = oauth2.Consumer(self.consumer_key, self.consumer_secret)
|
oauth_consumer = oauth2.Consumer(self.consumer_key, self.consumer_secret)
|
||||||
oauth_client = oauth2.Client(oauth_consumer)
|
oauth_client = oauth2.Client(oauth_consumer)
|
||||||
@@ -82,31 +79,29 @@ class Twitter(Notification):
|
|||||||
|
|
||||||
if resp['status'] != '200':
|
if resp['status'] != '200':
|
||||||
log.error('Invalid response from Twitter requesting temp token: %s', resp['status'])
|
log.error('Invalid response from Twitter requesting temp token: %s', resp['status'])
|
||||||
return jsonified({
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
})
|
}
|
||||||
else:
|
else:
|
||||||
self.request_token = dict(parse_qsl(content))
|
self.request_token = dict(parse_qsl(content))
|
||||||
|
|
||||||
auth_url = self.urls['authorize'] + ("?oauth_token=%s" % self.request_token['oauth_token'])
|
auth_url = self.urls['authorize'] + ("?oauth_token=%s" % self.request_token['oauth_token'])
|
||||||
|
|
||||||
log.info('Redirecting to "%s"', auth_url)
|
log.info('Redirecting to "%s"', auth_url)
|
||||||
return jsonified({
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
'url': auth_url,
|
'url': auth_url,
|
||||||
})
|
}
|
||||||
|
|
||||||
def getCredentials(self):
|
def getCredentials(self, oauth_verifier, **kwargs):
|
||||||
|
|
||||||
key = getParam('oauth_verifier')
|
|
||||||
|
|
||||||
token = oauth2.Token(self.request_token['oauth_token'], self.request_token['oauth_token_secret'])
|
token = oauth2.Token(self.request_token['oauth_token'], self.request_token['oauth_token_secret'])
|
||||||
token.set_verifier(key)
|
token.set_verifier(oauth_verifier)
|
||||||
|
|
||||||
oauth_consumer = oauth2.Consumer(key = self.consumer_key, secret = self.consumer_secret)
|
oauth_consumer = oauth2.Consumer(key = self.consumer_key, secret = self.consumer_secret)
|
||||||
oauth_client = oauth2.Client(oauth_consumer, token)
|
oauth_client = oauth2.Client(oauth_consumer, token)
|
||||||
|
|
||||||
resp, content = oauth_client.request(self.urls['access'], method = 'POST', body = 'oauth_verifier=%s' % key)
|
resp, content = oauth_client.request(self.urls['access'], method = 'POST', body = 'oauth_verifier=%s' % oauth_verifier)
|
||||||
access_token = dict(parse_qsl(content))
|
access_token = dict(parse_qsl(content))
|
||||||
|
|
||||||
if resp['status'] != '200':
|
if resp['status'] != '200':
|
||||||
@@ -121,4 +116,4 @@ class Twitter(Notification):
|
|||||||
|
|
||||||
self.request_token = None
|
self.request_token = None
|
||||||
|
|
||||||
return redirect(url_for('web.index') + 'settings/notifications/')
|
return 'redirect', Env.get('web_base') + 'settings/notifications/'
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
from couchpotato.core.helpers.variable import splitString
|
from couchpotato.core.helpers.variable import splitString
|
||||||
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 flask.helpers import json
|
|
||||||
import base64
|
import base64
|
||||||
|
import json
|
||||||
import traceback
|
import traceback
|
||||||
import urllib
|
import urllib
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
from couchpotato import addView
|
|
||||||
from couchpotato.core.event import fireEvent, addEvent
|
from couchpotato.core.event import fireEvent, addEvent
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode, ss, toSafeString
|
from couchpotato.core.helpers.encoding import tryUrlencode, ss, toSafeString
|
||||||
from couchpotato.core.helpers.variable import getExt, md5
|
from couchpotato.core.helpers.variable import getExt, md5
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
from flask.templating import render_template_string
|
|
||||||
from multipartpost import MultipartPostHandler
|
from multipartpost import MultipartPostHandler
|
||||||
|
from tornado import template
|
||||||
|
from tornado.web import StaticFileHandler
|
||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
import cookielib
|
import cookielib
|
||||||
import glob
|
import glob
|
||||||
@@ -44,35 +44,37 @@ class Plugin(object):
|
|||||||
def getName(self):
|
def getName(self):
|
||||||
return self.__class__.__name__
|
return self.__class__.__name__
|
||||||
|
|
||||||
def renderTemplate(self, parent_file, template, **params):
|
def renderTemplate(self, parent_file, templ, **params):
|
||||||
|
|
||||||
template = open(os.path.join(os.path.dirname(parent_file), template), 'r').read()
|
t = template.Template(open(os.path.join(os.path.dirname(parent_file), templ), 'r').read())
|
||||||
return render_template_string(template, **params)
|
return t.generate(**params)
|
||||||
|
|
||||||
def registerStatic(self, plugin_file, add_to_head = True):
|
def registerStatic(self, plugin_file, add_to_head = True):
|
||||||
|
|
||||||
# Register plugin path
|
# Register plugin path
|
||||||
self.plugin_path = os.path.dirname(plugin_file)
|
self.plugin_path = os.path.dirname(plugin_file)
|
||||||
|
static_folder = os.path.join(self.plugin_path, 'static')
|
||||||
|
|
||||||
|
if not os.path.isdir(static_folder):
|
||||||
|
return
|
||||||
|
|
||||||
# Get plugin_name from PluginName
|
# Get plugin_name from PluginName
|
||||||
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', self.__class__.__name__)
|
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', self.__class__.__name__)
|
||||||
class_name = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
|
class_name = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
|
||||||
|
|
||||||
|
# View path
|
||||||
path = 'api/%s/static/%s/' % (Env.setting('api_key'), class_name)
|
path = 'api/%s/static/%s/' % (Env.setting('api_key'), class_name)
|
||||||
addView(path + '<path:filename>', self.showStatic, static = True)
|
|
||||||
|
|
||||||
|
# Add handler to Tornado
|
||||||
|
Env.get('app').add_handlers(".*$", [(Env.get('web_base') + path + '(.*)', StaticFileHandler, {'path': static_folder})])
|
||||||
|
|
||||||
|
# Register for HTML <HEAD>
|
||||||
if add_to_head:
|
if add_to_head:
|
||||||
for f in glob.glob(os.path.join(self.plugin_path, 'static', '*')):
|
for f in glob.glob(os.path.join(self.plugin_path, 'static', '*')):
|
||||||
ext = getExt(f)
|
ext = getExt(f)
|
||||||
if ext in ['js', 'css']:
|
if ext in ['js', 'css']:
|
||||||
fireEvent('register_%s' % ('script' if ext in 'js' else 'style'), path + os.path.basename(f), f)
|
fireEvent('register_%s' % ('script' if ext in 'js' else 'style'), path + os.path.basename(f), f)
|
||||||
|
|
||||||
def showStatic(self, filename):
|
|
||||||
d = os.path.join(self.plugin_path, 'static')
|
|
||||||
|
|
||||||
from flask.helpers import send_from_directory
|
|
||||||
return send_from_directory(d, filename)
|
|
||||||
|
|
||||||
def createFile(self, path, content, binary = False):
|
def createFile(self, path, content, binary = False):
|
||||||
path = ss(path)
|
path = ss(path)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.helpers.request import getParam, jsonified
|
|
||||||
from couchpotato.core.helpers.variable import getUserDir
|
from couchpotato.core.helpers.variable import getUserDir
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
import ctypes
|
import ctypes
|
||||||
@@ -63,16 +62,15 @@ class FileBrowser(Plugin):
|
|||||||
|
|
||||||
return driveletters
|
return driveletters
|
||||||
|
|
||||||
def view(self):
|
def view(self, path = '/', show_hidden = True, **kwargs):
|
||||||
|
|
||||||
path = getParam('path', '/')
|
|
||||||
home = getUserDir()
|
home = getUserDir()
|
||||||
|
|
||||||
if not path:
|
if not path:
|
||||||
path = home
|
path = home
|
||||||
|
|
||||||
try:
|
try:
|
||||||
dirs = self.getDirectories(path = path, show_hidden = getParam('show_hidden', True))
|
dirs = self.getDirectories(path = path, show_hidden = show_hidden)
|
||||||
except:
|
except:
|
||||||
dirs = []
|
dirs = []
|
||||||
|
|
||||||
@@ -82,14 +80,14 @@ class FileBrowser(Plugin):
|
|||||||
elif parent != '/' and parent[-2:] != ':\\':
|
elif parent != '/' and parent[-2:] != ':\\':
|
||||||
parent += os.path.sep
|
parent += os.path.sep
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'is_root': path == '/',
|
'is_root': path == '/',
|
||||||
'empty': len(dirs) == 0,
|
'empty': len(dirs) == 0,
|
||||||
'parent': parent,
|
'parent': parent,
|
||||||
'home': home + os.path.sep,
|
'home': home + os.path.sep,
|
||||||
'platform': os.name,
|
'platform': os.name,
|
||||||
'dirs': dirs,
|
'dirs': dirs,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
def is_hidden(self, filepath):
|
def is_hidden(self, filepath):
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
from couchpotato import get_session
|
from couchpotato import get_session
|
||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import fireEvent
|
from couchpotato.core.event import fireEvent
|
||||||
from couchpotato.core.helpers.request import jsonified, getParams
|
|
||||||
from couchpotato.core.helpers.variable import splitString, tryInt
|
from couchpotato.core.helpers.variable import splitString, tryInt
|
||||||
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
|
from couchpotato.core.settings.model import Movie
|
||||||
from sqlalchemy.orm import joinedload_all
|
from sqlalchemy.orm import joinedload_all
|
||||||
import random
|
import random as rndm
|
||||||
import time
|
import time
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
@@ -43,14 +42,13 @@ class Dashboard(Plugin):
|
|||||||
|
|
||||||
suggestions = fireEvent('movie.suggest', movies = identifiers, single = True)
|
suggestions = fireEvent('movie.suggest', movies = identifiers, single = True)
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'result': True,
|
'result': True,
|
||||||
'suggestions': suggestions
|
'suggestions': suggestions
|
||||||
})
|
}
|
||||||
|
|
||||||
def getSoonView(self):
|
def getSoonView(self, limit_offset = None, random = False, late = False, **kwargs):
|
||||||
|
|
||||||
params = getParams()
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
now = time.time()
|
now = time.time()
|
||||||
|
|
||||||
@@ -85,7 +83,6 @@ class Dashboard(Plugin):
|
|||||||
.options(joinedload_all('files'))
|
.options(joinedload_all('files'))
|
||||||
|
|
||||||
# Add limit
|
# Add limit
|
||||||
limit_offset = params.get('limit_offset')
|
|
||||||
limit = 12
|
limit = 12
|
||||||
if limit_offset:
|
if limit_offset:
|
||||||
splt = splitString(limit_offset) if isinstance(limit_offset, (str, unicode)) else limit_offset
|
splt = splitString(limit_offset) if isinstance(limit_offset, (str, unicode)) else limit_offset
|
||||||
@@ -93,8 +90,8 @@ class Dashboard(Plugin):
|
|||||||
|
|
||||||
all_movies = q.all()
|
all_movies = q.all()
|
||||||
|
|
||||||
if params.get('random', False):
|
if random:
|
||||||
random.shuffle(all_movies)
|
rndm.shuffle(all_movies)
|
||||||
|
|
||||||
movies = []
|
movies = []
|
||||||
for movie in all_movies:
|
for movie in all_movies:
|
||||||
@@ -126,18 +123,18 @@ class Dashboard(Plugin):
|
|||||||
})
|
})
|
||||||
|
|
||||||
# Don't list older movies
|
# Don't list older movies
|
||||||
if ((not params.get('late') and (not eta.get('dvd') or (eta.get('dvd') and eta.get('dvd') > (now - 2419200)))) or \
|
if ((not late and (not eta.get('dvd') or (eta.get('dvd') and eta.get('dvd') > (now - 2419200)))) or \
|
||||||
(params.get('late') and eta.get('dvd') and eta.get('dvd') < (now - 2419200))):
|
(late and eta.get('dvd') and eta.get('dvd') < (now - 2419200))):
|
||||||
movies.append(temp)
|
movies.append(temp)
|
||||||
|
|
||||||
if len(movies) >= limit:
|
if len(movies) >= limit:
|
||||||
break
|
break
|
||||||
|
|
||||||
db.expire_all()
|
db.expire_all()
|
||||||
return jsonified({
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
'empty': len(movies) == 0,
|
'empty': len(movies) == 0,
|
||||||
'movies': movies,
|
'movies': movies,
|
||||||
})
|
}
|
||||||
|
|
||||||
getLateView = getSoonView
|
getLateView = getSoonView
|
||||||
@@ -2,15 +2,13 @@ from couchpotato import get_session
|
|||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import addEvent
|
from couchpotato.core.event import addEvent
|
||||||
from couchpotato.core.helpers.encoding import toUnicode
|
from couchpotato.core.helpers.encoding import toUnicode
|
||||||
from couchpotato.core.helpers.request import jsonified
|
|
||||||
from couchpotato.core.helpers.variable import md5, getExt
|
from couchpotato.core.helpers.variable import md5, getExt
|
||||||
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.plugins.scanner.main import Scanner
|
from couchpotato.core.plugins.scanner.main import Scanner
|
||||||
from couchpotato.core.settings.model import FileType, File
|
from couchpotato.core.settings.model import FileType, File
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
from flask.helpers import send_file
|
from tornado.web import StaticFileHandler
|
||||||
from werkzeug.exceptions import NotFound
|
|
||||||
import os.path
|
import os.path
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
@@ -25,7 +23,7 @@ class FileManager(Plugin):
|
|||||||
addEvent('file.download', self.download)
|
addEvent('file.download', self.download)
|
||||||
addEvent('file.types', self.getTypes)
|
addEvent('file.types', self.getTypes)
|
||||||
|
|
||||||
addApiView('file.cache/<path:filename>', self.showCacheFile, static = True, docs = {
|
addApiView('file.cache/(.*)', self.showCacheFile, static = True, docs = {
|
||||||
'desc': 'Return a file from the cp_data/cache directory',
|
'desc': 'Return a file from the cp_data/cache directory',
|
||||||
'params': {
|
'params': {
|
||||||
'filename': {'desc': 'path/filename of the wanted file'}
|
'filename': {'desc': 'path/filename of the wanted file'}
|
||||||
@@ -81,15 +79,9 @@ class FileManager(Plugin):
|
|||||||
except:
|
except:
|
||||||
log.error('Failed removing unused file: %s', traceback.format_exc())
|
log.error('Failed removing unused file: %s', traceback.format_exc())
|
||||||
|
|
||||||
def showCacheFile(self, filename = ''):
|
def showCacheFile(self, route):
|
||||||
|
Env.get('app').add_handlers(".*$", [('%s%s' % (Env.get('api_base'), route), StaticFileHandler, {'path': Env.get('cache_dir')})])
|
||||||
|
|
||||||
file_path = os.path.join(Env.get('cache_dir'), os.path.basename(filename))
|
|
||||||
|
|
||||||
if not os.path.isfile(file_path):
|
|
||||||
log.error('File "%s" not found', file_path)
|
|
||||||
raise NotFound()
|
|
||||||
|
|
||||||
return send_file(file_path, conditional = True)
|
|
||||||
|
|
||||||
def download(self, url = '', dest = None, overwrite = False, urlopen_kwargs = {}):
|
def download(self, url = '', dest = None, overwrite = False, urlopen_kwargs = {}):
|
||||||
|
|
||||||
@@ -160,6 +152,6 @@ class FileManager(Plugin):
|
|||||||
|
|
||||||
def getTypesView(self):
|
def getTypesView(self):
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'types': self.getTypes()
|
'types': self.getTypes()
|
||||||
})
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.helpers.encoding import toUnicode
|
from couchpotato.core.helpers.encoding import toUnicode
|
||||||
from couchpotato.core.helpers.request import jsonified, getParam, getParams
|
|
||||||
from couchpotato.core.helpers.variable import tryInt
|
from couchpotato.core.helpers.variable import tryInt
|
||||||
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
|
||||||
@@ -47,9 +46,9 @@ class Logging(Plugin):
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
def get(self):
|
def get(self, nr = 0, **kwargs):
|
||||||
|
|
||||||
nr = int(getParam('nr', 0))
|
nr = tryInt(nr)
|
||||||
current_path = None
|
current_path = None
|
||||||
|
|
||||||
total = 1
|
total = 1
|
||||||
@@ -71,16 +70,15 @@ class Logging(Plugin):
|
|||||||
f = open(current_path, 'r')
|
f = open(current_path, 'r')
|
||||||
log = f.read()
|
log = f.read()
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
'log': toUnicode(log),
|
'log': toUnicode(log),
|
||||||
'total': total,
|
'total': total,
|
||||||
})
|
}
|
||||||
|
|
||||||
def partial(self):
|
def partial(self, type = 'all', lines = 30, **kwargs):
|
||||||
|
|
||||||
log_type = getParam('type', 'all')
|
total_lines = tryInt(lines)
|
||||||
total_lines = tryInt(getParam('lines', 30))
|
|
||||||
|
|
||||||
log_lines = []
|
log_lines = []
|
||||||
|
|
||||||
@@ -100,7 +98,7 @@ class Logging(Plugin):
|
|||||||
brk = False
|
brk = False
|
||||||
for line in reversed_lines:
|
for line in reversed_lines:
|
||||||
|
|
||||||
if log_type == 'all' or '%s ' % log_type.upper() in line:
|
if type == 'all' or '%s ' % type.upper() in line:
|
||||||
log_lines.append(line)
|
log_lines.append(line)
|
||||||
|
|
||||||
if len(log_lines) >= total_lines:
|
if len(log_lines) >= total_lines:
|
||||||
@@ -111,10 +109,10 @@ class Logging(Plugin):
|
|||||||
break
|
break
|
||||||
|
|
||||||
log_lines.reverse()
|
log_lines.reverse()
|
||||||
return jsonified({
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
'log': '[0m\n'.join(log_lines),
|
'log': '[0m\n'.join(log_lines),
|
||||||
})
|
}
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
|
|
||||||
@@ -135,24 +133,21 @@ class Logging(Plugin):
|
|||||||
except:
|
except:
|
||||||
log.error('Couldn\'t delete file "%s": %s', (path, traceback.format_exc()))
|
log.error('Couldn\'t delete file "%s": %s', (path, traceback.format_exc()))
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'success': True
|
'success': True
|
||||||
})
|
}
|
||||||
|
|
||||||
def log(self):
|
def log(self, type = 'error', **kwargs):
|
||||||
|
|
||||||
params = getParams()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
log_message = 'API log: %s' % params
|
log_message = 'API log: %s' % kwargs
|
||||||
try:
|
try:
|
||||||
getattr(log, params.get('type', 'error'))(log_message)
|
getattr(log, type)(log_message)
|
||||||
except:
|
except:
|
||||||
log.error(log_message)
|
log.error(log_message)
|
||||||
except:
|
except:
|
||||||
log.error('Couldn\'t log via API: %s', params)
|
log.error('Couldn\'t log via API: %s', kwargs)
|
||||||
|
|
||||||
|
return {
|
||||||
return jsonified({
|
|
||||||
'success': True
|
'success': True
|
||||||
})
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import fireEvent, addEvent, fireEventAsync
|
from couchpotato.core.event import fireEvent, addEvent, fireEventAsync
|
||||||
from couchpotato.core.helpers.encoding import ss
|
from couchpotato.core.helpers.encoding import ss
|
||||||
from couchpotato.core.helpers.request import jsonified, getParam
|
|
||||||
from couchpotato.core.helpers.variable import splitString, getTitle
|
from couchpotato.core.helpers.variable import splitString, getTitle
|
||||||
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
|
||||||
@@ -51,18 +50,17 @@ class Manage(Plugin):
|
|||||||
addEvent('app.load', updateLibrary)
|
addEvent('app.load', updateLibrary)
|
||||||
|
|
||||||
def getProgress(self):
|
def getProgress(self):
|
||||||
return jsonified({
|
return {
|
||||||
'progress': self.in_progress
|
'progress': self.in_progress
|
||||||
})
|
}
|
||||||
|
|
||||||
def updateLibraryView(self):
|
def updateLibraryView(self, full = 1, **kwargs):
|
||||||
|
|
||||||
full = getParam('full', default = 1)
|
|
||||||
fireEventAsync('manage.update', full = True if full == '1' else False)
|
fireEventAsync('manage.update', full = True if full == '1' else False)
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'success': True
|
'success': True
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
def updateLibrary(self, full = True):
|
def updateLibrary(self, full = True):
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from couchpotato import get_session
|
|||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import fireEvent, fireEventAsync, addEvent
|
from couchpotato.core.event import fireEvent, fireEventAsync, addEvent
|
||||||
from couchpotato.core.helpers.encoding import toUnicode, simplifyString
|
from couchpotato.core.helpers.encoding import toUnicode, simplifyString
|
||||||
from couchpotato.core.helpers.request import getParams, jsonified, getParam
|
|
||||||
from couchpotato.core.helpers.variable import getImdb, splitString
|
from couchpotato.core.helpers.variable import getImdb, splitString
|
||||||
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
|
||||||
@@ -125,15 +124,14 @@ class MoviePlugin(Plugin):
|
|||||||
|
|
||||||
db.expire_all()
|
db.expire_all()
|
||||||
|
|
||||||
def getView(self):
|
def getView(self, id = None):
|
||||||
|
|
||||||
movie_id = getParam('id')
|
movie = self.get(id) if id else None
|
||||||
movie = self.get(movie_id) if movie_id else None
|
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'success': movie is not None,
|
'success': movie is not None,
|
||||||
'movie': movie,
|
'movie': movie,
|
||||||
})
|
}
|
||||||
|
|
||||||
def get(self, movie_id):
|
def get(self, movie_id):
|
||||||
|
|
||||||
@@ -263,15 +261,14 @@ class MoviePlugin(Plugin):
|
|||||||
db.expire_all()
|
db.expire_all()
|
||||||
return ''.join(sorted(chars, key = str.lower))
|
return ''.join(sorted(chars, key = str.lower))
|
||||||
|
|
||||||
def listView(self):
|
def listView(self, **kwargs):
|
||||||
|
|
||||||
params = getParams()
|
status = splitString(kwargs.get('status', None))
|
||||||
status = splitString(params.get('status', None))
|
release_status = splitString(kwargs.get('release_status', None))
|
||||||
release_status = splitString(params.get('release_status', None))
|
limit_offset = kwargs.get('limit_offset', None)
|
||||||
limit_offset = params.get('limit_offset', None)
|
starts_with = kwargs.get('starts_with', None)
|
||||||
starts_with = params.get('starts_with', None)
|
search = kwargs.get('search', None)
|
||||||
search = params.get('search', None)
|
order = kwargs.get('order', None)
|
||||||
order = params.get('order', None)
|
|
||||||
|
|
||||||
total_movies, movies = self.list(
|
total_movies, movies = self.list(
|
||||||
status = status,
|
status = status,
|
||||||
@@ -282,32 +279,31 @@ class MoviePlugin(Plugin):
|
|||||||
order = order
|
order = order
|
||||||
)
|
)
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
'empty': len(movies) == 0,
|
'empty': len(movies) == 0,
|
||||||
'total': total_movies,
|
'total': total_movies,
|
||||||
'movies': movies,
|
'movies': movies,
|
||||||
})
|
}
|
||||||
|
|
||||||
def charView(self):
|
def charView(self, **kwargs):
|
||||||
|
|
||||||
params = getParams()
|
status = splitString(kwargs.get('status', None))
|
||||||
status = splitString(params.get('status', None))
|
release_status = splitString(kwargs.get('release_status', None))
|
||||||
release_status = splitString(params.get('release_status', None))
|
|
||||||
chars = self.availableChars(status, release_status)
|
chars = self.availableChars(status, release_status)
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
'empty': len(chars) == 0,
|
'empty': len(chars) == 0,
|
||||||
'chars': chars,
|
'chars': chars,
|
||||||
})
|
}
|
||||||
|
|
||||||
def refresh(self):
|
def refresh(self, id = ''):
|
||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
|
|
||||||
for id in splitString(getParam('id')):
|
for x in splitString(id):
|
||||||
movie = db.query(Movie).filter_by(id = id).first()
|
movie = db.query(Movie).filter_by(id = x).first()
|
||||||
|
|
||||||
if movie:
|
if movie:
|
||||||
|
|
||||||
@@ -316,17 +312,16 @@ class MoviePlugin(Plugin):
|
|||||||
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
|
||||||
|
|
||||||
fireEvent('notify.frontend', type = 'movie.busy.%s' % id, data = True)
|
fireEvent('notify.frontend', type = 'movie.busy.%s' % x, data = True)
|
||||||
fireEventAsync('library.update', identifier = movie.library.identifier, default_title = default_title, force = True, on_complete = self.createOnComplete(id))
|
fireEventAsync('library.update', identifier = movie.library.identifier, default_title = default_title, force = True, on_complete = self.createOnComplete(x))
|
||||||
|
|
||||||
db.expire_all()
|
db.expire_all()
|
||||||
return jsonified({
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
})
|
}
|
||||||
|
|
||||||
def search(self):
|
def search(self, q = ''):
|
||||||
|
|
||||||
q = getParam('q')
|
|
||||||
cache_key = u'%s/%s' % (__name__, simplifyString(q))
|
cache_key = u'%s/%s' % (__name__, simplifyString(q))
|
||||||
movies = Env.get('cache').get(cache_key)
|
movies = Env.get('cache').get(cache_key)
|
||||||
|
|
||||||
@@ -338,11 +333,11 @@ class MoviePlugin(Plugin):
|
|||||||
movies = fireEvent('movie.search', q = q, merge = True)
|
movies = fireEvent('movie.search', q = q, merge = True)
|
||||||
Env.get('cache').set(cache_key, movies)
|
Env.get('cache').set(cache_key, movies)
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
'empty': len(movies) == 0 if movies else 0,
|
'empty': len(movies) == 0 if movies else 0,
|
||||||
'movies': movies,
|
'movies': movies,
|
||||||
})
|
}
|
||||||
|
|
||||||
def add(self, params = {}, force_readd = True, search_after = True, update_library = False, status_id = None):
|
def add(self, params = {}, force_readd = True, search_after = True, update_library = False, status_id = None):
|
||||||
|
|
||||||
@@ -432,33 +427,30 @@ class MoviePlugin(Plugin):
|
|||||||
return movie_dict
|
return movie_dict
|
||||||
|
|
||||||
|
|
||||||
def addView(self):
|
def addView(self, **kwargs):
|
||||||
|
|
||||||
params = getParams()
|
movie_dict = self.add(params = kwargs)
|
||||||
|
|
||||||
movie_dict = self.add(params)
|
return {
|
||||||
|
|
||||||
return jsonified({
|
|
||||||
'success': True,
|
'success': True,
|
||||||
'added': True if movie_dict else False,
|
'added': True if movie_dict else False,
|
||||||
'movie': movie_dict,
|
'movie': movie_dict,
|
||||||
})
|
}
|
||||||
|
|
||||||
def edit(self):
|
def edit(self, id = '', **kwargs):
|
||||||
|
|
||||||
params = getParams()
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
|
|
||||||
available_status = fireEvent('status.get', 'available', single = True)
|
available_status = fireEvent('status.get', 'available', single = True)
|
||||||
|
|
||||||
ids = splitString(params.get('id'))
|
ids = splitString(id)
|
||||||
for movie_id in ids:
|
for movie_id in ids:
|
||||||
|
|
||||||
m = db.query(Movie).filter_by(id = movie_id).first()
|
m = db.query(Movie).filter_by(id = movie_id).first()
|
||||||
if not m:
|
if not m:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
m.profile_id = params.get('profile_id')
|
m.profile_id = kwargs.get('profile_id')
|
||||||
|
|
||||||
# Remove releases
|
# Remove releases
|
||||||
for rel in m.releases:
|
for rel in m.releases:
|
||||||
@@ -467,9 +459,9 @@ class MoviePlugin(Plugin):
|
|||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
# Default title
|
# Default title
|
||||||
if params.get('default_title'):
|
if kwargs.get('default_title'):
|
||||||
for title in m.library.titles:
|
for title in m.library.titles:
|
||||||
title.default = toUnicode(params.get('default_title', '')).lower() == toUnicode(title.title).lower()
|
title.default = toUnicode(kwargs.get('default_title', '')).lower() == toUnicode(title.title).lower()
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
@@ -479,21 +471,19 @@ class MoviePlugin(Plugin):
|
|||||||
fireEventAsync('searcher.single', movie_dict, on_complete = self.createNotifyFront(movie_id))
|
fireEventAsync('searcher.single', movie_dict, on_complete = self.createNotifyFront(movie_id))
|
||||||
|
|
||||||
db.expire_all()
|
db.expire_all()
|
||||||
return jsonified({
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
})
|
}
|
||||||
|
|
||||||
def deleteView(self):
|
def deleteView(self, id = '', **kwargs):
|
||||||
|
|
||||||
params = getParams()
|
ids = splitString(id)
|
||||||
|
|
||||||
ids = splitString(params.get('id'))
|
|
||||||
for movie_id in ids:
|
for movie_id in ids:
|
||||||
self.delete(movie_id, delete_from = params.get('delete_from', 'all'))
|
self.delete(movie_id, delete_from = kwargs.get('delete_from', 'all'))
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
})
|
}
|
||||||
|
|
||||||
def delete(self, movie_id, delete_from = None):
|
def delete(self, movie_id, delete_from = None):
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from couchpotato import get_session
|
|||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import addEvent, fireEvent
|
from couchpotato.core.event import addEvent, fireEvent
|
||||||
from couchpotato.core.helpers.encoding import toUnicode
|
from couchpotato.core.helpers.encoding import toUnicode
|
||||||
from couchpotato.core.helpers.request import jsonified, getParams, getParam
|
|
||||||
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 Profile, ProfileType, Movie
|
from couchpotato.core.settings.model import Profile, ProfileType, Movie
|
||||||
@@ -48,10 +47,10 @@ class ProfilePlugin(Plugin):
|
|||||||
|
|
||||||
def allView(self):
|
def allView(self):
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
'list': self.all()
|
'list': self.all()
|
||||||
})
|
}
|
||||||
|
|
||||||
def all(self):
|
def all(self):
|
||||||
|
|
||||||
@@ -65,30 +64,28 @@ class ProfilePlugin(Plugin):
|
|||||||
db.expire_all()
|
db.expire_all()
|
||||||
return temp
|
return temp
|
||||||
|
|
||||||
def save(self):
|
def save(self, **kwargs):
|
||||||
|
|
||||||
params = getParams()
|
|
||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
|
|
||||||
p = db.query(Profile).filter_by(id = params.get('id')).first()
|
p = db.query(Profile).filter_by(id = kwargs.get('id')).first()
|
||||||
if not p:
|
if not p:
|
||||||
p = Profile()
|
p = Profile()
|
||||||
db.add(p)
|
db.add(p)
|
||||||
|
|
||||||
p.label = toUnicode(params.get('label'))
|
p.label = toUnicode(kwargs.get('label'))
|
||||||
p.order = params.get('order', p.order if p.order else 0)
|
p.order = kwargs.get('order', p.order if p.order else 0)
|
||||||
p.core = params.get('core', False)
|
p.core = kwargs.get('core', False)
|
||||||
|
|
||||||
#delete old types
|
#delete old types
|
||||||
[db.delete(t) for t in p.types]
|
[db.delete(t) for t in p.types]
|
||||||
|
|
||||||
order = 0
|
order = 0
|
||||||
for type in params.get('types', []):
|
for type in kwargs.get('types', []):
|
||||||
t = ProfileType(
|
t = ProfileType(
|
||||||
order = order,
|
order = order,
|
||||||
finish = type.get('finish') if order > 0 else 1,
|
finish = type.get('finish') if order > 0 else 1,
|
||||||
wait_for = params.get('wait_for'),
|
wait_for = kwargs.get('wait_for'),
|
||||||
quality_id = type.get('quality_id')
|
quality_id = type.get('quality_id')
|
||||||
)
|
)
|
||||||
p.types.append(t)
|
p.types.append(t)
|
||||||
@@ -99,10 +96,10 @@ class ProfilePlugin(Plugin):
|
|||||||
|
|
||||||
profile_dict = p.to_dict(self.to_dict)
|
profile_dict = p.to_dict(self.to_dict)
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
'profile': profile_dict
|
'profile': profile_dict
|
||||||
})
|
}
|
||||||
|
|
||||||
def default(self):
|
def default(self):
|
||||||
|
|
||||||
@@ -113,28 +110,25 @@ class ProfilePlugin(Plugin):
|
|||||||
db.expire_all()
|
db.expire_all()
|
||||||
return default_dict
|
return default_dict
|
||||||
|
|
||||||
def saveOrder(self):
|
def saveOrder(self, **kwargs):
|
||||||
|
|
||||||
params = getParams()
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
|
|
||||||
order = 0
|
order = 0
|
||||||
for profile in params.get('ids', []):
|
for profile in kwargs.get('ids', []):
|
||||||
p = db.query(Profile).filter_by(id = profile).first()
|
p = db.query(Profile).filter_by(id = profile).first()
|
||||||
p.hide = params.get('hidden')[order]
|
p.hide = kwargs.get('hidden')[order]
|
||||||
p.order = order
|
p.order = order
|
||||||
|
|
||||||
order += 1
|
order += 1
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'success': True
|
'success': True
|
||||||
})
|
}
|
||||||
|
|
||||||
def delete(self):
|
def delete(self, id = None):
|
||||||
|
|
||||||
id = getParam('id')
|
|
||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
|
|
||||||
@@ -154,10 +148,10 @@ class ProfilePlugin(Plugin):
|
|||||||
message = log.error('Failed deleting Profile: %s', e)
|
message = log.error('Failed deleting Profile: %s', e)
|
||||||
|
|
||||||
db.expire_all()
|
db.expire_all()
|
||||||
return jsonified({
|
return {
|
||||||
'success': success,
|
'success': success,
|
||||||
'message': message
|
'message': message
|
||||||
})
|
}
|
||||||
|
|
||||||
def fill(self):
|
def fill(self):
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from couchpotato import get_session
|
|||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import addEvent
|
from couchpotato.core.event import addEvent
|
||||||
from couchpotato.core.helpers.encoding import toUnicode
|
from couchpotato.core.helpers.encoding import toUnicode
|
||||||
from couchpotato.core.helpers.request import jsonified, getParams
|
|
||||||
from couchpotato.core.helpers.variable import mergeDicts, md5, getExt
|
from couchpotato.core.helpers.variable import mergeDicts, md5, getExt
|
||||||
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
|
||||||
@@ -53,10 +52,10 @@ class QualityPlugin(Plugin):
|
|||||||
|
|
||||||
def allView(self):
|
def allView(self):
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
'list': self.all()
|
'list': self.all()
|
||||||
})
|
}
|
||||||
|
|
||||||
def all(self):
|
def all(self):
|
||||||
|
|
||||||
@@ -88,20 +87,18 @@ class QualityPlugin(Plugin):
|
|||||||
if identifier == q.get('identifier'):
|
if identifier == q.get('identifier'):
|
||||||
return q
|
return q
|
||||||
|
|
||||||
def saveSize(self):
|
def saveSize(self, **kwargs):
|
||||||
|
|
||||||
params = getParams()
|
|
||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
quality = db.query(Quality).filter_by(identifier = params.get('identifier')).first()
|
quality = db.query(Quality).filter_by(identifier = kwargs.get('identifier')).first()
|
||||||
|
|
||||||
if quality:
|
if quality:
|
||||||
setattr(quality, params.get('value_type'), params.get('value'))
|
setattr(quality, kwargs.get('value_type'), kwargs.get('value'))
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'success': True
|
'success': True
|
||||||
})
|
}
|
||||||
|
|
||||||
def fill(self):
|
def fill(self):
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from couchpotato import get_session
|
|||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import fireEvent, addEvent
|
from couchpotato.core.event import fireEvent, addEvent
|
||||||
from couchpotato.core.helpers.encoding import ss
|
from couchpotato.core.helpers.encoding import ss
|
||||||
from couchpotato.core.helpers.request import getParam, 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.core.plugins.scanner.main import Scanner
|
from couchpotato.core.plugins.scanner.main import Scanner
|
||||||
@@ -108,13 +107,11 @@ class Release(Plugin):
|
|||||||
# Check database and update/insert if necessary
|
# Check database and update/insert if necessary
|
||||||
return fireEvent('file.add', path = filepath, part = fireEvent('scanner.partnumber', file, single = True), type_tuple = Scanner.file_types.get(type), properties = properties, single = True)
|
return fireEvent('file.add', path = filepath, part = fireEvent('scanner.partnumber', file, single = True), type_tuple = Scanner.file_types.get(type), properties = properties, single = True)
|
||||||
|
|
||||||
def deleteView(self):
|
def deleteView(self, id = None, **kwargs):
|
||||||
|
|
||||||
release_id = getParam('id')
|
return {
|
||||||
|
'success': self.delete(id)
|
||||||
return jsonified({
|
}
|
||||||
'success': self.delete(release_id)
|
|
||||||
})
|
|
||||||
|
|
||||||
def delete(self, id):
|
def delete(self, id):
|
||||||
|
|
||||||
@@ -146,10 +143,9 @@ class Release(Plugin):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def ignore(self):
|
def ignore(self, id = None, **kwargs):
|
||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
id = getParam('id')
|
|
||||||
|
|
||||||
rel = db.query(Relea).filter_by(id = id).first()
|
rel = db.query(Relea).filter_by(id = id).first()
|
||||||
if rel:
|
if rel:
|
||||||
@@ -157,14 +153,13 @@ 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()
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'success': True
|
'success': True
|
||||||
})
|
}
|
||||||
|
|
||||||
def download(self):
|
def download(self, id = None, **kwargs):
|
||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
id = getParam('id')
|
|
||||||
|
|
||||||
snatched_status, done_status = fireEvent('status.get', ['snatched', 'done'], single = True)
|
snatched_status, done_status = fireEvent('status.get', ['snatched', 'done'], single = True)
|
||||||
|
|
||||||
@@ -199,12 +194,12 @@ class Release(Plugin):
|
|||||||
|
|
||||||
fireEvent('notify.frontend', type = 'release.download', data = True, message = 'Successfully snatched "%s"' % item['name'])
|
fireEvent('notify.frontend', type = 'release.download', data = True, message = 'Successfully snatched "%s"' % item['name'])
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'success': success
|
'success': success
|
||||||
})
|
}
|
||||||
else:
|
else:
|
||||||
log.error('Couldn\'t find release with id: %s', id)
|
log.error('Couldn\'t find release with id: %s', id)
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'success': False
|
'success': False
|
||||||
})
|
}
|
||||||
@@ -2,7 +2,6 @@ from couchpotato import get_session
|
|||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
|
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
|
||||||
from couchpotato.core.helpers.encoding import toUnicode, ss
|
from couchpotato.core.helpers.encoding import toUnicode, ss
|
||||||
from couchpotato.core.helpers.request import getParams, jsonified
|
|
||||||
from couchpotato.core.helpers.variable import getExt, mergeDicts, getTitle, \
|
from couchpotato.core.helpers.variable import getExt, mergeDicts, getTitle, \
|
||||||
getImdb, link, symlink, tryInt
|
getImdb, link, symlink, tryInt
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
@@ -59,13 +58,12 @@ class Renamer(Plugin):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def scanView(self):
|
def scanView(self, **kwargs):
|
||||||
|
|
||||||
params = getParams()
|
async = tryInt(kwargs.get('async', None))
|
||||||
async = tryInt(params.get('async', None))
|
movie_folder = kwargs.get('movie_folder', None)
|
||||||
movie_folder = params.get('movie_folder', None)
|
downloader = kwargs.get('downloader', None)
|
||||||
downloader = params.get('downloader', None)
|
download_id = kwargs.get('download_id', None)
|
||||||
download_id = params.get('download_id', None)
|
|
||||||
|
|
||||||
fire_handle = fireEvent if not async else fireEventAsync
|
fire_handle = fireEvent if not async else fireEventAsync
|
||||||
|
|
||||||
@@ -74,9 +72,9 @@ class Renamer(Plugin):
|
|||||||
download_info = {'id': download_id, 'downloader': downloader} if download_id else None
|
download_info = {'id': download_id, 'downloader': downloader} if download_id else None
|
||||||
)
|
)
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'success': True
|
'success': True
|
||||||
})
|
}
|
||||||
|
|
||||||
def scan(self, movie_folder = None, download_info = None):
|
def scan(self, movie_folder = None, download_info = None):
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from couchpotato import get_session
|
|||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
|
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
|
||||||
from couchpotato.core.helpers.encoding import simplifyString, toUnicode
|
from couchpotato.core.helpers.encoding import simplifyString, toUnicode
|
||||||
from couchpotato.core.helpers.request import jsonified, getParam
|
|
||||||
from couchpotato.core.helpers.variable import md5, getTitle, splitString, \
|
from couchpotato.core.helpers.variable import md5, getTitle, splitString, \
|
||||||
possibleTitles
|
possibleTitles
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
@@ -67,15 +66,15 @@ class Searcher(Plugin):
|
|||||||
else:
|
else:
|
||||||
fireEvent('notify.frontend', type = 'searcher.already_started', data = True, message = 'Full search already in progress')
|
fireEvent('notify.frontend', type = 'searcher.already_started', data = True, message = 'Full search already in progress')
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'success': not in_progress
|
'success': not in_progress
|
||||||
})
|
}
|
||||||
|
|
||||||
def getProgress(self):
|
def getProgress(self):
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'progress': self.in_progress
|
'progress': self.in_progress
|
||||||
})
|
}
|
||||||
|
|
||||||
def allMovies(self):
|
def allMovies(self):
|
||||||
|
|
||||||
@@ -586,13 +585,13 @@ class Searcher(Plugin):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def tryNextReleaseView(self):
|
def tryNextReleaseView(self, id = None, **kwargs):
|
||||||
|
|
||||||
trynext = self.tryNextRelease(getParam('id'))
|
trynext = self.tryNextRelease(id)
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'success': trynext
|
'success': trynext
|
||||||
})
|
}
|
||||||
|
|
||||||
def tryNextRelease(self, movie_id, manual = False):
|
def tryNextRelease(self, movie_id, manual = False):
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from couchpotato import get_session
|
|||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import addEvent
|
from couchpotato.core.event import addEvent
|
||||||
from couchpotato.core.helpers.encoding import toUnicode
|
from couchpotato.core.helpers.encoding import toUnicode
|
||||||
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.core.settings.model import Status
|
from couchpotato.core.settings.model import Status
|
||||||
@@ -44,10 +43,10 @@ class StatusPlugin(Plugin):
|
|||||||
|
|
||||||
def list(self):
|
def list(self):
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
'list': self.all()
|
'list': self.all()
|
||||||
})
|
}
|
||||||
|
|
||||||
def getById(self, id):
|
def getById(self, id):
|
||||||
db = get_session()
|
db = get_session()
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import fireEvent
|
from couchpotato.core.event import fireEvent
|
||||||
from couchpotato.core.helpers.request import jsonified, getParam
|
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
|
|
||||||
class Suggestion(Plugin):
|
class Suggestion(Plugin):
|
||||||
@@ -9,14 +8,13 @@ class Suggestion(Plugin):
|
|||||||
|
|
||||||
addApiView('suggestion.view', self.getView)
|
addApiView('suggestion.view', self.getView)
|
||||||
|
|
||||||
def getView(self):
|
def getView(self, limit_offset = None, **kwargs):
|
||||||
|
|
||||||
limit_offset = getParam('limit_offset', None)
|
|
||||||
total_movies, movies = fireEvent('movie.list', status = 'suggest', limit_offset = limit_offset, single = True)
|
total_movies, movies = fireEvent('movie.list', status = 'suggest', limit_offset = limit_offset, single = True)
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
'empty': len(movies) == 0,
|
'empty': len(movies) == 0,
|
||||||
'total': total_movies,
|
'total': total_movies,
|
||||||
'movies': movies,
|
'movies': movies,
|
||||||
})
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
var includes = {{includes|tojson}};
|
{% autoescape None %}
|
||||||
var excludes = {{excludes|tojson}};
|
|
||||||
|
var includes = {{ json_encode(includes) }};
|
||||||
|
var excludes = {{ json_encode(excludes) }};
|
||||||
|
|
||||||
var specialChars = '\\{}+.():-|^$';
|
var specialChars = '\\{}+.():-|^$';
|
||||||
var makeRegex = function(pattern) {
|
var makeRegex = function(pattern) {
|
||||||
@@ -20,6 +22,8 @@ var makeRegex = function(pattern) {
|
|||||||
|
|
||||||
var isCorrectUrl = function() {
|
var isCorrectUrl = function() {
|
||||||
for(i in includes) {
|
for(i in includes) {
|
||||||
|
if(!includes.hasOwnProperty(i)) continue;
|
||||||
|
|
||||||
var reg = includes[i]
|
var reg = includes[i]
|
||||||
if (makeRegex(reg).test(document.location.href))
|
if (makeRegex(reg).test(document.location.href))
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
|
from couchpotato import index
|
||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import fireEvent, addEvent
|
from couchpotato.core.event import fireEvent, addEvent
|
||||||
from couchpotato.core.helpers.request import getParam, jsonified
|
|
||||||
from couchpotato.core.helpers.variable import isDict
|
from couchpotato.core.helpers.variable import isDict
|
||||||
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
|
from couchpotato.environment import Env
|
||||||
from flask.globals import request
|
from tornado.web import RequestHandler
|
||||||
from flask.helpers import url_for
|
|
||||||
from flask.templating import render_template
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
@@ -18,7 +16,8 @@ class Userscript(Plugin):
|
|||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
addApiView('userscript.get/<random>/<path:filename>', self.getUserScript, static = True)
|
addApiView('userscript.get/(.*)/(.*)', self.getUserScript, static = True)
|
||||||
|
|
||||||
addApiView('userscript', self.iFrame)
|
addApiView('userscript', self.iFrame)
|
||||||
addApiView('userscript.add_via_url', self.getViaUrl)
|
addApiView('userscript.add_via_url', self.getViaUrl)
|
||||||
addApiView('userscript.includes', self.getIncludes)
|
addApiView('userscript.includes', self.getIncludes)
|
||||||
@@ -26,38 +25,46 @@ class Userscript(Plugin):
|
|||||||
|
|
||||||
addEvent('userscript.get_version', self.getVersion)
|
addEvent('userscript.get_version', self.getVersion)
|
||||||
|
|
||||||
def bookmark(self):
|
def bookmark(self, host = None, **kwargs):
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
'includes': fireEvent('userscript.get_includes', merge = True),
|
'includes': fireEvent('userscript.get_includes', merge = True),
|
||||||
'excludes': fireEvent('userscript.get_excludes', merge = True),
|
'excludes': fireEvent('userscript.get_excludes', merge = True),
|
||||||
'host': getParam('host', None),
|
'host': host,
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.renderTemplate(__file__, 'bookmark.js', **params)
|
return self.renderTemplate(__file__, 'bookmark.js', **params)
|
||||||
|
|
||||||
def getIncludes(self):
|
def getIncludes(self):
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'includes': fireEvent('userscript.get_includes', merge = True),
|
'includes': fireEvent('userscript.get_includes', merge = True),
|
||||||
'excludes': fireEvent('userscript.get_excludes', merge = True),
|
'excludes': fireEvent('userscript.get_excludes', merge = True),
|
||||||
})
|
|
||||||
|
|
||||||
def getUserScript(self, random = '', filename = ''):
|
|
||||||
|
|
||||||
params = {
|
|
||||||
'includes': fireEvent('userscript.get_includes', merge = True),
|
|
||||||
'excludes': fireEvent('userscript.get_excludes', merge = True),
|
|
||||||
'version': self.getVersion(),
|
|
||||||
'api': '%suserscript/' % url_for('api.index').lstrip('/'),
|
|
||||||
'host': request.host_url,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
script = self.renderTemplate(__file__, 'template.js', **params)
|
def getUserScript(self, route):
|
||||||
self.createFile(os.path.join(Env.get('cache_dir'), 'couchpotato.user.js'), script)
|
|
||||||
|
klass = self
|
||||||
|
|
||||||
|
class UserscriptHandler(RequestHandler):
|
||||||
|
|
||||||
|
def get(self, random, route):
|
||||||
|
|
||||||
|
params = {
|
||||||
|
'includes': fireEvent('userscript.get_includes', merge = True),
|
||||||
|
'excludes': fireEvent('userscript.get_excludes', merge = True),
|
||||||
|
'version': klass.getVersion(),
|
||||||
|
'api': '%suserscript/' % Env.get('api_base'),
|
||||||
|
'host': '%s://%s' % (self.request.protocol, self.request.host),
|
||||||
|
}
|
||||||
|
|
||||||
|
script = klass.renderTemplate(__file__, 'template.js', **params)
|
||||||
|
klass.createFile(os.path.join(Env.get('cache_dir'), 'couchpotato.user.js'), script)
|
||||||
|
|
||||||
|
self.redirect(Env.get('api_base') + 'file.cache/couchpotato.user.js')
|
||||||
|
|
||||||
|
Env.get('app').add_handlers(".*$", [('%s%s' % (Env.get('api_base'), route), UserscriptHandler)])
|
||||||
|
|
||||||
from flask.helpers import send_from_directory
|
|
||||||
return send_from_directory(Env.get('cache_dir'), 'couchpotato.user.js')
|
|
||||||
|
|
||||||
def getVersion(self):
|
def getVersion(self):
|
||||||
|
|
||||||
@@ -69,12 +76,12 @@ class Userscript(Plugin):
|
|||||||
|
|
||||||
return version
|
return version
|
||||||
|
|
||||||
def iFrame(self):
|
def iFrame(self, **kwargs):
|
||||||
return render_template('index.html', sep = os.sep, fireEvent = fireEvent, env = Env)
|
return index()
|
||||||
|
|
||||||
def getViaUrl(self):
|
def getViaUrl(self, url = None, **kwargs):
|
||||||
|
|
||||||
url = getParam('url')
|
print url
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
'url': url,
|
'url': url,
|
||||||
@@ -84,4 +91,4 @@ class Userscript(Plugin):
|
|||||||
log.error('Failed adding movie via url: %s', url)
|
log.error('Failed adding movie via url: %s', url)
|
||||||
params['error'] = params['movie'] if params['movie'] else 'Failed getting movie info'
|
params['error'] = params['movie'] if params['movie'] else 'Failed getting movie info'
|
||||||
|
|
||||||
return jsonified(params)
|
return params
|
||||||
@@ -9,15 +9,16 @@
|
|||||||
// @grant none
|
// @grant none
|
||||||
// @version {{version}}
|
// @version {{version}}
|
||||||
|
|
||||||
// @match {{host}}*
|
// @match {{host}}/*
|
||||||
{% for include in includes %}
|
{% for include in includes %}
|
||||||
// @match {{include}}{% endfor %}
|
// @match {{include}}{% end %}
|
||||||
{% for exclude in excludes %}
|
{% for exclude in excludes %}
|
||||||
// @exclude {{exclude}}{% endfor %}
|
// @exclude {{exclude}}{% end %}
|
||||||
// @exclude {{host}}{{api.rstrip('/')}}*
|
// @exclude {{host}}{{api.rstrip('/')}}*
|
||||||
|
|
||||||
// ==/UserScript==
|
// ==/UserScript==
|
||||||
|
|
||||||
|
{% autoescape None %}
|
||||||
if (window.top == window.self){ // Only run on top window
|
if (window.top == window.self){ // Only run on top window
|
||||||
|
|
||||||
var version = {{version}},
|
var version = {{version}},
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
from .main import V1Importer
|
|
||||||
|
|
||||||
def start():
|
|
||||||
return V1Importer()
|
|
||||||
|
|
||||||
config = []
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
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)
|
|
||||||
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
from couchpotato import get_session
|
from couchpotato import get_session
|
||||||
from couchpotato.core.event import addEvent, fireEvent
|
from couchpotato.core.event import addEvent, fireEvent
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||||
from couchpotato.core.helpers.request import jsonified, getParams
|
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.movie.base import MovieProvider
|
from couchpotato.core.providers.movie.base import MovieProvider
|
||||||
from couchpotato.core.settings.model import Movie
|
from couchpotato.core.settings.model import Movie
|
||||||
@@ -92,11 +91,10 @@ class CouchPotatoApi(MovieProvider):
|
|||||||
|
|
||||||
return suggestions
|
return suggestions
|
||||||
|
|
||||||
def suggestView(self):
|
def suggestView(self, **kwargs):
|
||||||
|
|
||||||
params = getParams()
|
movies = kwargs.get('movies')
|
||||||
movies = params.get('movies')
|
ignore = kwargs.get('ignore', [])
|
||||||
ignore = params.get('ignore', [])
|
|
||||||
|
|
||||||
if not movies:
|
if not movies:
|
||||||
db = get_session()
|
db = get_session()
|
||||||
@@ -105,11 +103,11 @@ class CouchPotatoApi(MovieProvider):
|
|||||||
|
|
||||||
suggestions = self.suggest(movies, ignore)
|
suggestions = self.suggest(movies, ignore)
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
'count': len(suggestions),
|
'count': len(suggestions),
|
||||||
'suggestions': suggestions
|
'suggestions': suggestions
|
||||||
})
|
}
|
||||||
|
|
||||||
def getRequestHeaders(self):
|
def getRequestHeaders(self):
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from couchpotato.core.event import addEvent
|
|||||||
from couchpotato.core.helpers.encoding import simplifyString, toUnicode
|
from couchpotato.core.helpers.encoding import simplifyString, toUnicode
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.movie.base import MovieProvider
|
from couchpotato.core.providers.movie.base import MovieProvider
|
||||||
from libs.themoviedb import tmdb
|
from themoviedb import tmdb
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from __future__ import with_statement
|
|||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import addEvent, fireEvent
|
from couchpotato.core.event import addEvent, fireEvent
|
||||||
from couchpotato.core.helpers.encoding import isInt, toUnicode
|
from couchpotato.core.helpers.encoding import isInt, toUnicode
|
||||||
from couchpotato.core.helpers.request import getParams, jsonified
|
|
||||||
from couchpotato.core.helpers.variable import mergeDicts, tryInt
|
from couchpotato.core.helpers.variable import mergeDicts, tryInt
|
||||||
from couchpotato.core.settings.model import Properties
|
from couchpotato.core.settings.model import Properties
|
||||||
import ConfigParser
|
import ConfigParser
|
||||||
@@ -170,18 +169,16 @@ class Settings(object):
|
|||||||
|
|
||||||
|
|
||||||
def view(self):
|
def view(self):
|
||||||
return jsonified({
|
return {
|
||||||
'options': self.getOptions(),
|
'options': self.getOptions(),
|
||||||
'values': self.getValues()
|
'values': self.getValues()
|
||||||
})
|
}
|
||||||
|
|
||||||
def saveView(self):
|
def saveView(self, **kwargs):
|
||||||
|
|
||||||
params = getParams()
|
section = kwargs.get('section')
|
||||||
|
option = kwargs.get('name')
|
||||||
section = params.get('section')
|
value = kwargs.get('value')
|
||||||
option = params.get('name')
|
|
||||||
value = params.get('value')
|
|
||||||
|
|
||||||
# See if a value handler is attached, use that as value
|
# See if a value handler is attached, use that as value
|
||||||
new_value = fireEvent('setting.save.%s.%s' % (section, option), value, single = True)
|
new_value = fireEvent('setting.save.%s.%s' % (section, option), value, single = True)
|
||||||
@@ -192,9 +189,9 @@ class Settings(object):
|
|||||||
# After save (for re-interval etc)
|
# After save (for re-interval etc)
|
||||||
fireEvent('setting.save.%s.%s.after' % (section, option), single = True)
|
fireEvent('setting.save.%s.%s.after' % (section, option), single = True)
|
||||||
|
|
||||||
return jsonified({
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
})
|
}
|
||||||
|
|
||||||
def getProperty(self, identifier):
|
def getProperty(self, identifier):
|
||||||
from couchpotato import get_session
|
from couchpotato import get_session
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ class Env(object):
|
|||||||
_appname = 'CouchPotato'
|
_appname = 'CouchPotato'
|
||||||
|
|
||||||
''' Environment variables '''
|
''' Environment variables '''
|
||||||
|
_app = None
|
||||||
_encoding = 'UTF-8'
|
_encoding = 'UTF-8'
|
||||||
_debug = False
|
_debug = False
|
||||||
_dev = False
|
_dev = False
|
||||||
|
|||||||
+58
-40
@@ -1,13 +1,12 @@
|
|||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from couchpotato import web
|
from cache import FileSystemCache
|
||||||
from couchpotato.api import api, NonBlockHandler
|
from couchpotato import KeyHandler
|
||||||
|
from couchpotato.api import NonBlockHandler, ApiHandler
|
||||||
from couchpotato.core.event import fireEventAsync, fireEvent
|
from couchpotato.core.event import fireEventAsync, fireEvent
|
||||||
from couchpotato.core.helpers.variable import getDataDir, tryInt
|
from couchpotato.core.helpers.variable import getDataDir, tryInt
|
||||||
from logging import handlers
|
from logging import handlers
|
||||||
from tornado.httpserver import HTTPServer
|
from tornado.httpserver import HTTPServer
|
||||||
from tornado.web import Application, FallbackHandler
|
from tornado.web import Application, StaticFileHandler, RedirectHandler
|
||||||
from tornado.wsgi import WSGIContainer
|
|
||||||
from werkzeug.contrib.cache import FileSystemCache
|
|
||||||
import locale
|
import locale
|
||||||
import logging
|
import logging
|
||||||
import os.path
|
import os.path
|
||||||
@@ -172,11 +171,6 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
|
|||||||
db = Env.get('db_path')
|
db = Env.get('db_path')
|
||||||
db_exists = os.path.isfile(db_path)
|
db_exists = os.path.isfile(db_path)
|
||||||
|
|
||||||
# Load configs & plugins
|
|
||||||
loader = Env.get('loader')
|
|
||||||
loader.preload(root = base_path)
|
|
||||||
loader.run()
|
|
||||||
|
|
||||||
# Load migrations
|
# Load migrations
|
||||||
if db_exists:
|
if db_exists:
|
||||||
|
|
||||||
@@ -201,17 +195,16 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
|
|||||||
from couchpotato.core.settings.model import setup
|
from couchpotato.core.settings.model import setup
|
||||||
setup()
|
setup()
|
||||||
|
|
||||||
# Fill database with needed stuff
|
|
||||||
if not db_exists:
|
|
||||||
fireEvent('app.initialize', in_order = True)
|
|
||||||
|
|
||||||
# Create app
|
# Create app
|
||||||
from couchpotato import app
|
from couchpotato import WebHandler
|
||||||
|
web_base = ('/' + Env.setting('url_base').lstrip('/') + '/') if Env.setting('url_base') else '/'
|
||||||
|
Env.set('web_base', web_base)
|
||||||
|
|
||||||
api_key = Env.setting('api_key')
|
api_key = Env.setting('api_key')
|
||||||
url_base = '/' + Env.setting('url_base').lstrip('/') if Env.setting('url_base') else ''
|
api_base = r'%sapi/%s/' % (web_base, api_key)
|
||||||
|
Env.set('api_base', api_base)
|
||||||
|
|
||||||
# Basic config
|
# Basic config
|
||||||
app.secret_key = api_key
|
|
||||||
host = Env.setting('host', default = '0.0.0.0')
|
host = Env.setting('host', default = '0.0.0.0')
|
||||||
# app.debug = development
|
# app.debug = development
|
||||||
config = {
|
config = {
|
||||||
@@ -222,36 +215,61 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
|
|||||||
'ssl_key': Env.setting('ssl_key', default = None),
|
'ssl_key': Env.setting('ssl_key', default = None),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Static path
|
|
||||||
app.static_folder = os.path.join(base_path, 'couchpotato', 'static')
|
|
||||||
web.add_url_rule('api/%s/static/<path:filename>' % api_key,
|
|
||||||
endpoint = 'static',
|
|
||||||
view_func = app.send_static_file)
|
|
||||||
|
|
||||||
# Register modules
|
# Load the app
|
||||||
app.register_blueprint(web, url_prefix = '%s/' % url_base)
|
application = Application([],
|
||||||
app.register_blueprint(api, url_prefix = '%s/api/%s/' % (url_base, api_key))
|
log_function = lambda x : None,
|
||||||
|
debug = config['use_reloader'],
|
||||||
|
gzip = True,
|
||||||
|
)
|
||||||
|
Env.set('app', application)
|
||||||
|
|
||||||
|
|
||||||
|
# Request handlers
|
||||||
|
application.add_handlers(".*$", [
|
||||||
|
(r'%snonblock/(.*)/' % api_base, NonBlockHandler),
|
||||||
|
|
||||||
|
# API handlers
|
||||||
|
(r'%s(.*)/' % api_base, ApiHandler), # Main API handler
|
||||||
|
(r'%sgetkey/' % web_base, KeyHandler), # Get API key
|
||||||
|
(r'%s' % api_base, RedirectHandler, {"url": web_base + 'docs/'}), # API docs
|
||||||
|
|
||||||
|
# Catch all webhandlers
|
||||||
|
(r'%s(.*)/' % web_base, WebHandler),
|
||||||
|
(r'%s(.*)' % web_base, WebHandler),
|
||||||
|
(r'(.*)', WebHandler),
|
||||||
|
])
|
||||||
|
|
||||||
|
# Static paths
|
||||||
|
static_path = '%sstatic/' % api_base
|
||||||
|
for dir_name in ['fonts', 'images', 'scripts', 'style']:
|
||||||
|
application.add_handlers(".*$", [
|
||||||
|
('%s%s/(.*)' % (static_path, dir_name), StaticFileHandler, {'path': os.path.join(base_path, 'couchpotato', 'static', dir_name)})
|
||||||
|
])
|
||||||
|
Env.set('static_path', static_path);
|
||||||
|
|
||||||
|
|
||||||
|
# Load configs & plugins
|
||||||
|
loader = Env.get('loader')
|
||||||
|
loader.preload(root = base_path)
|
||||||
|
loader.run()
|
||||||
|
|
||||||
|
|
||||||
|
# Fill database with needed stuff
|
||||||
|
if not db_exists:
|
||||||
|
fireEvent('app.initialize', in_order = True)
|
||||||
|
|
||||||
|
|
||||||
|
# Go go go!
|
||||||
|
from tornado.ioloop import IOLoop
|
||||||
|
loop = IOLoop.current()
|
||||||
|
|
||||||
|
|
||||||
# Some logging and fire load event
|
# Some logging and fire load event
|
||||||
try: log.info('Starting server on port %(port)s', config)
|
try: log.info('Starting server on port %(port)s', config)
|
||||||
except: pass
|
except: pass
|
||||||
fireEventAsync('app.load')
|
fireEventAsync('app.load')
|
||||||
|
|
||||||
# Go go go!
|
|
||||||
from tornado.ioloop import IOLoop
|
|
||||||
web_container = WSGIContainer(app)
|
|
||||||
web_container._log = _log
|
|
||||||
loop = IOLoop.current()
|
|
||||||
|
|
||||||
|
|
||||||
application = Application([
|
|
||||||
(r'%s/api/%s/nonblock/(.*)/' % (url_base, api_key), NonBlockHandler),
|
|
||||||
(r'.*', FallbackHandler, dict(fallback = web_container)),
|
|
||||||
],
|
|
||||||
log_function = lambda x : None,
|
|
||||||
debug = config['use_reloader'],
|
|
||||||
gzip = True,
|
|
||||||
)
|
|
||||||
|
|
||||||
if config['ssl_cert'] and config['ssl_key']:
|
if config['ssl_cert'] and config['ssl_key']:
|
||||||
server = HTTPServer(application, no_keep_alive = True, ssl_options = {
|
server = HTTPServer(application, no_keep_alive = True, ssl_options = {
|
||||||
|
|||||||
@@ -1,27 +1,28 @@
|
|||||||
|
{% autoescape None %}
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" href="{{ url_for('web.static', filename='style/api.css') }}" type="text/css">
|
<link rel="stylesheet" href="{{ Env.get('static_path') }}style/api.css" type="text/css">
|
||||||
<title>API documentation</title>
|
<title>API documentation</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<h1>CouchPotato API Documentation</h1>
|
<h1>CouchPotato API Documentation</h1>
|
||||||
<div class="api">
|
<div class="api">
|
||||||
You can access the API via <pre>{{ fireEvent('app.api_url', single = True)|safe }}/</pre>
|
You can access the API via <pre>{{ Env.get('api_base') }}</pre>
|
||||||
To see it in action, have a look at the webinterface with Firebug (on firefox) or the development tools included in Chrome.
|
To see it in action, have a look at the webinterface with Firebug (on firefox) or the development tools included in Chrome.
|
||||||
All the data that you see there are from the API.
|
All the data that you see there are from the API.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
A normal API call:
|
A normal API call:
|
||||||
<pre><a href="{{ fireEvent('app.api_url', single = True)|safe }}/updater.info/">{{ fireEvent('app.api_url', single = True)|safe }}/updater.info/</a></pre>
|
<pre><a href="{{ Env.get('api_base') }}updater.info/">{{ Env.get('api_base') }}updater.info/</a></pre>
|
||||||
<br />
|
<br />
|
||||||
You can also use the API over another domain using JSONP, the callback function should be in 'callback_func'
|
You can also use the API over another domain using JSONP, the callback function should be in 'callback_func'
|
||||||
<pre><a href="{{ fireEvent('app.api_url', single = True)|safe }}/updater.info/?callback_func=myfunction">{{ fireEvent('app.api_url', single = True)|safe }}/updater.info/?callback_func=myfunction</a></pre>
|
<pre><a href="{{ Env.get('api_base') }}updater.info/?callback_func=myfunction">{{ Env.get('api_base') }}updater.info/?callback_func=myfunction</a></pre>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
Get the API key:
|
Get the API key:
|
||||||
<pre><a href="{{ url_for('web.index') }}getkey/?p=md5(password)&u=md5(username)">{{ url_for('web.index') }}getkey/?p=md5(password)&u=md5(username)</a></pre>
|
<pre><a href="{{ Env.get('web_base') }}getkey/?p=md5(password)&u=md5(username)">{{ Env.get('web_base') }}getkey/?p=md5(password)&u=md5(username)</a></pre>
|
||||||
Will return {"api_key": "XXXXXXXXXX", "success": true}. When username or password is empty you don't need to md5 it.
|
Will return {"api_key": "XXXXXXXXXX", "success": true}. When username or password is empty you don't need to md5 it.
|
||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
@@ -41,9 +42,9 @@
|
|||||||
<td class="type">{{ api_docs[route]['params'][param].get('type', 'string') }}</td>
|
<td class="type">{{ api_docs[route]['params'][param].get('type', 'string') }}</td>
|
||||||
<td class="description">{{ api_docs[route]['params'][param]['desc'] }}</td>
|
<td class="description">{{ api_docs[route]['params'][param]['desc'] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% end %}
|
||||||
</table>
|
</table>
|
||||||
{% endif %}
|
{% end %}
|
||||||
|
|
||||||
{% if api_docs[route].get('return') %}
|
{% if api_docs[route].get('return') %}
|
||||||
<h3>Return</h3>
|
<h3>Return</h3>
|
||||||
@@ -52,14 +53,14 @@
|
|||||||
{% if api_docs[route]['return'].get('example') %}
|
{% if api_docs[route]['return'].get('example') %}
|
||||||
<div class="example">
|
<div class="example">
|
||||||
<h4>Example</h4>
|
<h4>Example</h4>
|
||||||
<pre>{{ api_docs[route]['return'].get('example', '')|safe }}</pre>
|
<pre>{{ api_docs[route]['return'].get('example', '') }}</pre>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% end %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% end %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% end %}
|
||||||
{% endfor %}
|
{% end %}
|
||||||
|
|
||||||
<div class="missing">
|
<div class="missing">
|
||||||
<h1>Missing documentation</h1>
|
<h1>Missing documentation</h1>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
{% autoescape None %}
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
@@ -5,38 +6,38 @@
|
|||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
|
||||||
{% for url in fireEvent('clientscript.get_styles', as_html = True, location = 'front', single = True) %}
|
{% for url in fireEvent('clientscript.get_styles', as_html = True, location = 'front', single = True) %}
|
||||||
<link rel="stylesheet" href="{{ url_for('web.index') }}{{ url }}" type="text/css">{% endfor %}
|
<link rel="stylesheet" href="{{ Env.get('web_base') }}{{ url }}" type="text/css">{% end %}
|
||||||
{% for url in fireEvent('clientscript.get_scripts', as_html = True, location = 'front', single = True) %}
|
{% for url in fireEvent('clientscript.get_scripts', as_html = True, location = 'front', single = True) %}
|
||||||
<script type="text/javascript" src="{{ url_for('web.index') }}{{ url }}"></script>{% endfor %}
|
<script type="text/javascript" src="{{ Env.get('web_base') }}{{ url }}"></script>{% end %}
|
||||||
|
|
||||||
{% for url in fireEvent('clientscript.get_scripts', as_html = True, location = 'head', single = True) %}
|
{% for url in fireEvent('clientscript.get_scripts', as_html = True, location = 'head', single = True) %}
|
||||||
<script type="text/javascript" src="{{ url_for('web.index') }}{{ url }}"></script>{% endfor %}
|
<script type="text/javascript" src="{{ Env.get('web_base') }}{{ url }}"></script>{% end %}
|
||||||
{% for url in fireEvent('clientscript.get_styles', as_html = True, location = 'head', single = True) %}
|
{% for url in fireEvent('clientscript.get_styles', as_html = True, location = 'head', single = True) %}
|
||||||
<link rel="stylesheet" href="{{ url_for('web.index') }}{{ url }}" type="text/css">{% endfor %}
|
<link rel="stylesheet" href="{{ Env.get('web_base') }}{{ url }}" type="text/css">{% end %}
|
||||||
|
|
||||||
<link href="{{ url_for('web.static', filename='images/favicon.ico') }}" rel="icon" type="image/x-icon" />
|
<link href="{{ Env.get('static_path') }}images/favicon.ico" rel="icon" type="image/x-icon" />
|
||||||
<link rel="apple-touch-icon" href="{{ url_for('web.static', filename='images/homescreen.png') }}" />
|
<link rel="apple-touch-icon" href="{{ Env.get('static_path') }}images/homescreen.png" />
|
||||||
|
|
||||||
<script type="text/javascript" src="https://www.youtube.com/player_api" defer="defer"></script>
|
<script type="text/javascript" src="https://www.youtube.com/player_api" defer="defer"></script>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
||||||
if($(window).getSize().x <= 480)
|
if($(window).getSize().x <= 480)
|
||||||
window.addEvent('load', function() {
|
window.addEvent('load', function() {
|
||||||
|
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
window.scrollTo(0, 1);
|
window.scrollTo(0, 1);
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEvent('domready', function() {
|
window.addEvent('domready', function() {
|
||||||
new Uniform();
|
new Uniform();
|
||||||
|
|
||||||
Api.setup({
|
Api.setup({
|
||||||
'url': {{ url_for('api.index')|tojson|safe }},
|
'url': {{ json_encode(Env.get('api_base')) }},
|
||||||
'path_sep': {{ sep|tojson|safe }},
|
'path_sep': {{ json_encode(sep) }},
|
||||||
'is_remote': false
|
'is_remote': false
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -61,29 +62,29 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
Quality.setup({
|
Quality.setup({
|
||||||
'profiles': {{ fireEvent('profile.all', single = True)|tojson|safe }},
|
'profiles': {{ json_encode(fireEvent('profile.all', single = True)) }},
|
||||||
'qualities': {{ fireEvent('quality.all', single = True)|tojson|safe }}
|
'qualities': {{ json_encode(fireEvent('quality.all', single = True)) }}
|
||||||
});
|
});
|
||||||
|
|
||||||
Status.setup({{ fireEvent('status.all', single = True)|tojson|safe }});
|
Status.setup({{ json_encode(fireEvent('status.all', single = True)) }});
|
||||||
|
|
||||||
File.Type.setup({{ fireEvent('file.types', single = True)|tojson|safe }});
|
File.Type.setup({{ json_encode(fireEvent('file.types', single = True)) }});
|
||||||
|
|
||||||
App.setup({
|
App.setup({
|
||||||
'base_url': {{ url_for('web.index')|tojson|safe }},
|
'base_url': {{ json_encode(Env.get('web_base')) }},
|
||||||
'args': {{ env.get('args')|tojson|safe }},
|
'args': {{ json_encode(Env.get('args')) }},
|
||||||
'options': {{ ('%s' % env.get('options'))|tojson|safe }},
|
'options': {{ json_encode(('%s' % Env.get('options'))) }},
|
||||||
'app_dir': {{ env.get('app_dir')|tojson|safe }},
|
'app_dir': {{ json_encode(Env.get('app_dir')) }},
|
||||||
'data_dir': {{ env.get('data_dir')|tojson|safe }},
|
'data_dir': {{ json_encode(Env.get('data_dir')) }},
|
||||||
'pid': {{ env.getPid()|tojson|safe }},
|
'pid': {{ json_encode(Env.getPid()) }},
|
||||||
'userscript_version': {{ fireEvent('userscript.get_version', single = True)|tojson|safe }}
|
'userscript_version': {{ json_encode(fireEvent('userscript.get_version', single = True)) }}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
{% if env.setting('show_wizard') %}
|
{% if Env.setting('show_wizard') %}
|
||||||
if(!window.location.href.contains('wizard'))
|
if(!window.location.href.contains('wizard'))
|
||||||
window.location = '{{ url_for('web.index') }}wizard/'
|
window.location = '{{ Env.get('web_base') }}wizard/'
|
||||||
{% endif %}
|
{% end %}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<title>CouchPotato</title>
|
<title>CouchPotato</title>
|
||||||
|
|||||||
Vendored
+262
@@ -0,0 +1,262 @@
|
|||||||
|
"""
|
||||||
|
copied from
|
||||||
|
werkzeug.contrib.cache
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
from cache.posixemulation import rename
|
||||||
|
from itertools import izip
|
||||||
|
from time import time
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import tempfile
|
||||||
|
try:
|
||||||
|
from hashlib import md5
|
||||||
|
except ImportError:
|
||||||
|
from md5 import new as md5
|
||||||
|
|
||||||
|
try:
|
||||||
|
import cPickle as pickle
|
||||||
|
except ImportError:
|
||||||
|
import pickle
|
||||||
|
|
||||||
|
|
||||||
|
def _items(mappingorseq):
|
||||||
|
"""Wrapper for efficient iteration over mappings represented by dicts
|
||||||
|
or sequences::
|
||||||
|
|
||||||
|
>>> for k, v in _items((i, i*i) for i in xrange(5)):
|
||||||
|
... assert k*k == v
|
||||||
|
|
||||||
|
>>> for k, v in _items(dict((i, i*i) for i in xrange(5))):
|
||||||
|
... assert k*k == v
|
||||||
|
|
||||||
|
"""
|
||||||
|
return mappingorseq.iteritems() if hasattr(mappingorseq, 'iteritems') \
|
||||||
|
else mappingorseq
|
||||||
|
|
||||||
|
|
||||||
|
class BaseCache(object):
|
||||||
|
"""Baseclass for the cache systems. All the cache systems implement this
|
||||||
|
API or a superset of it.
|
||||||
|
|
||||||
|
:param default_timeout: the default timeout that is used if no timeout is
|
||||||
|
specified on :meth:`set`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, default_timeout = 300):
|
||||||
|
self.default_timeout = default_timeout
|
||||||
|
|
||||||
|
def delete(self, key):
|
||||||
|
"""Deletes `key` from the cache. If it does not exist in the cache
|
||||||
|
nothing happens.
|
||||||
|
|
||||||
|
:param key: the key to delete.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_many(self, *keys):
|
||||||
|
"""Returns a list of values for the given keys.
|
||||||
|
For each key a item in the list is created. Example::
|
||||||
|
|
||||||
|
foo, bar = cache.get_many("foo", "bar")
|
||||||
|
|
||||||
|
If a key can't be looked up `None` is returned for that key
|
||||||
|
instead.
|
||||||
|
|
||||||
|
:param keys: The function accepts multiple keys as positional
|
||||||
|
arguments.
|
||||||
|
"""
|
||||||
|
return map(self.get, keys)
|
||||||
|
|
||||||
|
def get_dict(self, *keys):
|
||||||
|
"""Works like :meth:`get_many` but returns a dict::
|
||||||
|
|
||||||
|
d = cache.get_dict("foo", "bar")
|
||||||
|
foo = d["foo"]
|
||||||
|
bar = d["bar"]
|
||||||
|
|
||||||
|
:param keys: The function accepts multiple keys as positional
|
||||||
|
arguments.
|
||||||
|
"""
|
||||||
|
return dict(izip(keys, self.get_many(*keys)))
|
||||||
|
|
||||||
|
def set(self, key, value, timeout = None):
|
||||||
|
"""Adds a new key/value to the cache (overwrites value, if key already
|
||||||
|
exists in the cache).
|
||||||
|
|
||||||
|
:param key: the key to set
|
||||||
|
:param value: the value for the key
|
||||||
|
:param timeout: the cache timeout for the key (if not specified,
|
||||||
|
it uses the default timeout).
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def add(self, key, value, timeout = None):
|
||||||
|
"""Works like :meth:`set` but does not overwrite the values of already
|
||||||
|
existing keys.
|
||||||
|
|
||||||
|
:param key: the key to set
|
||||||
|
:param value: the value for the key
|
||||||
|
:param timeout: the cache timeout for the key or the default
|
||||||
|
timeout if not specified.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_many(self, mapping, timeout = None):
|
||||||
|
"""Sets multiple keys and values from a mapping.
|
||||||
|
|
||||||
|
:param mapping: a mapping with the keys/values to set.
|
||||||
|
:param timeout: the cache timeout for the key (if not specified,
|
||||||
|
it uses the default timeout).
|
||||||
|
"""
|
||||||
|
for key, value in _items(mapping):
|
||||||
|
self.set(key, value, timeout)
|
||||||
|
|
||||||
|
def delete_many(self, *keys):
|
||||||
|
"""Deletes multiple keys at once.
|
||||||
|
|
||||||
|
:param keys: The function accepts multiple keys as positional
|
||||||
|
arguments.
|
||||||
|
"""
|
||||||
|
for key in keys:
|
||||||
|
self.delete(key)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
"""Clears the cache. Keep in mind that not all caches support
|
||||||
|
completely clearing the cache.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def inc(self, key, delta = 1):
|
||||||
|
"""Increments the value of a key by `delta`. If the key does
|
||||||
|
not yet exist it is initialized with `delta`.
|
||||||
|
|
||||||
|
For supporting caches this is an atomic operation.
|
||||||
|
|
||||||
|
:param key: the key to increment.
|
||||||
|
:param delta: the delta to add.
|
||||||
|
"""
|
||||||
|
self.set(key, (self.get(key) or 0) + delta)
|
||||||
|
|
||||||
|
def dec(self, key, delta = 1):
|
||||||
|
"""Decrements the value of a key by `delta`. If the key does
|
||||||
|
not yet exist it is initialized with `-delta`.
|
||||||
|
|
||||||
|
For supporting caches this is an atomic operation.
|
||||||
|
|
||||||
|
:param key: the key to increment.
|
||||||
|
:param delta: the delta to subtract.
|
||||||
|
"""
|
||||||
|
self.set(key, (self.get(key) or 0) - delta)
|
||||||
|
|
||||||
|
|
||||||
|
class FileSystemCache(BaseCache):
|
||||||
|
"""A cache that stores the items on the file system. This cache depends
|
||||||
|
on being the only user of the `cache_dir`. Make absolutely sure that
|
||||||
|
nobody but this cache stores files there or otherwise the cache will
|
||||||
|
randomly delete files therein.
|
||||||
|
|
||||||
|
:param cache_dir: the directory where cache files are stored.
|
||||||
|
:param threshold: the maximum number of items the cache stores before
|
||||||
|
it starts deleting some.
|
||||||
|
:param default_timeout: the default timeout that is used if no timeout is
|
||||||
|
specified on :meth:`~BaseCache.set`.
|
||||||
|
:param mode: the file mode wanted for the cache files, default 0600
|
||||||
|
"""
|
||||||
|
|
||||||
|
#: used for temporary files by the FileSystemCache
|
||||||
|
_fs_transaction_suffix = '.__wz_cache'
|
||||||
|
|
||||||
|
def __init__(self, cache_dir, threshold = 500, default_timeout = 300, mode = 0600):
|
||||||
|
BaseCache.__init__(self, default_timeout)
|
||||||
|
self._path = cache_dir
|
||||||
|
self._threshold = threshold
|
||||||
|
self._mode = mode
|
||||||
|
if not os.path.exists(self._path):
|
||||||
|
os.makedirs(self._path)
|
||||||
|
|
||||||
|
def _list_dir(self):
|
||||||
|
"""return a list of (fully qualified) cache filenames
|
||||||
|
"""
|
||||||
|
return [os.path.join(self._path, fn) for fn in os.listdir(self._path)
|
||||||
|
if not fn.endswith(self._fs_transaction_suffix)]
|
||||||
|
|
||||||
|
def _prune(self):
|
||||||
|
entries = self._list_dir()
|
||||||
|
if len(entries) > self._threshold:
|
||||||
|
now = time()
|
||||||
|
for idx, fname in enumerate(entries):
|
||||||
|
remove = False
|
||||||
|
f = None
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
f = open(fname, 'rb')
|
||||||
|
expires = pickle.load(f)
|
||||||
|
remove = expires <= now or idx % 3 == 0
|
||||||
|
finally:
|
||||||
|
if f is not None:
|
||||||
|
f.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if remove:
|
||||||
|
try:
|
||||||
|
os.remove(fname)
|
||||||
|
except (IOError, OSError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
for fname in self._list_dir():
|
||||||
|
try:
|
||||||
|
os.remove(fname)
|
||||||
|
except (IOError, OSError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _get_filename(self, key):
|
||||||
|
hash = md5(key).hexdigest()
|
||||||
|
return os.path.join(self._path, hash)
|
||||||
|
|
||||||
|
def get(self, key):
|
||||||
|
filename = self._get_filename(key)
|
||||||
|
try:
|
||||||
|
f = open(filename, 'rb')
|
||||||
|
try:
|
||||||
|
if pickle.load(f) >= time():
|
||||||
|
return pickle.load(f)
|
||||||
|
finally:
|
||||||
|
f.close()
|
||||||
|
os.remove(filename)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def add(self, key, value, timeout = None):
|
||||||
|
filename = self._get_filename(key)
|
||||||
|
if not os.path.exists(filename):
|
||||||
|
self.set(key, value, timeout)
|
||||||
|
|
||||||
|
def set(self, key, value, timeout = None):
|
||||||
|
if timeout is None:
|
||||||
|
timeout = self.default_timeout
|
||||||
|
filename = self._get_filename(key)
|
||||||
|
self._prune()
|
||||||
|
try:
|
||||||
|
fd, tmp = tempfile.mkstemp(suffix = self._fs_transaction_suffix,
|
||||||
|
dir = self._path)
|
||||||
|
f = os.fdopen(fd, 'wb')
|
||||||
|
try:
|
||||||
|
pickle.dump(int(time() + timeout), f, 1)
|
||||||
|
pickle.dump(value, f, pickle.HIGHEST_PROTOCOL)
|
||||||
|
finally:
|
||||||
|
f.close()
|
||||||
|
rename(tmp, filename)
|
||||||
|
os.chmod(filename, self._mode)
|
||||||
|
except (IOError, OSError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def delete(self, key):
|
||||||
|
try:
|
||||||
|
os.remove(self._get_filename(key))
|
||||||
|
except (IOError, OSError):
|
||||||
|
pass
|
||||||
File renamed without changes.
@@ -1,44 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask
|
|
||||||
~~~~~
|
|
||||||
|
|
||||||
A microframework based on Werkzeug. It's extensively documented
|
|
||||||
and follows best practice patterns.
|
|
||||||
|
|
||||||
:copyright: (c) 2011 by Armin Ronacher.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__version__ = '0.9'
|
|
||||||
|
|
||||||
# utilities we import from Werkzeug and Jinja2 that are unused
|
|
||||||
# in the module but are exported as public interface.
|
|
||||||
from werkzeug.exceptions import abort
|
|
||||||
from werkzeug.utils import redirect
|
|
||||||
from jinja2 import Markup, escape
|
|
||||||
|
|
||||||
from .app import Flask, Request, Response
|
|
||||||
from .config import Config
|
|
||||||
from .helpers import url_for, jsonify, json_available, flash, \
|
|
||||||
send_file, send_from_directory, get_flashed_messages, \
|
|
||||||
get_template_attribute, make_response, safe_join, \
|
|
||||||
stream_with_context
|
|
||||||
from .globals import current_app, g, request, session, _request_ctx_stack, \
|
|
||||||
_app_ctx_stack
|
|
||||||
from .ctx import has_request_context, has_app_context, \
|
|
||||||
after_this_request
|
|
||||||
from .module import Module
|
|
||||||
from .blueprints import Blueprint
|
|
||||||
from .templating import render_template, render_template_string
|
|
||||||
|
|
||||||
# the signals
|
|
||||||
from .signals import signals_available, template_rendered, request_started, \
|
|
||||||
request_finished, got_request_exception, request_tearing_down
|
|
||||||
|
|
||||||
# only import json if it's available
|
|
||||||
if json_available:
|
|
||||||
from .helpers import json
|
|
||||||
|
|
||||||
# backwards compat, goes away in 1.0
|
|
||||||
from .sessions import SecureCookieSession as Session
|
|
||||||
-1701
File diff suppressed because it is too large.
Load diff
@@ -1,345 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.blueprints
|
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Blueprints are the recommended way to implement larger or more
|
|
||||||
pluggable applications in Flask 0.7 and later.
|
|
||||||
|
|
||||||
:copyright: (c) 2011 by Armin Ronacher.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
from functools import update_wrapper
|
|
||||||
|
|
||||||
from .helpers import _PackageBoundObject, _endpoint_from_view_func
|
|
||||||
|
|
||||||
|
|
||||||
class BlueprintSetupState(object):
|
|
||||||
"""Temporary holder object for registering a blueprint with the
|
|
||||||
application. An instance of this class is created by the
|
|
||||||
:meth:`~flask.Blueprint.make_setup_state` method and later passed
|
|
||||||
to all register callback functions.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, blueprint, app, options, first_registration):
|
|
||||||
#: a reference to the current application
|
|
||||||
self.app = app
|
|
||||||
|
|
||||||
#: a reference to the blueprint that created this setup state.
|
|
||||||
self.blueprint = blueprint
|
|
||||||
|
|
||||||
#: a dictionary with all options that were passed to the
|
|
||||||
#: :meth:`~flask.Flask.register_blueprint` method.
|
|
||||||
self.options = options
|
|
||||||
|
|
||||||
#: as blueprints can be registered multiple times with the
|
|
||||||
#: application and not everything wants to be registered
|
|
||||||
#: multiple times on it, this attribute can be used to figure
|
|
||||||
#: out if the blueprint was registered in the past already.
|
|
||||||
self.first_registration = first_registration
|
|
||||||
|
|
||||||
subdomain = self.options.get('subdomain')
|
|
||||||
if subdomain is None:
|
|
||||||
subdomain = self.blueprint.subdomain
|
|
||||||
|
|
||||||
#: The subdomain that the blueprint should be active for, `None`
|
|
||||||
#: otherwise.
|
|
||||||
self.subdomain = subdomain
|
|
||||||
|
|
||||||
url_prefix = self.options.get('url_prefix')
|
|
||||||
if url_prefix is None:
|
|
||||||
url_prefix = self.blueprint.url_prefix
|
|
||||||
|
|
||||||
#: The prefix that should be used for all URLs defined on the
|
|
||||||
#: blueprint.
|
|
||||||
self.url_prefix = url_prefix
|
|
||||||
|
|
||||||
#: A dictionary with URL defaults that is added to each and every
|
|
||||||
#: URL that was defined with the blueprint.
|
|
||||||
self.url_defaults = dict(self.blueprint.url_values_defaults)
|
|
||||||
self.url_defaults.update(self.options.get('url_defaults', ()))
|
|
||||||
|
|
||||||
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
|
|
||||||
"""A helper method to register a rule (and optionally a view function)
|
|
||||||
to the application. The endpoint is automatically prefixed with the
|
|
||||||
blueprint's name.
|
|
||||||
"""
|
|
||||||
if self.url_prefix:
|
|
||||||
rule = self.url_prefix + rule
|
|
||||||
options.setdefault('subdomain', self.subdomain)
|
|
||||||
if endpoint is None:
|
|
||||||
endpoint = _endpoint_from_view_func(view_func)
|
|
||||||
defaults = self.url_defaults
|
|
||||||
if 'defaults' in options:
|
|
||||||
defaults = dict(defaults, **options.pop('defaults'))
|
|
||||||
self.app.add_url_rule(rule, '%s.%s' % (self.blueprint.name, endpoint),
|
|
||||||
view_func, defaults=defaults, **options)
|
|
||||||
|
|
||||||
|
|
||||||
class Blueprint(_PackageBoundObject):
|
|
||||||
"""Represents a blueprint. A blueprint is an object that records
|
|
||||||
functions that will be called with the
|
|
||||||
:class:`~flask.blueprint.BlueprintSetupState` later to register functions
|
|
||||||
or other things on the main application. See :ref:`blueprints` for more
|
|
||||||
information.
|
|
||||||
|
|
||||||
.. versionadded:: 0.7
|
|
||||||
"""
|
|
||||||
|
|
||||||
warn_on_modifications = False
|
|
||||||
_got_registered_once = False
|
|
||||||
|
|
||||||
def __init__(self, name, import_name, static_folder=None,
|
|
||||||
static_url_path=None, template_folder=None,
|
|
||||||
url_prefix=None, subdomain=None, url_defaults=None):
|
|
||||||
_PackageBoundObject.__init__(self, import_name, template_folder)
|
|
||||||
self.name = name
|
|
||||||
self.url_prefix = url_prefix
|
|
||||||
self.subdomain = subdomain
|
|
||||||
self.static_folder = static_folder
|
|
||||||
self.static_url_path = static_url_path
|
|
||||||
self.deferred_functions = []
|
|
||||||
self.view_functions = {}
|
|
||||||
if url_defaults is None:
|
|
||||||
url_defaults = {}
|
|
||||||
self.url_values_defaults = url_defaults
|
|
||||||
|
|
||||||
def record(self, func):
|
|
||||||
"""Registers a function that is called when the blueprint is
|
|
||||||
registered on the application. This function is called with the
|
|
||||||
state as argument as returned by the :meth:`make_setup_state`
|
|
||||||
method.
|
|
||||||
"""
|
|
||||||
if self._got_registered_once and self.warn_on_modifications:
|
|
||||||
from warnings import warn
|
|
||||||
warn(Warning('The blueprint was already registered once '
|
|
||||||
'but is getting modified now. These changes '
|
|
||||||
'will not show up.'))
|
|
||||||
self.deferred_functions.append(func)
|
|
||||||
|
|
||||||
def record_once(self, func):
|
|
||||||
"""Works like :meth:`record` but wraps the function in another
|
|
||||||
function that will ensure the function is only called once. If the
|
|
||||||
blueprint is registered a second time on the application, the
|
|
||||||
function passed is not called.
|
|
||||||
"""
|
|
||||||
def wrapper(state):
|
|
||||||
if state.first_registration:
|
|
||||||
func(state)
|
|
||||||
return self.record(update_wrapper(wrapper, func))
|
|
||||||
|
|
||||||
def make_setup_state(self, app, options, first_registration=False):
|
|
||||||
"""Creates an instance of :meth:`~flask.blueprints.BlueprintSetupState`
|
|
||||||
object that is later passed to the register callback functions.
|
|
||||||
Subclasses can override this to return a subclass of the setup state.
|
|
||||||
"""
|
|
||||||
return BlueprintSetupState(self, app, options, first_registration)
|
|
||||||
|
|
||||||
def register(self, app, options, first_registration=False):
|
|
||||||
"""Called by :meth:`Flask.register_blueprint` to register a blueprint
|
|
||||||
on the application. This can be overridden to customize the register
|
|
||||||
behavior. Keyword arguments from
|
|
||||||
:func:`~flask.Flask.register_blueprint` are directly forwarded to this
|
|
||||||
method in the `options` dictionary.
|
|
||||||
"""
|
|
||||||
self._got_registered_once = True
|
|
||||||
state = self.make_setup_state(app, options, first_registration)
|
|
||||||
if self.has_static_folder:
|
|
||||||
state.add_url_rule(self.static_url_path + '/<path:filename>',
|
|
||||||
view_func=self.send_static_file,
|
|
||||||
endpoint='static')
|
|
||||||
|
|
||||||
for deferred in self.deferred_functions:
|
|
||||||
deferred(state)
|
|
||||||
|
|
||||||
def route(self, rule, **options):
|
|
||||||
"""Like :meth:`Flask.route` but for a blueprint. The endpoint for the
|
|
||||||
:func:`url_for` function is prefixed with the name of the blueprint.
|
|
||||||
"""
|
|
||||||
def decorator(f):
|
|
||||||
endpoint = options.pop("endpoint", f.__name__)
|
|
||||||
self.add_url_rule(rule, endpoint, f, **options)
|
|
||||||
return f
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
|
|
||||||
"""Like :meth:`Flask.add_url_rule` but for a blueprint. The endpoint for
|
|
||||||
the :func:`url_for` function is prefixed with the name of the blueprint.
|
|
||||||
"""
|
|
||||||
if endpoint:
|
|
||||||
assert '.' not in endpoint, "Blueprint endpoint's should not contain dot's"
|
|
||||||
self.record(lambda s:
|
|
||||||
s.add_url_rule(rule, endpoint, view_func, **options))
|
|
||||||
|
|
||||||
def endpoint(self, endpoint):
|
|
||||||
"""Like :meth:`Flask.endpoint` but for a blueprint. This does not
|
|
||||||
prefix the endpoint with the blueprint name, this has to be done
|
|
||||||
explicitly by the user of this method. If the endpoint is prefixed
|
|
||||||
with a `.` it will be registered to the current blueprint, otherwise
|
|
||||||
it's an application independent endpoint.
|
|
||||||
"""
|
|
||||||
def decorator(f):
|
|
||||||
def register_endpoint(state):
|
|
||||||
state.app.view_functions[endpoint] = f
|
|
||||||
self.record_once(register_endpoint)
|
|
||||||
return f
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
def app_template_filter(self, name=None):
|
|
||||||
"""Register a custom template filter, available application wide. Like
|
|
||||||
:meth:`Flask.template_filter` but for a blueprint.
|
|
||||||
|
|
||||||
:param name: the optional name of the filter, otherwise the
|
|
||||||
function name will be used.
|
|
||||||
"""
|
|
||||||
def decorator(f):
|
|
||||||
self.add_app_template_filter(f, name=name)
|
|
||||||
return f
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
def add_app_template_filter(self, f, name=None):
|
|
||||||
"""Register a custom template filter, available application wide. Like
|
|
||||||
:meth:`Flask.add_template_filter` but for a blueprint. Works exactly
|
|
||||||
like the :meth:`app_template_filter` decorator.
|
|
||||||
|
|
||||||
:param name: the optional name of the filter, otherwise the
|
|
||||||
function name will be used.
|
|
||||||
"""
|
|
||||||
def register_template(state):
|
|
||||||
state.app.jinja_env.filters[name or f.__name__] = f
|
|
||||||
self.record_once(register_template)
|
|
||||||
|
|
||||||
def before_request(self, f):
|
|
||||||
"""Like :meth:`Flask.before_request` but for a blueprint. This function
|
|
||||||
is only executed before each request that is handled by a function of
|
|
||||||
that blueprint.
|
|
||||||
"""
|
|
||||||
self.record_once(lambda s: s.app.before_request_funcs
|
|
||||||
.setdefault(self.name, []).append(f))
|
|
||||||
return f
|
|
||||||
|
|
||||||
def before_app_request(self, f):
|
|
||||||
"""Like :meth:`Flask.before_request`. Such a function is executed
|
|
||||||
before each request, even if outside of a blueprint.
|
|
||||||
"""
|
|
||||||
self.record_once(lambda s: s.app.before_request_funcs
|
|
||||||
.setdefault(None, []).append(f))
|
|
||||||
return f
|
|
||||||
|
|
||||||
def before_app_first_request(self, f):
|
|
||||||
"""Like :meth:`Flask.before_first_request`. Such a function is
|
|
||||||
executed before the first request to the application.
|
|
||||||
"""
|
|
||||||
self.record_once(lambda s: s.app.before_first_request_funcs.append(f))
|
|
||||||
return f
|
|
||||||
|
|
||||||
def after_request(self, f):
|
|
||||||
"""Like :meth:`Flask.after_request` but for a blueprint. This function
|
|
||||||
is only executed after each request that is handled by a function of
|
|
||||||
that blueprint.
|
|
||||||
"""
|
|
||||||
self.record_once(lambda s: s.app.after_request_funcs
|
|
||||||
.setdefault(self.name, []).append(f))
|
|
||||||
return f
|
|
||||||
|
|
||||||
def after_app_request(self, f):
|
|
||||||
"""Like :meth:`Flask.after_request` but for a blueprint. Such a function
|
|
||||||
is executed after each request, even if outside of the blueprint.
|
|
||||||
"""
|
|
||||||
self.record_once(lambda s: s.app.after_request_funcs
|
|
||||||
.setdefault(None, []).append(f))
|
|
||||||
return f
|
|
||||||
|
|
||||||
def teardown_request(self, f):
|
|
||||||
"""Like :meth:`Flask.teardown_request` but for a blueprint. This
|
|
||||||
function is only executed when tearing down requests handled by a
|
|
||||||
function of that blueprint. Teardown request functions are executed
|
|
||||||
when the request context is popped, even when no actual request was
|
|
||||||
performed.
|
|
||||||
"""
|
|
||||||
self.record_once(lambda s: s.app.teardown_request_funcs
|
|
||||||
.setdefault(self.name, []).append(f))
|
|
||||||
return f
|
|
||||||
|
|
||||||
def teardown_app_request(self, f):
|
|
||||||
"""Like :meth:`Flask.teardown_request` but for a blueprint. Such a
|
|
||||||
function is executed when tearing down each request, even if outside of
|
|
||||||
the blueprint.
|
|
||||||
"""
|
|
||||||
self.record_once(lambda s: s.app.teardown_request_funcs
|
|
||||||
.setdefault(None, []).append(f))
|
|
||||||
return f
|
|
||||||
|
|
||||||
def context_processor(self, f):
|
|
||||||
"""Like :meth:`Flask.context_processor` but for a blueprint. This
|
|
||||||
function is only executed for requests handled by a blueprint.
|
|
||||||
"""
|
|
||||||
self.record_once(lambda s: s.app.template_context_processors
|
|
||||||
.setdefault(self.name, []).append(f))
|
|
||||||
return f
|
|
||||||
|
|
||||||
def app_context_processor(self, f):
|
|
||||||
"""Like :meth:`Flask.context_processor` but for a blueprint. Such a
|
|
||||||
function is executed each request, even if outside of the blueprint.
|
|
||||||
"""
|
|
||||||
self.record_once(lambda s: s.app.template_context_processors
|
|
||||||
.setdefault(None, []).append(f))
|
|
||||||
return f
|
|
||||||
|
|
||||||
def app_errorhandler(self, code):
|
|
||||||
"""Like :meth:`Flask.errorhandler` but for a blueprint. This
|
|
||||||
handler is used for all requests, even if outside of the blueprint.
|
|
||||||
"""
|
|
||||||
def decorator(f):
|
|
||||||
self.record_once(lambda s: s.app.errorhandler(code)(f))
|
|
||||||
return f
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
def url_value_preprocessor(self, f):
|
|
||||||
"""Registers a function as URL value preprocessor for this
|
|
||||||
blueprint. It's called before the view functions are called and
|
|
||||||
can modify the url values provided.
|
|
||||||
"""
|
|
||||||
self.record_once(lambda s: s.app.url_value_preprocessors
|
|
||||||
.setdefault(self.name, []).append(f))
|
|
||||||
return f
|
|
||||||
|
|
||||||
def url_defaults(self, f):
|
|
||||||
"""Callback function for URL defaults for this blueprint. It's called
|
|
||||||
with the endpoint and values and should update the values passed
|
|
||||||
in place.
|
|
||||||
"""
|
|
||||||
self.record_once(lambda s: s.app.url_default_functions
|
|
||||||
.setdefault(self.name, []).append(f))
|
|
||||||
return f
|
|
||||||
|
|
||||||
def app_url_value_preprocessor(self, f):
|
|
||||||
"""Same as :meth:`url_value_preprocessor` but application wide.
|
|
||||||
"""
|
|
||||||
self.record_once(lambda s: s.app.url_value_preprocessors
|
|
||||||
.setdefault(None, []).append(f))
|
|
||||||
return f
|
|
||||||
|
|
||||||
def app_url_defaults(self, f):
|
|
||||||
"""Same as :meth:`url_defaults` but application wide.
|
|
||||||
"""
|
|
||||||
self.record_once(lambda s: s.app.url_default_functions
|
|
||||||
.setdefault(None, []).append(f))
|
|
||||||
return f
|
|
||||||
|
|
||||||
def errorhandler(self, code_or_exception):
|
|
||||||
"""Registers an error handler that becomes active for this blueprint
|
|
||||||
only. Please be aware that routing does not happen local to a
|
|
||||||
blueprint so an error handler for 404 usually is not handled by
|
|
||||||
a blueprint unless it is caused inside a view function. Another
|
|
||||||
special case is the 500 internal server error which is always looked
|
|
||||||
up from the application.
|
|
||||||
|
|
||||||
Otherwise works as the :meth:`~flask.Flask.errorhandler` decorator
|
|
||||||
of the :class:`~flask.Flask` object.
|
|
||||||
"""
|
|
||||||
def decorator(f):
|
|
||||||
self.record_once(lambda s: s.app._register_error_handler(
|
|
||||||
self.name, code_or_exception, f))
|
|
||||||
return f
|
|
||||||
return decorator
|
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.config
|
|
||||||
~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Implements the configuration related objects.
|
|
||||||
|
|
||||||
:copyright: (c) 2011 by Armin Ronacher.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
import imp
|
|
||||||
import os
|
|
||||||
import errno
|
|
||||||
|
|
||||||
from werkzeug.utils import import_string
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigAttribute(object):
|
|
||||||
"""Makes an attribute forward to the config"""
|
|
||||||
|
|
||||||
def __init__(self, name, get_converter=None):
|
|
||||||
self.__name__ = name
|
|
||||||
self.get_converter = get_converter
|
|
||||||
|
|
||||||
def __get__(self, obj, type=None):
|
|
||||||
if obj is None:
|
|
||||||
return self
|
|
||||||
rv = obj.config[self.__name__]
|
|
||||||
if self.get_converter is not None:
|
|
||||||
rv = self.get_converter(rv)
|
|
||||||
return rv
|
|
||||||
|
|
||||||
def __set__(self, obj, value):
|
|
||||||
obj.config[self.__name__] = value
|
|
||||||
|
|
||||||
|
|
||||||
class Config(dict):
|
|
||||||
"""Works exactly like a dict but provides ways to fill it from files
|
|
||||||
or special dictionaries. There are two common patterns to populate the
|
|
||||||
config.
|
|
||||||
|
|
||||||
Either you can fill the config from a config file::
|
|
||||||
|
|
||||||
app.config.from_pyfile('yourconfig.cfg')
|
|
||||||
|
|
||||||
Or alternatively you can define the configuration options in the
|
|
||||||
module that calls :meth:`from_object` or provide an import path to
|
|
||||||
a module that should be loaded. It is also possible to tell it to
|
|
||||||
use the same module and with that provide the configuration values
|
|
||||||
just before the call::
|
|
||||||
|
|
||||||
DEBUG = True
|
|
||||||
SECRET_KEY = 'development key'
|
|
||||||
app.config.from_object(__name__)
|
|
||||||
|
|
||||||
In both cases (loading from any Python file or loading from modules),
|
|
||||||
only uppercase keys are added to the config. This makes it possible to use
|
|
||||||
lowercase values in the config file for temporary values that are not added
|
|
||||||
to the config or to define the config keys in the same file that implements
|
|
||||||
the application.
|
|
||||||
|
|
||||||
Probably the most interesting way to load configurations is from an
|
|
||||||
environment variable pointing to a file::
|
|
||||||
|
|
||||||
app.config.from_envvar('YOURAPPLICATION_SETTINGS')
|
|
||||||
|
|
||||||
In this case before launching the application you have to set this
|
|
||||||
environment variable to the file you want to use. On Linux and OS X
|
|
||||||
use the export statement::
|
|
||||||
|
|
||||||
export YOURAPPLICATION_SETTINGS='/path/to/config/file'
|
|
||||||
|
|
||||||
On windows use `set` instead.
|
|
||||||
|
|
||||||
:param root_path: path to which files are read relative from. When the
|
|
||||||
config object is created by the application, this is
|
|
||||||
the application's :attr:`~flask.Flask.root_path`.
|
|
||||||
:param defaults: an optional dictionary of default values
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, root_path, defaults=None):
|
|
||||||
dict.__init__(self, defaults or {})
|
|
||||||
self.root_path = root_path
|
|
||||||
|
|
||||||
def from_envvar(self, variable_name, silent=False):
|
|
||||||
"""Loads a configuration from an environment variable pointing to
|
|
||||||
a configuration file. This is basically just a shortcut with nicer
|
|
||||||
error messages for this line of code::
|
|
||||||
|
|
||||||
app.config.from_pyfile(os.environ['YOURAPPLICATION_SETTINGS'])
|
|
||||||
|
|
||||||
:param variable_name: name of the environment variable
|
|
||||||
:param silent: set to `True` if you want silent failure for missing
|
|
||||||
files.
|
|
||||||
:return: bool. `True` if able to load config, `False` otherwise.
|
|
||||||
"""
|
|
||||||
rv = os.environ.get(variable_name)
|
|
||||||
if not rv:
|
|
||||||
if silent:
|
|
||||||
return False
|
|
||||||
raise RuntimeError('The environment variable %r is not set '
|
|
||||||
'and as such configuration could not be '
|
|
||||||
'loaded. Set this variable and make it '
|
|
||||||
'point to a configuration file' %
|
|
||||||
variable_name)
|
|
||||||
return self.from_pyfile(rv, silent=silent)
|
|
||||||
|
|
||||||
def from_pyfile(self, filename, silent=False):
|
|
||||||
"""Updates the values in the config from a Python file. This function
|
|
||||||
behaves as if the file was imported as module with the
|
|
||||||
:meth:`from_object` function.
|
|
||||||
|
|
||||||
:param filename: the filename of the config. This can either be an
|
|
||||||
absolute filename or a filename relative to the
|
|
||||||
root path.
|
|
||||||
:param silent: set to `True` if you want silent failure for missing
|
|
||||||
files.
|
|
||||||
|
|
||||||
.. versionadded:: 0.7
|
|
||||||
`silent` parameter.
|
|
||||||
"""
|
|
||||||
filename = os.path.join(self.root_path, filename)
|
|
||||||
d = imp.new_module('config')
|
|
||||||
d.__file__ = filename
|
|
||||||
try:
|
|
||||||
execfile(filename, d.__dict__)
|
|
||||||
except IOError, e:
|
|
||||||
if silent and e.errno in (errno.ENOENT, errno.EISDIR):
|
|
||||||
return False
|
|
||||||
e.strerror = 'Unable to load configuration file (%s)' % e.strerror
|
|
||||||
raise
|
|
||||||
self.from_object(d)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def from_object(self, obj):
|
|
||||||
"""Updates the values from the given object. An object can be of one
|
|
||||||
of the following two types:
|
|
||||||
|
|
||||||
- a string: in this case the object with that name will be imported
|
|
||||||
- an actual object reference: that object is used directly
|
|
||||||
|
|
||||||
Objects are usually either modules or classes.
|
|
||||||
|
|
||||||
Just the uppercase variables in that object are stored in the config.
|
|
||||||
Example usage::
|
|
||||||
|
|
||||||
app.config.from_object('yourapplication.default_config')
|
|
||||||
from yourapplication import default_config
|
|
||||||
app.config.from_object(default_config)
|
|
||||||
|
|
||||||
You should not use this function to load the actual configuration but
|
|
||||||
rather configuration defaults. The actual config should be loaded
|
|
||||||
with :meth:`from_pyfile` and ideally from a location not within the
|
|
||||||
package because the package might be installed system wide.
|
|
||||||
|
|
||||||
:param obj: an import name or object
|
|
||||||
"""
|
|
||||||
if isinstance(obj, basestring):
|
|
||||||
obj = import_string(obj)
|
|
||||||
for key in dir(obj):
|
|
||||||
if key.isupper():
|
|
||||||
self[key] = getattr(obj, key)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<%s %s>' % (self.__class__.__name__, dict.__repr__(self))
|
|
||||||
@@ -1,295 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.ctx
|
|
||||||
~~~~~~~~~
|
|
||||||
|
|
||||||
Implements the objects required to keep the context.
|
|
||||||
|
|
||||||
:copyright: (c) 2011 by Armin Ronacher.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from werkzeug.exceptions import HTTPException
|
|
||||||
|
|
||||||
from .globals import _request_ctx_stack, _app_ctx_stack
|
|
||||||
from .module import blueprint_is_module
|
|
||||||
|
|
||||||
|
|
||||||
class _RequestGlobals(object):
|
|
||||||
"""A plain object."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def after_this_request(f):
|
|
||||||
"""Executes a function after this request. This is useful to modify
|
|
||||||
response objects. The function is passed the response object and has
|
|
||||||
to return the same or a new one.
|
|
||||||
|
|
||||||
Example::
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def index():
|
|
||||||
@after_this_request
|
|
||||||
def add_header(response):
|
|
||||||
response.headers['X-Foo'] = 'Parachute'
|
|
||||||
return response
|
|
||||||
return 'Hello World!'
|
|
||||||
|
|
||||||
This is more useful if a function other than the view function wants to
|
|
||||||
modify a response. For instance think of a decorator that wants to add
|
|
||||||
some headers without converting the return value into a response object.
|
|
||||||
|
|
||||||
.. versionadded:: 0.9
|
|
||||||
"""
|
|
||||||
_request_ctx_stack.top._after_request_functions.append(f)
|
|
||||||
return f
|
|
||||||
|
|
||||||
|
|
||||||
def has_request_context():
|
|
||||||
"""If you have code that wants to test if a request context is there or
|
|
||||||
not this function can be used. For instance, you may want to take advantage
|
|
||||||
of request information if the request object is available, but fail
|
|
||||||
silently if it is unavailable.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
class User(db.Model):
|
|
||||||
|
|
||||||
def __init__(self, username, remote_addr=None):
|
|
||||||
self.username = username
|
|
||||||
if remote_addr is None and has_request_context():
|
|
||||||
remote_addr = request.remote_addr
|
|
||||||
self.remote_addr = remote_addr
|
|
||||||
|
|
||||||
Alternatively you can also just test any of the context bound objects
|
|
||||||
(such as :class:`request` or :class:`g` for truthness)::
|
|
||||||
|
|
||||||
class User(db.Model):
|
|
||||||
|
|
||||||
def __init__(self, username, remote_addr=None):
|
|
||||||
self.username = username
|
|
||||||
if remote_addr is None and request:
|
|
||||||
remote_addr = request.remote_addr
|
|
||||||
self.remote_addr = remote_addr
|
|
||||||
|
|
||||||
.. versionadded:: 0.7
|
|
||||||
"""
|
|
||||||
return _request_ctx_stack.top is not None
|
|
||||||
|
|
||||||
|
|
||||||
def has_app_context():
|
|
||||||
"""Works like :func:`has_request_context` but for the application
|
|
||||||
context. You can also just do a boolean check on the
|
|
||||||
:data:`current_app` object instead.
|
|
||||||
|
|
||||||
.. versionadded:: 0.9
|
|
||||||
"""
|
|
||||||
return _app_ctx_stack.top is not None
|
|
||||||
|
|
||||||
|
|
||||||
class AppContext(object):
|
|
||||||
"""The application context binds an application object implicitly
|
|
||||||
to the current thread or greenlet, similar to how the
|
|
||||||
:class:`RequestContext` binds request information. The application
|
|
||||||
context is also implicitly created if a request context is created
|
|
||||||
but the application is not on top of the individual application
|
|
||||||
context.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, app):
|
|
||||||
self.app = app
|
|
||||||
self.url_adapter = app.create_url_adapter(None)
|
|
||||||
|
|
||||||
# Like request context, app contexts can be pushed multiple times
|
|
||||||
# but there a basic "refcount" is enough to track them.
|
|
||||||
self._refcnt = 0
|
|
||||||
|
|
||||||
def push(self):
|
|
||||||
"""Binds the app context to the current context."""
|
|
||||||
self._refcnt += 1
|
|
||||||
_app_ctx_stack.push(self)
|
|
||||||
|
|
||||||
def pop(self, exc=None):
|
|
||||||
"""Pops the app context."""
|
|
||||||
self._refcnt -= 1
|
|
||||||
if self._refcnt <= 0:
|
|
||||||
if exc is None:
|
|
||||||
exc = sys.exc_info()[1]
|
|
||||||
self.app.do_teardown_appcontext(exc)
|
|
||||||
rv = _app_ctx_stack.pop()
|
|
||||||
assert rv is self, 'Popped wrong app context. (%r instead of %r)' \
|
|
||||||
% (rv, self)
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
self.push()
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, tb):
|
|
||||||
self.pop(exc_value)
|
|
||||||
|
|
||||||
|
|
||||||
class RequestContext(object):
|
|
||||||
"""The request context contains all request relevant information. It is
|
|
||||||
created at the beginning of the request and pushed to the
|
|
||||||
`_request_ctx_stack` and removed at the end of it. It will create the
|
|
||||||
URL adapter and request object for the WSGI environment provided.
|
|
||||||
|
|
||||||
Do not attempt to use this class directly, instead use
|
|
||||||
:meth:`~flask.Flask.test_request_context` and
|
|
||||||
:meth:`~flask.Flask.request_context` to create this object.
|
|
||||||
|
|
||||||
When the request context is popped, it will evaluate all the
|
|
||||||
functions registered on the application for teardown execution
|
|
||||||
(:meth:`~flask.Flask.teardown_request`).
|
|
||||||
|
|
||||||
The request context is automatically popped at the end of the request
|
|
||||||
for you. In debug mode the request context is kept around if
|
|
||||||
exceptions happen so that interactive debuggers have a chance to
|
|
||||||
introspect the data. With 0.4 this can also be forced for requests
|
|
||||||
that did not fail and outside of `DEBUG` mode. By setting
|
|
||||||
``'flask._preserve_context'`` to `True` on the WSGI environment the
|
|
||||||
context will not pop itself at the end of the request. This is used by
|
|
||||||
the :meth:`~flask.Flask.test_client` for example to implement the
|
|
||||||
deferred cleanup functionality.
|
|
||||||
|
|
||||||
You might find this helpful for unittests where you need the
|
|
||||||
information from the context local around for a little longer. Make
|
|
||||||
sure to properly :meth:`~werkzeug.LocalStack.pop` the stack yourself in
|
|
||||||
that situation, otherwise your unittests will leak memory.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, app, environ):
|
|
||||||
self.app = app
|
|
||||||
self.request = app.request_class(environ)
|
|
||||||
self.url_adapter = app.create_url_adapter(self.request)
|
|
||||||
self.g = app.request_globals_class()
|
|
||||||
self.flashes = None
|
|
||||||
self.session = None
|
|
||||||
|
|
||||||
# Request contexts can be pushed multiple times and interleaved with
|
|
||||||
# other request contexts. Now only if the last level is popped we
|
|
||||||
# get rid of them. Additionally if an application context is missing
|
|
||||||
# one is created implicitly so for each level we add this information
|
|
||||||
self._implicit_app_ctx_stack = []
|
|
||||||
|
|
||||||
# indicator if the context was preserved. Next time another context
|
|
||||||
# is pushed the preserved context is popped.
|
|
||||||
self.preserved = False
|
|
||||||
|
|
||||||
# Functions that should be executed after the request on the response
|
|
||||||
# object. These will be called before the regular "after_request"
|
|
||||||
# functions.
|
|
||||||
self._after_request_functions = []
|
|
||||||
|
|
||||||
self.match_request()
|
|
||||||
|
|
||||||
# XXX: Support for deprecated functionality. This is going away with
|
|
||||||
# Flask 1.0
|
|
||||||
blueprint = self.request.blueprint
|
|
||||||
if blueprint is not None:
|
|
||||||
# better safe than sorry, we don't want to break code that
|
|
||||||
# already worked
|
|
||||||
bp = app.blueprints.get(blueprint)
|
|
||||||
if bp is not None and blueprint_is_module(bp):
|
|
||||||
self.request._is_old_module = True
|
|
||||||
|
|
||||||
def match_request(self):
|
|
||||||
"""Can be overridden by a subclass to hook into the matching
|
|
||||||
of the request.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
url_rule, self.request.view_args = \
|
|
||||||
self.url_adapter.match(return_rule=True)
|
|
||||||
self.request.url_rule = url_rule
|
|
||||||
except HTTPException, e:
|
|
||||||
self.request.routing_exception = e
|
|
||||||
|
|
||||||
def push(self):
|
|
||||||
"""Binds the request context to the current context."""
|
|
||||||
# If an exception ocurrs in debug mode or if context preservation is
|
|
||||||
# activated under exception situations exactly one context stays
|
|
||||||
# on the stack. The rationale is that you want to access that
|
|
||||||
# information under debug situations. However if someone forgets to
|
|
||||||
# pop that context again we want to make sure that on the next push
|
|
||||||
# it's invalidated otherwise we run at risk that something leaks
|
|
||||||
# memory. This is usually only a problem in testsuite since this
|
|
||||||
# functionality is not active in production environments.
|
|
||||||
top = _request_ctx_stack.top
|
|
||||||
if top is not None and top.preserved:
|
|
||||||
top.pop()
|
|
||||||
|
|
||||||
# Before we push the request context we have to ensure that there
|
|
||||||
# is an application context.
|
|
||||||
app_ctx = _app_ctx_stack.top
|
|
||||||
if app_ctx is None or app_ctx.app != self.app:
|
|
||||||
app_ctx = self.app.app_context()
|
|
||||||
app_ctx.push()
|
|
||||||
self._implicit_app_ctx_stack.append(app_ctx)
|
|
||||||
else:
|
|
||||||
self._implicit_app_ctx_stack.append(None)
|
|
||||||
|
|
||||||
_request_ctx_stack.push(self)
|
|
||||||
|
|
||||||
# Open the session at the moment that the request context is
|
|
||||||
# available. This allows a custom open_session method to use the
|
|
||||||
# request context (e.g. flask-sqlalchemy).
|
|
||||||
self.session = self.app.open_session(self.request)
|
|
||||||
if self.session is None:
|
|
||||||
self.session = self.app.make_null_session()
|
|
||||||
|
|
||||||
def pop(self, exc=None):
|
|
||||||
"""Pops the request context and unbinds it by doing that. This will
|
|
||||||
also trigger the execution of functions registered by the
|
|
||||||
:meth:`~flask.Flask.teardown_request` decorator.
|
|
||||||
|
|
||||||
.. versionchanged:: 0.9
|
|
||||||
Added the `exc` argument.
|
|
||||||
"""
|
|
||||||
app_ctx = self._implicit_app_ctx_stack.pop()
|
|
||||||
|
|
||||||
clear_request = False
|
|
||||||
if not self._implicit_app_ctx_stack:
|
|
||||||
self.preserved = False
|
|
||||||
if exc is None:
|
|
||||||
exc = sys.exc_info()[1]
|
|
||||||
self.app.do_teardown_request(exc)
|
|
||||||
clear_request = True
|
|
||||||
|
|
||||||
rv = _request_ctx_stack.pop()
|
|
||||||
assert rv is self, 'Popped wrong request context. (%r instead of %r)' \
|
|
||||||
% (rv, self)
|
|
||||||
|
|
||||||
# get rid of circular dependencies at the end of the request
|
|
||||||
# so that we don't require the GC to be active.
|
|
||||||
if clear_request:
|
|
||||||
rv.request.environ['werkzeug.request'] = None
|
|
||||||
|
|
||||||
# Get rid of the app as well if necessary.
|
|
||||||
if app_ctx is not None:
|
|
||||||
app_ctx.pop(exc)
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
self.push()
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, tb):
|
|
||||||
# do not pop the request stack if we are in debug mode and an
|
|
||||||
# exception happened. This will allow the debugger to still
|
|
||||||
# access the request object in the interactive shell. Furthermore
|
|
||||||
# the context can be force kept alive for the test client.
|
|
||||||
# See flask.testing for how this works.
|
|
||||||
if self.request.environ.get('flask._preserve_context') or \
|
|
||||||
(tb is not None and self.app.preserve_context_on_exception):
|
|
||||||
self.preserved = True
|
|
||||||
else:
|
|
||||||
self.pop(exc_value)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<%s \'%s\' [%s] of %s>' % (
|
|
||||||
self.__class__.__name__,
|
|
||||||
self.request.url,
|
|
||||||
self.request.method,
|
|
||||||
self.app.name
|
|
||||||
)
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.debughelpers
|
|
||||||
~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Various helpers to make the development experience better.
|
|
||||||
|
|
||||||
:copyright: (c) 2011 by Armin Ronacher.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class DebugFilesKeyError(KeyError, AssertionError):
|
|
||||||
"""Raised from request.files during debugging. The idea is that it can
|
|
||||||
provide a better error message than just a generic KeyError/BadRequest.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, request, key):
|
|
||||||
form_matches = request.form.getlist(key)
|
|
||||||
buf = ['You tried to access the file "%s" in the request.files '
|
|
||||||
'dictionary but it does not exist. The mimetype for the request '
|
|
||||||
'is "%s" instead of "multipart/form-data" which means that no '
|
|
||||||
'file contents were transmitted. To fix this error you should '
|
|
||||||
'provide enctype="multipart/form-data" in your form.' %
|
|
||||||
(key, request.mimetype)]
|
|
||||||
if form_matches:
|
|
||||||
buf.append('\n\nThe browser instead transmitted some file names. '
|
|
||||||
'This was submitted: %s' % ', '.join('"%s"' % x
|
|
||||||
for x in form_matches))
|
|
||||||
self.msg = ''.join(buf).encode('utf-8')
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.msg
|
|
||||||
|
|
||||||
|
|
||||||
class FormDataRoutingRedirect(AssertionError):
|
|
||||||
"""This exception is raised by Flask in debug mode if it detects a
|
|
||||||
redirect caused by the routing system when the request method is not
|
|
||||||
GET, HEAD or OPTIONS. Reasoning: form data will be dropped.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, request):
|
|
||||||
exc = request.routing_exception
|
|
||||||
buf = ['A request was sent to this URL (%s) but a redirect was '
|
|
||||||
'issued automatically by the routing system to "%s".'
|
|
||||||
% (request.url, exc.new_url)]
|
|
||||||
|
|
||||||
# In case just a slash was appended we can be extra helpful
|
|
||||||
if request.base_url + '/' == exc.new_url.split('?')[0]:
|
|
||||||
buf.append(' The URL was defined with a trailing slash so '
|
|
||||||
'Flask will automatically redirect to the URL '
|
|
||||||
'with the trailing slash if it was accessed '
|
|
||||||
'without one.')
|
|
||||||
|
|
||||||
buf.append(' Make sure to directly send your %s-request to this URL '
|
|
||||||
'since we can\'t make browsers or HTTP clients redirect '
|
|
||||||
'with form data reliably or without user interaction.' %
|
|
||||||
request.method)
|
|
||||||
buf.append('\n\nNote: this exception is only raised in debug mode')
|
|
||||||
AssertionError.__init__(self, ''.join(buf).encode('utf-8'))
|
|
||||||
|
|
||||||
|
|
||||||
def attach_enctype_error_multidict(request):
|
|
||||||
"""Since Flask 0.8 we're monkeypatching the files object in case a
|
|
||||||
request is detected that does not use multipart form data but the files
|
|
||||||
object is accessed.
|
|
||||||
"""
|
|
||||||
oldcls = request.files.__class__
|
|
||||||
class newcls(oldcls):
|
|
||||||
def __getitem__(self, key):
|
|
||||||
try:
|
|
||||||
return oldcls.__getitem__(self, key)
|
|
||||||
except KeyError, e:
|
|
||||||
if key not in request.form:
|
|
||||||
raise
|
|
||||||
raise DebugFilesKeyError(request, key)
|
|
||||||
newcls.__name__ = oldcls.__name__
|
|
||||||
newcls.__module__ = oldcls.__module__
|
|
||||||
request.files.__class__ = newcls
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.exceptions
|
|
||||||
~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Flask specific additions to :class:`~werkzeug.exceptions.HTTPException`
|
|
||||||
|
|
||||||
:copyright: (c) 2011 by Armin Ronacher.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
from werkzeug.exceptions import HTTPException, BadRequest
|
|
||||||
from .helpers import json
|
|
||||||
|
|
||||||
|
|
||||||
class JSONHTTPException(HTTPException):
|
|
||||||
"""A base class for HTTP exceptions with ``Content-Type:
|
|
||||||
application/json``.
|
|
||||||
|
|
||||||
The ``description`` attribute of this class must set to a string (*not* an
|
|
||||||
HTML string) which describes the error.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get_body(self, environ):
|
|
||||||
"""Overrides :meth:`werkzeug.exceptions.HTTPException.get_body` to
|
|
||||||
return the description of this error in JSON format instead of HTML.
|
|
||||||
|
|
||||||
"""
|
|
||||||
return json.dumps(dict(description=self.get_description(environ)))
|
|
||||||
|
|
||||||
def get_headers(self, environ):
|
|
||||||
"""Returns a list of headers including ``Content-Type:
|
|
||||||
application/json``.
|
|
||||||
|
|
||||||
"""
|
|
||||||
return [('Content-Type', 'application/json')]
|
|
||||||
|
|
||||||
|
|
||||||
class JSONBadRequest(JSONHTTPException, BadRequest):
|
|
||||||
"""Represents an HTTP ``400 Bad Request`` error whose body contains an
|
|
||||||
error message in JSON format instead of HTML format (as in the superclass).
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
#: The description of the error which occurred as a string.
|
|
||||||
description = (
|
|
||||||
'The browser (or proxy) sent a request that this server could not '
|
|
||||||
'understand.'
|
|
||||||
)
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.ext
|
|
||||||
~~~~~~~~~
|
|
||||||
|
|
||||||
Redirect imports for extensions. This module basically makes it possible
|
|
||||||
for us to transition from flaskext.foo to flask_foo without having to
|
|
||||||
force all extensions to upgrade at the same time.
|
|
||||||
|
|
||||||
When a user does ``from flask.ext.foo import bar`` it will attempt to
|
|
||||||
import ``from flask_foo import bar`` first and when that fails it will
|
|
||||||
try to import ``from flaskext.foo import bar``.
|
|
||||||
|
|
||||||
We're switching from namespace packages because it was just too painful for
|
|
||||||
everybody involved.
|
|
||||||
|
|
||||||
:copyright: (c) 2011 by Armin Ronacher.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def setup():
|
|
||||||
from ..exthook import ExtensionImporter
|
|
||||||
importer = ExtensionImporter(['flask_%s', 'flaskext.%s'], __name__)
|
|
||||||
importer.install()
|
|
||||||
|
|
||||||
|
|
||||||
setup()
|
|
||||||
del setup
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.exthook
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Redirect imports for extensions. This module basically makes it possible
|
|
||||||
for us to transition from flaskext.foo to flask_foo without having to
|
|
||||||
force all extensions to upgrade at the same time.
|
|
||||||
|
|
||||||
When a user does ``from flask.ext.foo import bar`` it will attempt to
|
|
||||||
import ``from flask_foo import bar`` first and when that fails it will
|
|
||||||
try to import ``from flaskext.foo import bar``.
|
|
||||||
|
|
||||||
We're switching from namespace packages because it was just too painful for
|
|
||||||
everybody involved.
|
|
||||||
|
|
||||||
This is used by `flask.ext`.
|
|
||||||
|
|
||||||
:copyright: (c) 2011 by Armin Ronacher.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
class ExtensionImporter(object):
|
|
||||||
"""This importer redirects imports from this submodule to other locations.
|
|
||||||
This makes it possible to transition from the old flaskext.name to the
|
|
||||||
newer flask_name without people having a hard time.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, module_choices, wrapper_module):
|
|
||||||
self.module_choices = module_choices
|
|
||||||
self.wrapper_module = wrapper_module
|
|
||||||
self.prefix = wrapper_module + '.'
|
|
||||||
self.prefix_cutoff = wrapper_module.count('.') + 1
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return self.__class__.__module__ == other.__class__.__module__ and \
|
|
||||||
self.__class__.__name__ == other.__class__.__name__ and \
|
|
||||||
self.wrapper_module == other.wrapper_module and \
|
|
||||||
self.module_choices == other.module_choices
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return not self.__eq__(other)
|
|
||||||
|
|
||||||
def install(self):
|
|
||||||
sys.meta_path[:] = [x for x in sys.meta_path if self != x] + [self]
|
|
||||||
|
|
||||||
def find_module(self, fullname, path=None):
|
|
||||||
if fullname.startswith(self.prefix):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def load_module(self, fullname):
|
|
||||||
if fullname in sys.modules:
|
|
||||||
return sys.modules[fullname]
|
|
||||||
modname = fullname.split('.', self.prefix_cutoff)[self.prefix_cutoff]
|
|
||||||
for path in self.module_choices:
|
|
||||||
realname = path % modname
|
|
||||||
try:
|
|
||||||
__import__(realname)
|
|
||||||
except ImportError:
|
|
||||||
exc_type, exc_value, tb = sys.exc_info()
|
|
||||||
# since we only establish the entry in sys.modules at the
|
|
||||||
# very this seems to be redundant, but if recursive imports
|
|
||||||
# happen we will call into the move import a second time.
|
|
||||||
# On the second invocation we still don't have an entry for
|
|
||||||
# fullname in sys.modules, but we will end up with the same
|
|
||||||
# fake module name and that import will succeed since this
|
|
||||||
# one already has a temporary entry in the modules dict.
|
|
||||||
# Since this one "succeeded" temporarily that second
|
|
||||||
# invocation now will have created a fullname entry in
|
|
||||||
# sys.modules which we have to kill.
|
|
||||||
sys.modules.pop(fullname, None)
|
|
||||||
|
|
||||||
# If it's an important traceback we reraise it, otherwise
|
|
||||||
# we swallow it and try the next choice. The skipped frame
|
|
||||||
# is the one from __import__ above which we don't care about
|
|
||||||
if self.is_important_traceback(realname, tb):
|
|
||||||
raise exc_type, exc_value, tb.tb_next
|
|
||||||
continue
|
|
||||||
module = sys.modules[fullname] = sys.modules[realname]
|
|
||||||
if '.' not in modname:
|
|
||||||
setattr(sys.modules[self.wrapper_module], modname, module)
|
|
||||||
return module
|
|
||||||
raise ImportError('No module named %s' % fullname)
|
|
||||||
|
|
||||||
def is_important_traceback(self, important_module, tb):
|
|
||||||
"""Walks a traceback's frames and checks if any of the frames
|
|
||||||
originated in the given important module. If that is the case then we
|
|
||||||
were able to import the module itself but apparently something went
|
|
||||||
wrong when the module was imported. (Eg: import of an import failed).
|
|
||||||
"""
|
|
||||||
while tb is not None:
|
|
||||||
if self.is_important_frame(important_module, tb):
|
|
||||||
return True
|
|
||||||
tb = tb.tb_next
|
|
||||||
return False
|
|
||||||
|
|
||||||
def is_important_frame(self, important_module, tb):
|
|
||||||
"""Checks a single frame if it's important."""
|
|
||||||
g = tb.tb_frame.f_globals
|
|
||||||
if '__name__' not in g:
|
|
||||||
return False
|
|
||||||
|
|
||||||
module_name = g['__name__']
|
|
||||||
|
|
||||||
# Python 2.7 Behavior. Modules are cleaned up late so the
|
|
||||||
# name shows up properly here. Success!
|
|
||||||
if module_name == important_module:
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Some python verisons will will clean up modules so early that the
|
|
||||||
# module name at that point is no longer set. Try guessing from
|
|
||||||
# the filename then.
|
|
||||||
filename = os.path.abspath(tb.tb_frame.f_code.co_filename)
|
|
||||||
test_string = os.path.sep + important_module.replace('.', os.path.sep)
|
|
||||||
return test_string + '.py' in filename or \
|
|
||||||
test_string + os.path.sep + '__init__.py' in filename
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.globals
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Defines all the global objects that are proxies to the current
|
|
||||||
active context.
|
|
||||||
|
|
||||||
:copyright: (c) 2011 by Armin Ronacher.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from functools import partial
|
|
||||||
from werkzeug.local import LocalStack, LocalProxy
|
|
||||||
|
|
||||||
def _lookup_object(name):
|
|
||||||
top = _request_ctx_stack.top
|
|
||||||
if top is None:
|
|
||||||
raise RuntimeError('working outside of request context')
|
|
||||||
return getattr(top, name)
|
|
||||||
|
|
||||||
|
|
||||||
def _find_app():
|
|
||||||
top = _app_ctx_stack.top
|
|
||||||
if top is None:
|
|
||||||
raise RuntimeError('working outside of application context')
|
|
||||||
return top.app
|
|
||||||
|
|
||||||
|
|
||||||
# context locals
|
|
||||||
_request_ctx_stack = LocalStack()
|
|
||||||
_app_ctx_stack = LocalStack()
|
|
||||||
current_app = LocalProxy(_find_app)
|
|
||||||
request = LocalProxy(partial(_lookup_object, 'request'))
|
|
||||||
session = LocalProxy(partial(_lookup_object, 'session'))
|
|
||||||
g = LocalProxy(partial(_lookup_object, 'g'))
|
|
||||||
@@ -1,893 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.helpers
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Implements various helpers.
|
|
||||||
|
|
||||||
:copyright: (c) 2011 by Armin Ronacher.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import pkgutil
|
|
||||||
import posixpath
|
|
||||||
import mimetypes
|
|
||||||
from time import time
|
|
||||||
from zlib import adler32
|
|
||||||
from threading import RLock
|
|
||||||
from werkzeug.routing import BuildError
|
|
||||||
from werkzeug.urls import url_quote
|
|
||||||
from functools import update_wrapper
|
|
||||||
|
|
||||||
# try to load the best simplejson implementation available. If JSON
|
|
||||||
# is not installed, we add a failing class.
|
|
||||||
json_available = True
|
|
||||||
json = None
|
|
||||||
try:
|
|
||||||
import simplejson as json
|
|
||||||
except ImportError:
|
|
||||||
try:
|
|
||||||
import json
|
|
||||||
except ImportError:
|
|
||||||
try:
|
|
||||||
# Google Appengine offers simplejson via django
|
|
||||||
from django.utils import simplejson as json
|
|
||||||
except ImportError:
|
|
||||||
json_available = False
|
|
||||||
|
|
||||||
|
|
||||||
from werkzeug.datastructures import Headers
|
|
||||||
from werkzeug.exceptions import NotFound
|
|
||||||
|
|
||||||
# this was moved in 0.7
|
|
||||||
try:
|
|
||||||
from werkzeug.wsgi import wrap_file
|
|
||||||
except ImportError:
|
|
||||||
from werkzeug.utils import wrap_file
|
|
||||||
|
|
||||||
from jinja2 import FileSystemLoader
|
|
||||||
|
|
||||||
from .globals import session, _request_ctx_stack, _app_ctx_stack, \
|
|
||||||
current_app, request
|
|
||||||
|
|
||||||
|
|
||||||
def _assert_have_json():
|
|
||||||
"""Helper function that fails if JSON is unavailable."""
|
|
||||||
if not json_available:
|
|
||||||
raise RuntimeError('simplejson not installed')
|
|
||||||
|
|
||||||
|
|
||||||
# figure out if simplejson escapes slashes. This behavior was changed
|
|
||||||
# from one version to another without reason.
|
|
||||||
if not json_available or '\\/' not in json.dumps('/'):
|
|
||||||
|
|
||||||
def _tojson_filter(*args, **kwargs):
|
|
||||||
if __debug__:
|
|
||||||
_assert_have_json()
|
|
||||||
return json.dumps(*args, **kwargs).replace('/', '\\/')
|
|
||||||
else:
|
|
||||||
_tojson_filter = json.dumps
|
|
||||||
|
|
||||||
|
|
||||||
# sentinel
|
|
||||||
_missing = object()
|
|
||||||
|
|
||||||
|
|
||||||
# what separators does this operating system provide that are not a slash?
|
|
||||||
# this is used by the send_from_directory function to ensure that nobody is
|
|
||||||
# able to access files from outside the filesystem.
|
|
||||||
_os_alt_seps = list(sep for sep in [os.path.sep, os.path.altsep]
|
|
||||||
if sep not in (None, '/'))
|
|
||||||
|
|
||||||
|
|
||||||
def _endpoint_from_view_func(view_func):
|
|
||||||
"""Internal helper that returns the default endpoint for a given
|
|
||||||
function. This always is the function name.
|
|
||||||
"""
|
|
||||||
assert view_func is not None, 'expected view func if endpoint ' \
|
|
||||||
'is not provided.'
|
|
||||||
return view_func.__name__
|
|
||||||
|
|
||||||
|
|
||||||
def stream_with_context(generator_or_function):
|
|
||||||
"""Request contexts disappear when the response is started on the server.
|
|
||||||
This is done for efficiency reasons and to make it less likely to encounter
|
|
||||||
memory leaks with badly written WSGI middlewares. The downside is that if
|
|
||||||
you are using streamed responses, the generator cannot access request bound
|
|
||||||
information any more.
|
|
||||||
|
|
||||||
This function however can help you keep the context around for longer::
|
|
||||||
|
|
||||||
from flask import stream_with_context, request, Response
|
|
||||||
|
|
||||||
@app.route('/stream')
|
|
||||||
def streamed_response():
|
|
||||||
@stream_with_context
|
|
||||||
def generate():
|
|
||||||
yield 'Hello '
|
|
||||||
yield request.args['name']
|
|
||||||
yield '!'
|
|
||||||
return Response(generate())
|
|
||||||
|
|
||||||
Alternatively it can also be used around a specific generator:
|
|
||||||
|
|
||||||
from flask import stream_with_context, request, Response
|
|
||||||
|
|
||||||
@app.route('/stream')
|
|
||||||
def streamed_response():
|
|
||||||
def generate():
|
|
||||||
yield 'Hello '
|
|
||||||
yield request.args['name']
|
|
||||||
yield '!'
|
|
||||||
return Response(stream_with_context(generate()))
|
|
||||||
|
|
||||||
.. versionadded:: 0.9
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
gen = iter(generator_or_function)
|
|
||||||
except TypeError:
|
|
||||||
def decorator(*args, **kwargs):
|
|
||||||
gen = generator_or_function()
|
|
||||||
return stream_with_context(gen)
|
|
||||||
return update_wrapper(decorator, generator_or_function)
|
|
||||||
|
|
||||||
def generator():
|
|
||||||
ctx = _request_ctx_stack.top
|
|
||||||
if ctx is None:
|
|
||||||
raise RuntimeError('Attempted to stream with context but '
|
|
||||||
'there was no context in the first place to keep around.')
|
|
||||||
with ctx:
|
|
||||||
# Dummy sentinel. Has to be inside the context block or we're
|
|
||||||
# not actually keeping the context around.
|
|
||||||
yield None
|
|
||||||
|
|
||||||
# The try/finally is here so that if someone passes a WSGI level
|
|
||||||
# iterator in we're still running the cleanup logic. Generators
|
|
||||||
# don't need that because they are closed on their destruction
|
|
||||||
# automatically.
|
|
||||||
try:
|
|
||||||
for item in gen:
|
|
||||||
yield item
|
|
||||||
finally:
|
|
||||||
if hasattr(gen, 'close'):
|
|
||||||
gen.close()
|
|
||||||
|
|
||||||
# The trick is to start the generator. Then the code execution runs until
|
|
||||||
# the first dummy None is yielded at which point the context was already
|
|
||||||
# pushed. This item is discarded. Then when the iteration continues the
|
|
||||||
# real generator is executed.
|
|
||||||
wrapped_g = generator()
|
|
||||||
wrapped_g.next()
|
|
||||||
return wrapped_g
|
|
||||||
|
|
||||||
|
|
||||||
def jsonify(*args, **kwargs):
|
|
||||||
"""Creates a :class:`~flask.Response` with the JSON representation of
|
|
||||||
the given arguments with an `application/json` mimetype. The arguments
|
|
||||||
to this function are the same as to the :class:`dict` constructor.
|
|
||||||
|
|
||||||
Example usage::
|
|
||||||
|
|
||||||
@app.route('/_get_current_user')
|
|
||||||
def get_current_user():
|
|
||||||
return jsonify(username=g.user.username,
|
|
||||||
email=g.user.email,
|
|
||||||
id=g.user.id)
|
|
||||||
|
|
||||||
This will send a JSON response like this to the browser::
|
|
||||||
|
|
||||||
{
|
|
||||||
"username": "admin",
|
|
||||||
"email": "admin@localhost",
|
|
||||||
"id": 42
|
|
||||||
}
|
|
||||||
|
|
||||||
This requires Python 2.6 or an installed version of simplejson. For
|
|
||||||
security reasons only objects are supported toplevel. For more
|
|
||||||
information about this, have a look at :ref:`json-security`.
|
|
||||||
|
|
||||||
.. versionadded:: 0.2
|
|
||||||
"""
|
|
||||||
if __debug__:
|
|
||||||
_assert_have_json()
|
|
||||||
return current_app.response_class(json.dumps(dict(*args, **kwargs),
|
|
||||||
indent=None if request.is_xhr else 2), mimetype='application/json')
|
|
||||||
|
|
||||||
|
|
||||||
def make_response(*args):
|
|
||||||
"""Sometimes it is necessary to set additional headers in a view. Because
|
|
||||||
views do not have to return response objects but can return a value that
|
|
||||||
is converted into a response object by Flask itself, it becomes tricky to
|
|
||||||
add headers to it. This function can be called instead of using a return
|
|
||||||
and you will get a response object which you can use to attach headers.
|
|
||||||
|
|
||||||
If view looked like this and you want to add a new header::
|
|
||||||
|
|
||||||
def index():
|
|
||||||
return render_template('index.html', foo=42)
|
|
||||||
|
|
||||||
You can now do something like this::
|
|
||||||
|
|
||||||
def index():
|
|
||||||
response = make_response(render_template('index.html', foo=42))
|
|
||||||
response.headers['X-Parachutes'] = 'parachutes are cool'
|
|
||||||
return response
|
|
||||||
|
|
||||||
This function accepts the very same arguments you can return from a
|
|
||||||
view function. This for example creates a response with a 404 error
|
|
||||||
code::
|
|
||||||
|
|
||||||
response = make_response(render_template('not_found.html'), 404)
|
|
||||||
|
|
||||||
The other use case of this function is to force the return value of a
|
|
||||||
view function into a response which is helpful with view
|
|
||||||
decorators::
|
|
||||||
|
|
||||||
response = make_response(view_function())
|
|
||||||
response.headers['X-Parachutes'] = 'parachutes are cool'
|
|
||||||
|
|
||||||
Internally this function does the following things:
|
|
||||||
|
|
||||||
- if no arguments are passed, it creates a new response argument
|
|
||||||
- if one argument is passed, :meth:`flask.Flask.make_response`
|
|
||||||
is invoked with it.
|
|
||||||
- if more than one argument is passed, the arguments are passed
|
|
||||||
to the :meth:`flask.Flask.make_response` function as tuple.
|
|
||||||
|
|
||||||
.. versionadded:: 0.6
|
|
||||||
"""
|
|
||||||
if not args:
|
|
||||||
return current_app.response_class()
|
|
||||||
if len(args) == 1:
|
|
||||||
args = args[0]
|
|
||||||
return current_app.make_response(args)
|
|
||||||
|
|
||||||
|
|
||||||
def url_for(endpoint, **values):
|
|
||||||
"""Generates a URL to the given endpoint with the method provided.
|
|
||||||
|
|
||||||
Variable arguments that are unknown to the target endpoint are appended
|
|
||||||
to the generated URL as query arguments. If the value of a query argument
|
|
||||||
is `None`, the whole pair is skipped. In case blueprints are active
|
|
||||||
you can shortcut references to the same blueprint by prefixing the
|
|
||||||
local endpoint with a dot (``.``).
|
|
||||||
|
|
||||||
This will reference the index function local to the current blueprint::
|
|
||||||
|
|
||||||
url_for('.index')
|
|
||||||
|
|
||||||
For more information, head over to the :ref:`Quickstart <url-building>`.
|
|
||||||
|
|
||||||
To integrate applications, :class:`Flask` has a hook to intercept URL build
|
|
||||||
errors through :attr:`Flask.build_error_handler`. The `url_for` function
|
|
||||||
results in a :exc:`~werkzeug.routing.BuildError` when the current app does
|
|
||||||
not have a URL for the given endpoint and values. When it does, the
|
|
||||||
:data:`~flask.current_app` calls its :attr:`~Flask.build_error_handler` if
|
|
||||||
it is not `None`, which can return a string to use as the result of
|
|
||||||
`url_for` (instead of `url_for`'s default to raise the
|
|
||||||
:exc:`~werkzeug.routing.BuildError` exception) or re-raise the exception.
|
|
||||||
An example::
|
|
||||||
|
|
||||||
def external_url_handler(error, endpoint, **values):
|
|
||||||
"Looks up an external URL when `url_for` cannot build a URL."
|
|
||||||
# This is an example of hooking the build_error_handler.
|
|
||||||
# Here, lookup_url is some utility function you've built
|
|
||||||
# which looks up the endpoint in some external URL registry.
|
|
||||||
url = lookup_url(endpoint, **values)
|
|
||||||
if url is None:
|
|
||||||
# External lookup did not have a URL.
|
|
||||||
# Re-raise the BuildError, in context of original traceback.
|
|
||||||
exc_type, exc_value, tb = sys.exc_info()
|
|
||||||
if exc_value is error:
|
|
||||||
raise exc_type, exc_value, tb
|
|
||||||
else:
|
|
||||||
raise error
|
|
||||||
# url_for will use this result, instead of raising BuildError.
|
|
||||||
return url
|
|
||||||
|
|
||||||
app.build_error_handler = external_url_handler
|
|
||||||
|
|
||||||
Here, `error` is the instance of :exc:`~werkzeug.routing.BuildError`, and
|
|
||||||
`endpoint` and `**values` are the arguments passed into `url_for`. Note
|
|
||||||
that this is for building URLs outside the current application, and not for
|
|
||||||
handling 404 NotFound errors.
|
|
||||||
|
|
||||||
.. versionadded:: 0.9
|
|
||||||
The `_anchor` and `_method` parameters were added.
|
|
||||||
|
|
||||||
.. versionadded:: 0.9
|
|
||||||
Calls :meth:`Flask.handle_build_error` on
|
|
||||||
:exc:`~werkzeug.routing.BuildError`.
|
|
||||||
|
|
||||||
:param endpoint: the endpoint of the URL (name of the function)
|
|
||||||
:param values: the variable arguments of the URL rule
|
|
||||||
:param _external: if set to `True`, an absolute URL is generated.
|
|
||||||
:param _anchor: if provided this is added as anchor to the URL.
|
|
||||||
:param _method: if provided this explicitly specifies an HTTP method.
|
|
||||||
"""
|
|
||||||
appctx = _app_ctx_stack.top
|
|
||||||
reqctx = _request_ctx_stack.top
|
|
||||||
if appctx is None:
|
|
||||||
raise RuntimeError('Attempted to generate a URL with the application '
|
|
||||||
'context being pushed. This has to be executed ')
|
|
||||||
|
|
||||||
# If request specific information is available we have some extra
|
|
||||||
# features that support "relative" urls.
|
|
||||||
if reqctx is not None:
|
|
||||||
url_adapter = reqctx.url_adapter
|
|
||||||
blueprint_name = request.blueprint
|
|
||||||
if not reqctx.request._is_old_module:
|
|
||||||
if endpoint[:1] == '.':
|
|
||||||
if blueprint_name is not None:
|
|
||||||
endpoint = blueprint_name + endpoint
|
|
||||||
else:
|
|
||||||
endpoint = endpoint[1:]
|
|
||||||
else:
|
|
||||||
# TODO: get rid of this deprecated functionality in 1.0
|
|
||||||
if '.' not in endpoint:
|
|
||||||
if blueprint_name is not None:
|
|
||||||
endpoint = blueprint_name + '.' + endpoint
|
|
||||||
elif endpoint.startswith('.'):
|
|
||||||
endpoint = endpoint[1:]
|
|
||||||
external = values.pop('_external', False)
|
|
||||||
|
|
||||||
# Otherwise go with the url adapter from the appctx and make
|
|
||||||
# the urls external by default.
|
|
||||||
else:
|
|
||||||
url_adapter = appctx.url_adapter
|
|
||||||
if url_adapter is None:
|
|
||||||
raise RuntimeError('Application was not able to create a URL '
|
|
||||||
'adapter for request independent URL generation. '
|
|
||||||
'You might be able to fix this by setting '
|
|
||||||
'the SERVER_NAME config variable.')
|
|
||||||
external = values.pop('_external', True)
|
|
||||||
|
|
||||||
anchor = values.pop('_anchor', None)
|
|
||||||
method = values.pop('_method', None)
|
|
||||||
appctx.app.inject_url_defaults(endpoint, values)
|
|
||||||
try:
|
|
||||||
rv = url_adapter.build(endpoint, values, method=method,
|
|
||||||
force_external=external)
|
|
||||||
except BuildError, error:
|
|
||||||
# We need to inject the values again so that the app callback can
|
|
||||||
# deal with that sort of stuff.
|
|
||||||
values['_external'] = external
|
|
||||||
values['_anchor'] = anchor
|
|
||||||
values['_method'] = method
|
|
||||||
return appctx.app.handle_url_build_error(error, endpoint, values)
|
|
||||||
|
|
||||||
rv = url_adapter.build(endpoint, values, method=method,
|
|
||||||
force_external=external)
|
|
||||||
if anchor is not None:
|
|
||||||
rv += '#' + url_quote(anchor)
|
|
||||||
return rv
|
|
||||||
|
|
||||||
|
|
||||||
def get_template_attribute(template_name, attribute):
|
|
||||||
"""Loads a macro (or variable) a template exports. This can be used to
|
|
||||||
invoke a macro from within Python code. If you for example have a
|
|
||||||
template named `_cider.html` with the following contents:
|
|
||||||
|
|
||||||
.. sourcecode:: html+jinja
|
|
||||||
|
|
||||||
{% macro hello(name) %}Hello {{ name }}!{% endmacro %}
|
|
||||||
|
|
||||||
You can access this from Python code like this::
|
|
||||||
|
|
||||||
hello = get_template_attribute('_cider.html', 'hello')
|
|
||||||
return hello('World')
|
|
||||||
|
|
||||||
.. versionadded:: 0.2
|
|
||||||
|
|
||||||
:param template_name: the name of the template
|
|
||||||
:param attribute: the name of the variable of macro to acccess
|
|
||||||
"""
|
|
||||||
return getattr(current_app.jinja_env.get_template(template_name).module,
|
|
||||||
attribute)
|
|
||||||
|
|
||||||
|
|
||||||
def flash(message, category='message'):
|
|
||||||
"""Flashes a message to the next request. In order to remove the
|
|
||||||
flashed message from the session and to display it to the user,
|
|
||||||
the template has to call :func:`get_flashed_messages`.
|
|
||||||
|
|
||||||
.. versionchanged:: 0.3
|
|
||||||
`category` parameter added.
|
|
||||||
|
|
||||||
:param message: the message to be flashed.
|
|
||||||
:param category: the category for the message. The following values
|
|
||||||
are recommended: ``'message'`` for any kind of message,
|
|
||||||
``'error'`` for errors, ``'info'`` for information
|
|
||||||
messages and ``'warning'`` for warnings. However any
|
|
||||||
kind of string can be used as category.
|
|
||||||
"""
|
|
||||||
# Original implementation:
|
|
||||||
#
|
|
||||||
# session.setdefault('_flashes', []).append((category, message))
|
|
||||||
#
|
|
||||||
# This assumed that changes made to mutable structures in the session are
|
|
||||||
# are always in sync with the sess on object, which is not true for session
|
|
||||||
# implementations that use external storage for keeping their keys/values.
|
|
||||||
flashes = session.get('_flashes', [])
|
|
||||||
flashes.append((category, message))
|
|
||||||
session['_flashes'] = flashes
|
|
||||||
|
|
||||||
|
|
||||||
def get_flashed_messages(with_categories=False, category_filter=[]):
|
|
||||||
"""Pulls all flashed messages from the session and returns them.
|
|
||||||
Further calls in the same request to the function will return
|
|
||||||
the same messages. By default just the messages are returned,
|
|
||||||
but when `with_categories` is set to `True`, the return value will
|
|
||||||
be a list of tuples in the form ``(category, message)`` instead.
|
|
||||||
|
|
||||||
Filter the flashed messages to one or more categories by providing those
|
|
||||||
categories in `category_filter`. This allows rendering categories in
|
|
||||||
separate html blocks. The `with_categories` and `category_filter`
|
|
||||||
arguments are distinct:
|
|
||||||
|
|
||||||
* `with_categories` controls whether categories are returned with message
|
|
||||||
text (`True` gives a tuple, where `False` gives just the message text).
|
|
||||||
* `category_filter` filters the messages down to only those matching the
|
|
||||||
provided categories.
|
|
||||||
|
|
||||||
See :ref:`message-flashing-pattern` for examples.
|
|
||||||
|
|
||||||
.. versionchanged:: 0.3
|
|
||||||
`with_categories` parameter added.
|
|
||||||
|
|
||||||
.. versionchanged:: 0.9
|
|
||||||
`category_filter` parameter added.
|
|
||||||
|
|
||||||
:param with_categories: set to `True` to also receive categories.
|
|
||||||
:param category_filter: whitelist of categories to limit return values
|
|
||||||
"""
|
|
||||||
flashes = _request_ctx_stack.top.flashes
|
|
||||||
if flashes is None:
|
|
||||||
_request_ctx_stack.top.flashes = flashes = session.pop('_flashes') \
|
|
||||||
if '_flashes' in session else []
|
|
||||||
if category_filter:
|
|
||||||
flashes = filter(lambda f: f[0] in category_filter, flashes)
|
|
||||||
if not with_categories:
|
|
||||||
return [x[1] for x in flashes]
|
|
||||||
return flashes
|
|
||||||
|
|
||||||
|
|
||||||
def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
|
||||||
attachment_filename=None, add_etags=True,
|
|
||||||
cache_timeout=None, conditional=False):
|
|
||||||
"""Sends the contents of a file to the client. This will use the
|
|
||||||
most efficient method available and configured. By default it will
|
|
||||||
try to use the WSGI server's file_wrapper support. Alternatively
|
|
||||||
you can set the application's :attr:`~Flask.use_x_sendfile` attribute
|
|
||||||
to ``True`` to directly emit an `X-Sendfile` header. This however
|
|
||||||
requires support of the underlying webserver for `X-Sendfile`.
|
|
||||||
|
|
||||||
By default it will try to guess the mimetype for you, but you can
|
|
||||||
also explicitly provide one. For extra security you probably want
|
|
||||||
to send certain files as attachment (HTML for instance). The mimetype
|
|
||||||
guessing requires a `filename` or an `attachment_filename` to be
|
|
||||||
provided.
|
|
||||||
|
|
||||||
Please never pass filenames to this function from user sources without
|
|
||||||
checking them first. Something like this is usually sufficient to
|
|
||||||
avoid security problems::
|
|
||||||
|
|
||||||
if '..' in filename or filename.startswith('/'):
|
|
||||||
abort(404)
|
|
||||||
|
|
||||||
.. versionadded:: 0.2
|
|
||||||
|
|
||||||
.. versionadded:: 0.5
|
|
||||||
The `add_etags`, `cache_timeout` and `conditional` parameters were
|
|
||||||
added. The default behavior is now to attach etags.
|
|
||||||
|
|
||||||
.. versionchanged:: 0.7
|
|
||||||
mimetype guessing and etag support for file objects was
|
|
||||||
deprecated because it was unreliable. Pass a filename if you are
|
|
||||||
able to, otherwise attach an etag yourself. This functionality
|
|
||||||
will be removed in Flask 1.0
|
|
||||||
|
|
||||||
.. versionchanged:: 0.9
|
|
||||||
cache_timeout pulls its default from application config, when None.
|
|
||||||
|
|
||||||
:param filename_or_fp: the filename of the file to send. This is
|
|
||||||
relative to the :attr:`~Flask.root_path` if a
|
|
||||||
relative path is specified.
|
|
||||||
Alternatively a file object might be provided
|
|
||||||
in which case `X-Sendfile` might not work and
|
|
||||||
fall back to the traditional method. Make sure
|
|
||||||
that the file pointer is positioned at the start
|
|
||||||
of data to send before calling :func:`send_file`.
|
|
||||||
:param mimetype: the mimetype of the file if provided, otherwise
|
|
||||||
auto detection happens.
|
|
||||||
:param as_attachment: set to `True` if you want to send this file with
|
|
||||||
a ``Content-Disposition: attachment`` header.
|
|
||||||
:param attachment_filename: the filename for the attachment if it
|
|
||||||
differs from the file's filename.
|
|
||||||
:param add_etags: set to `False` to disable attaching of etags.
|
|
||||||
:param conditional: set to `True` to enable conditional responses.
|
|
||||||
|
|
||||||
:param cache_timeout: the timeout in seconds for the headers. When `None`
|
|
||||||
(default), this value is set by
|
|
||||||
:meth:`~Flask.get_send_file_max_age` of
|
|
||||||
:data:`~flask.current_app`.
|
|
||||||
"""
|
|
||||||
mtime = None
|
|
||||||
if isinstance(filename_or_fp, basestring):
|
|
||||||
filename = filename_or_fp
|
|
||||||
file = None
|
|
||||||
else:
|
|
||||||
from warnings import warn
|
|
||||||
file = filename_or_fp
|
|
||||||
filename = getattr(file, 'name', None)
|
|
||||||
|
|
||||||
# XXX: this behavior is now deprecated because it was unreliable.
|
|
||||||
# removed in Flask 1.0
|
|
||||||
if not attachment_filename and not mimetype \
|
|
||||||
and isinstance(filename, basestring):
|
|
||||||
warn(DeprecationWarning('The filename support for file objects '
|
|
||||||
'passed to send_file is now deprecated. Pass an '
|
|
||||||
'attach_filename if you want mimetypes to be guessed.'),
|
|
||||||
stacklevel=2)
|
|
||||||
if add_etags:
|
|
||||||
warn(DeprecationWarning('In future flask releases etags will no '
|
|
||||||
'longer be generated for file objects passed to the send_file '
|
|
||||||
'function because this behavior was unreliable. Pass '
|
|
||||||
'filenames instead if possible, otherwise attach an etag '
|
|
||||||
'yourself based on another value'), stacklevel=2)
|
|
||||||
|
|
||||||
if filename is not None:
|
|
||||||
if not os.path.isabs(filename):
|
|
||||||
filename = os.path.join(current_app.root_path, filename)
|
|
||||||
if mimetype is None and (filename or attachment_filename):
|
|
||||||
mimetype = mimetypes.guess_type(filename or attachment_filename)[0]
|
|
||||||
if mimetype is None:
|
|
||||||
mimetype = 'application/octet-stream'
|
|
||||||
|
|
||||||
headers = Headers()
|
|
||||||
if as_attachment:
|
|
||||||
if attachment_filename is None:
|
|
||||||
if filename is None:
|
|
||||||
raise TypeError('filename unavailable, required for '
|
|
||||||
'sending as attachment')
|
|
||||||
attachment_filename = os.path.basename(filename)
|
|
||||||
headers.add('Content-Disposition', 'attachment',
|
|
||||||
filename=attachment_filename)
|
|
||||||
|
|
||||||
if current_app.use_x_sendfile and filename:
|
|
||||||
if file is not None:
|
|
||||||
file.close()
|
|
||||||
headers['X-Sendfile'] = filename
|
|
||||||
data = None
|
|
||||||
else:
|
|
||||||
if file is None:
|
|
||||||
file = open(filename, 'rb')
|
|
||||||
mtime = os.path.getmtime(filename)
|
|
||||||
data = wrap_file(request.environ, file)
|
|
||||||
|
|
||||||
rv = current_app.response_class(data, mimetype=mimetype, headers=headers,
|
|
||||||
direct_passthrough=True)
|
|
||||||
|
|
||||||
# if we know the file modification date, we can store it as the
|
|
||||||
# the time of the last modification.
|
|
||||||
if mtime is not None:
|
|
||||||
rv.last_modified = int(mtime)
|
|
||||||
|
|
||||||
rv.cache_control.public = True
|
|
||||||
if cache_timeout is None:
|
|
||||||
cache_timeout = current_app.get_send_file_max_age(filename)
|
|
||||||
if cache_timeout is not None:
|
|
||||||
rv.cache_control.max_age = cache_timeout
|
|
||||||
rv.expires = int(time() + cache_timeout)
|
|
||||||
|
|
||||||
if add_etags and filename is not None:
|
|
||||||
rv.set_etag('flask-%s-%s-%s' % (
|
|
||||||
os.path.getmtime(filename),
|
|
||||||
os.path.getsize(filename),
|
|
||||||
adler32(
|
|
||||||
filename.encode('utf8') if isinstance(filename, unicode)
|
|
||||||
else filename
|
|
||||||
) & 0xffffffff
|
|
||||||
))
|
|
||||||
if conditional:
|
|
||||||
rv = rv.make_conditional(request)
|
|
||||||
# make sure we don't send x-sendfile for servers that
|
|
||||||
# ignore the 304 status code for x-sendfile.
|
|
||||||
if rv.status_code == 304:
|
|
||||||
rv.headers.pop('x-sendfile', None)
|
|
||||||
return rv
|
|
||||||
|
|
||||||
|
|
||||||
def safe_join(directory, filename):
|
|
||||||
"""Safely join `directory` and `filename`.
|
|
||||||
|
|
||||||
Example usage::
|
|
||||||
|
|
||||||
@app.route('/wiki/<path:filename>')
|
|
||||||
def wiki_page(filename):
|
|
||||||
filename = safe_join(app.config['WIKI_FOLDER'], filename)
|
|
||||||
with open(filename, 'rb') as fd:
|
|
||||||
content = fd.read() # Read and process the file content...
|
|
||||||
|
|
||||||
:param directory: the base directory.
|
|
||||||
:param filename: the untrusted filename relative to that directory.
|
|
||||||
:raises: :class:`~werkzeug.exceptions.NotFound` if the resulting path
|
|
||||||
would fall out of `directory`.
|
|
||||||
"""
|
|
||||||
filename = posixpath.normpath(filename)
|
|
||||||
for sep in _os_alt_seps:
|
|
||||||
if sep in filename:
|
|
||||||
raise NotFound()
|
|
||||||
if os.path.isabs(filename) or filename.startswith('../'):
|
|
||||||
raise NotFound()
|
|
||||||
return os.path.join(directory, filename)
|
|
||||||
|
|
||||||
|
|
||||||
def send_from_directory(directory, filename, **options):
|
|
||||||
"""Send a file from a given directory with :func:`send_file`. This
|
|
||||||
is a secure way to quickly expose static files from an upload folder
|
|
||||||
or something similar.
|
|
||||||
|
|
||||||
Example usage::
|
|
||||||
|
|
||||||
@app.route('/uploads/<path:filename>')
|
|
||||||
def download_file(filename):
|
|
||||||
return send_from_directory(app.config['UPLOAD_FOLDER'],
|
|
||||||
filename, as_attachment=True)
|
|
||||||
|
|
||||||
.. admonition:: Sending files and Performance
|
|
||||||
|
|
||||||
It is strongly recommended to activate either `X-Sendfile` support in
|
|
||||||
your webserver or (if no authentication happens) to tell the webserver
|
|
||||||
to serve files for the given path on its own without calling into the
|
|
||||||
web application for improved performance.
|
|
||||||
|
|
||||||
.. versionadded:: 0.5
|
|
||||||
|
|
||||||
:param directory: the directory where all the files are stored.
|
|
||||||
:param filename: the filename relative to that directory to
|
|
||||||
download.
|
|
||||||
:param options: optional keyword arguments that are directly
|
|
||||||
forwarded to :func:`send_file`.
|
|
||||||
"""
|
|
||||||
filename = safe_join(directory, filename)
|
|
||||||
if not os.path.isfile(filename):
|
|
||||||
raise NotFound()
|
|
||||||
options.setdefault('conditional', True)
|
|
||||||
return send_file(filename, **options)
|
|
||||||
|
|
||||||
|
|
||||||
def get_root_path(import_name):
|
|
||||||
"""Returns the path to a package or cwd if that cannot be found. This
|
|
||||||
returns the path of a package or the folder that contains a module.
|
|
||||||
|
|
||||||
Not to be confused with the package path returned by :func:`find_package`.
|
|
||||||
"""
|
|
||||||
# Module already imported and has a file attribute. Use that first.
|
|
||||||
mod = sys.modules.get(import_name)
|
|
||||||
if mod is not None and hasattr(mod, '__file__'):
|
|
||||||
return os.path.dirname(os.path.abspath(mod.__file__))
|
|
||||||
|
|
||||||
# Next attempt: check the loader.
|
|
||||||
loader = pkgutil.get_loader(import_name)
|
|
||||||
|
|
||||||
# Loader does not exist or we're referring to an unloaded main module
|
|
||||||
# or a main module without path (interactive sessions), go with the
|
|
||||||
# current working directory.
|
|
||||||
if loader is None or import_name == '__main__':
|
|
||||||
return os.getcwd()
|
|
||||||
|
|
||||||
# For .egg, zipimporter does not have get_filename until Python 2.7.
|
|
||||||
# Some other loaders might exhibit the same behavior.
|
|
||||||
if hasattr(loader, 'get_filename'):
|
|
||||||
filepath = loader.get_filename(import_name)
|
|
||||||
else:
|
|
||||||
# Fall back to imports.
|
|
||||||
__import__(import_name)
|
|
||||||
filepath = sys.modules[import_name].__file__
|
|
||||||
|
|
||||||
# filepath is import_name.py for a module, or __init__.py for a package.
|
|
||||||
return os.path.dirname(os.path.abspath(filepath))
|
|
||||||
|
|
||||||
|
|
||||||
def find_package(import_name):
|
|
||||||
"""Finds a package and returns the prefix (or None if the package is
|
|
||||||
not installed) as well as the folder that contains the package or
|
|
||||||
module as a tuple. The package path returned is the module that would
|
|
||||||
have to be added to the pythonpath in order to make it possible to
|
|
||||||
import the module. The prefix is the path below which a UNIX like
|
|
||||||
folder structure exists (lib, share etc.).
|
|
||||||
"""
|
|
||||||
root_mod_name = import_name.split('.')[0]
|
|
||||||
loader = pkgutil.get_loader(root_mod_name)
|
|
||||||
if loader is None or import_name == '__main__':
|
|
||||||
# import name is not found, or interactive/main module
|
|
||||||
package_path = os.getcwd()
|
|
||||||
else:
|
|
||||||
# For .egg, zipimporter does not have get_filename until Python 2.7.
|
|
||||||
if hasattr(loader, 'get_filename'):
|
|
||||||
filename = loader.get_filename(root_mod_name)
|
|
||||||
elif hasattr(loader, 'archive'):
|
|
||||||
# zipimporter's loader.archive points to the .egg or .zip
|
|
||||||
# archive filename is dropped in call to dirname below.
|
|
||||||
filename = loader.archive
|
|
||||||
else:
|
|
||||||
# At least one loader is missing both get_filename and archive:
|
|
||||||
# Google App Engine's HardenedModulesHook
|
|
||||||
#
|
|
||||||
# Fall back to imports.
|
|
||||||
__import__(import_name)
|
|
||||||
filename = sys.modules[import_name].__file__
|
|
||||||
package_path = os.path.abspath(os.path.dirname(filename))
|
|
||||||
# package_path ends with __init__.py for a package
|
|
||||||
if loader.is_package(root_mod_name):
|
|
||||||
package_path = os.path.dirname(package_path)
|
|
||||||
|
|
||||||
site_parent, site_folder = os.path.split(package_path)
|
|
||||||
py_prefix = os.path.abspath(sys.prefix)
|
|
||||||
if package_path.startswith(py_prefix):
|
|
||||||
return py_prefix, package_path
|
|
||||||
elif site_folder.lower() == 'site-packages':
|
|
||||||
parent, folder = os.path.split(site_parent)
|
|
||||||
# Windows like installations
|
|
||||||
if folder.lower() == 'lib':
|
|
||||||
base_dir = parent
|
|
||||||
# UNIX like installations
|
|
||||||
elif os.path.basename(parent).lower() == 'lib':
|
|
||||||
base_dir = os.path.dirname(parent)
|
|
||||||
else:
|
|
||||||
base_dir = site_parent
|
|
||||||
return base_dir, package_path
|
|
||||||
return None, package_path
|
|
||||||
|
|
||||||
|
|
||||||
class locked_cached_property(object):
|
|
||||||
"""A decorator that converts a function into a lazy property. The
|
|
||||||
function wrapped is called the first time to retrieve the result
|
|
||||||
and then that calculated result is used the next time you access
|
|
||||||
the value. Works like the one in Werkzeug but has a lock for
|
|
||||||
thread safety.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, func, name=None, doc=None):
|
|
||||||
self.__name__ = name or func.__name__
|
|
||||||
self.__module__ = func.__module__
|
|
||||||
self.__doc__ = doc or func.__doc__
|
|
||||||
self.func = func
|
|
||||||
self.lock = RLock()
|
|
||||||
|
|
||||||
def __get__(self, obj, type=None):
|
|
||||||
if obj is None:
|
|
||||||
return self
|
|
||||||
with self.lock:
|
|
||||||
value = obj.__dict__.get(self.__name__, _missing)
|
|
||||||
if value is _missing:
|
|
||||||
value = self.func(obj)
|
|
||||||
obj.__dict__[self.__name__] = value
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
class _PackageBoundObject(object):
|
|
||||||
|
|
||||||
def __init__(self, import_name, template_folder=None):
|
|
||||||
#: The name of the package or module. Do not change this once
|
|
||||||
#: it was set by the constructor.
|
|
||||||
self.import_name = import_name
|
|
||||||
|
|
||||||
#: location of the templates. `None` if templates should not be
|
|
||||||
#: exposed.
|
|
||||||
self.template_folder = template_folder
|
|
||||||
|
|
||||||
#: Where is the app root located?
|
|
||||||
self.root_path = get_root_path(self.import_name)
|
|
||||||
|
|
||||||
self._static_folder = None
|
|
||||||
self._static_url_path = None
|
|
||||||
|
|
||||||
def _get_static_folder(self):
|
|
||||||
if self._static_folder is not None:
|
|
||||||
return os.path.join(self.root_path, self._static_folder)
|
|
||||||
def _set_static_folder(self, value):
|
|
||||||
self._static_folder = value
|
|
||||||
static_folder = property(_get_static_folder, _set_static_folder)
|
|
||||||
del _get_static_folder, _set_static_folder
|
|
||||||
|
|
||||||
def _get_static_url_path(self):
|
|
||||||
if self._static_url_path is None:
|
|
||||||
if self.static_folder is None:
|
|
||||||
return None
|
|
||||||
return '/' + os.path.basename(self.static_folder)
|
|
||||||
return self._static_url_path
|
|
||||||
def _set_static_url_path(self, value):
|
|
||||||
self._static_url_path = value
|
|
||||||
static_url_path = property(_get_static_url_path, _set_static_url_path)
|
|
||||||
del _get_static_url_path, _set_static_url_path
|
|
||||||
|
|
||||||
@property
|
|
||||||
def has_static_folder(self):
|
|
||||||
"""This is `True` if the package bound object's container has a
|
|
||||||
folder named ``'static'``.
|
|
||||||
|
|
||||||
.. versionadded:: 0.5
|
|
||||||
"""
|
|
||||||
return self.static_folder is not None
|
|
||||||
|
|
||||||
@locked_cached_property
|
|
||||||
def jinja_loader(self):
|
|
||||||
"""The Jinja loader for this package bound object.
|
|
||||||
|
|
||||||
.. versionadded:: 0.5
|
|
||||||
"""
|
|
||||||
if self.template_folder is not None:
|
|
||||||
return FileSystemLoader(os.path.join(self.root_path,
|
|
||||||
self.template_folder))
|
|
||||||
|
|
||||||
def get_send_file_max_age(self, filename):
|
|
||||||
"""Provides default cache_timeout for the :func:`send_file` functions.
|
|
||||||
|
|
||||||
By default, this function returns ``SEND_FILE_MAX_AGE_DEFAULT`` from
|
|
||||||
the configuration of :data:`~flask.current_app`.
|
|
||||||
|
|
||||||
Static file functions such as :func:`send_from_directory` use this
|
|
||||||
function, and :func:`send_file` calls this function on
|
|
||||||
:data:`~flask.current_app` when the given cache_timeout is `None`. If a
|
|
||||||
cache_timeout is given in :func:`send_file`, that timeout is used;
|
|
||||||
otherwise, this method is called.
|
|
||||||
|
|
||||||
This allows subclasses to change the behavior when sending files based
|
|
||||||
on the filename. For example, to set the cache timeout for .js files
|
|
||||||
to 60 seconds::
|
|
||||||
|
|
||||||
class MyFlask(flask.Flask):
|
|
||||||
def get_send_file_max_age(self, name):
|
|
||||||
if name.lower().endswith('.js'):
|
|
||||||
return 60
|
|
||||||
return flask.Flask.get_send_file_max_age(self, name)
|
|
||||||
|
|
||||||
.. versionadded:: 0.9
|
|
||||||
"""
|
|
||||||
return current_app.config['SEND_FILE_MAX_AGE_DEFAULT']
|
|
||||||
|
|
||||||
def send_static_file(self, filename):
|
|
||||||
"""Function used internally to send static files from the static
|
|
||||||
folder to the browser.
|
|
||||||
|
|
||||||
.. versionadded:: 0.5
|
|
||||||
"""
|
|
||||||
if not self.has_static_folder:
|
|
||||||
raise RuntimeError('No static folder for this object')
|
|
||||||
# Ensure get_send_file_max_age is called in all cases.
|
|
||||||
# Here, we ensure get_send_file_max_age is called for Blueprints.
|
|
||||||
cache_timeout = self.get_send_file_max_age(filename)
|
|
||||||
return send_from_directory(self.static_folder, filename,
|
|
||||||
cache_timeout=cache_timeout)
|
|
||||||
|
|
||||||
def open_resource(self, resource, mode='rb'):
|
|
||||||
"""Opens a resource from the application's resource folder. To see
|
|
||||||
how this works, consider the following folder structure::
|
|
||||||
|
|
||||||
/myapplication.py
|
|
||||||
/schema.sql
|
|
||||||
/static
|
|
||||||
/style.css
|
|
||||||
/templates
|
|
||||||
/layout.html
|
|
||||||
/index.html
|
|
||||||
|
|
||||||
If you want to open the `schema.sql` file you would do the
|
|
||||||
following::
|
|
||||||
|
|
||||||
with app.open_resource('schema.sql') as f:
|
|
||||||
contents = f.read()
|
|
||||||
do_something_with(contents)
|
|
||||||
|
|
||||||
:param resource: the name of the resource. To access resources within
|
|
||||||
subfolders use forward slashes as separator.
|
|
||||||
"""
|
|
||||||
if mode not in ('r', 'rb'):
|
|
||||||
raise ValueError('Resources can only be opened for reading')
|
|
||||||
return open(os.path.join(self.root_path, resource), mode)
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.logging
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Implements the logging support for Flask.
|
|
||||||
|
|
||||||
:copyright: (c) 2011 by Armin Ronacher.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
from logging import getLogger, StreamHandler, Formatter, getLoggerClass, DEBUG
|
|
||||||
|
|
||||||
|
|
||||||
def create_logger(app):
|
|
||||||
"""Creates a logger for the given application. This logger works
|
|
||||||
similar to a regular Python logger but changes the effective logging
|
|
||||||
level based on the application's debug flag. Furthermore this
|
|
||||||
function also removes all attached handlers in case there was a
|
|
||||||
logger with the log name before.
|
|
||||||
"""
|
|
||||||
Logger = getLoggerClass()
|
|
||||||
|
|
||||||
class DebugLogger(Logger):
|
|
||||||
def getEffectiveLevel(x):
|
|
||||||
if x.level == 0 and app.debug:
|
|
||||||
return DEBUG
|
|
||||||
return Logger.getEffectiveLevel(x)
|
|
||||||
|
|
||||||
class DebugHandler(StreamHandler):
|
|
||||||
def emit(x, record):
|
|
||||||
StreamHandler.emit(x, record) if app.debug else None
|
|
||||||
|
|
||||||
handler = DebugHandler()
|
|
||||||
handler.setLevel(DEBUG)
|
|
||||||
handler.setFormatter(Formatter(app.debug_log_format))
|
|
||||||
logger = getLogger(app.logger_name)
|
|
||||||
# just in case that was not a new logger, get rid of all the handlers
|
|
||||||
# already attached to it.
|
|
||||||
del logger.handlers[:]
|
|
||||||
logger.__class__ = DebugLogger
|
|
||||||
logger.addHandler(handler)
|
|
||||||
return logger
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.module
|
|
||||||
~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Implements a class that represents module blueprints.
|
|
||||||
|
|
||||||
:copyright: (c) 2011 by Armin Ronacher.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from .blueprints import Blueprint
|
|
||||||
|
|
||||||
|
|
||||||
def blueprint_is_module(bp):
|
|
||||||
"""Used to figure out if something is actually a module"""
|
|
||||||
return isinstance(bp, Module)
|
|
||||||
|
|
||||||
|
|
||||||
class Module(Blueprint):
|
|
||||||
"""Deprecated module support. Until Flask 0.6 modules were a different
|
|
||||||
name of the concept now available as blueprints in Flask. They are
|
|
||||||
essentially doing the same but have some bad semantics for templates and
|
|
||||||
static files that were fixed with blueprints.
|
|
||||||
|
|
||||||
.. versionchanged:: 0.7
|
|
||||||
Modules were deprecated in favor for blueprints.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, import_name, name=None, url_prefix=None,
|
|
||||||
static_path=None, subdomain=None):
|
|
||||||
if name is None:
|
|
||||||
assert '.' in import_name, 'name required if package name ' \
|
|
||||||
'does not point to a submodule'
|
|
||||||
name = import_name.rsplit('.', 1)[1]
|
|
||||||
Blueprint.__init__(self, name, import_name, url_prefix=url_prefix,
|
|
||||||
subdomain=subdomain, template_folder='templates')
|
|
||||||
|
|
||||||
if os.path.isdir(os.path.join(self.root_path, 'static')):
|
|
||||||
self._static_folder = 'static'
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.session
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
This module used to flask with the session global so we moved it
|
|
||||||
over to flask.sessions
|
|
||||||
|
|
||||||
:copyright: (c) 2011 by Armin Ronacher.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from warnings import warn
|
|
||||||
warn(DeprecationWarning('please use flask.sessions instead'))
|
|
||||||
|
|
||||||
from .sessions import SecureCookieSession, NullSession
|
|
||||||
|
|
||||||
Session = SecureCookieSession
|
|
||||||
_NullSession = NullSession
|
|
||||||
@@ -1,205 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.sessions
|
|
||||||
~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Implements cookie based sessions based on Werkzeug's secure cookie
|
|
||||||
system.
|
|
||||||
|
|
||||||
:copyright: (c) 2011 by Armin Ronacher.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
from werkzeug.contrib.securecookie import SecureCookie
|
|
||||||
|
|
||||||
|
|
||||||
class SessionMixin(object):
|
|
||||||
"""Expands a basic dictionary with an accessors that are expected
|
|
||||||
by Flask extensions and users for the session.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _get_permanent(self):
|
|
||||||
return self.get('_permanent', False)
|
|
||||||
|
|
||||||
def _set_permanent(self, value):
|
|
||||||
self['_permanent'] = bool(value)
|
|
||||||
|
|
||||||
#: this reflects the ``'_permanent'`` key in the dict.
|
|
||||||
permanent = property(_get_permanent, _set_permanent)
|
|
||||||
del _get_permanent, _set_permanent
|
|
||||||
|
|
||||||
#: some session backends can tell you if a session is new, but that is
|
|
||||||
#: not necessarily guaranteed. Use with caution. The default mixin
|
|
||||||
#: implementation just hardcodes `False` in.
|
|
||||||
new = False
|
|
||||||
|
|
||||||
#: for some backends this will always be `True`, but some backends will
|
|
||||||
#: default this to false and detect changes in the dictionary for as
|
|
||||||
#: long as changes do not happen on mutable structures in the session.
|
|
||||||
#: The default mixin implementation just hardcodes `True` in.
|
|
||||||
modified = True
|
|
||||||
|
|
||||||
|
|
||||||
class SecureCookieSession(SecureCookie, SessionMixin):
|
|
||||||
"""Expands the session with support for switching between permanent
|
|
||||||
and non-permanent sessions.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class NullSession(SecureCookieSession):
|
|
||||||
"""Class used to generate nicer error messages if sessions are not
|
|
||||||
available. Will still allow read-only access to the empty session
|
|
||||||
but fail on setting.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _fail(self, *args, **kwargs):
|
|
||||||
raise RuntimeError('the session is unavailable because no secret '
|
|
||||||
'key was set. Set the secret_key on the '
|
|
||||||
'application to something unique and secret.')
|
|
||||||
__setitem__ = __delitem__ = clear = pop = popitem = \
|
|
||||||
update = setdefault = _fail
|
|
||||||
del _fail
|
|
||||||
|
|
||||||
|
|
||||||
class SessionInterface(object):
|
|
||||||
"""The basic interface you have to implement in order to replace the
|
|
||||||
default session interface which uses werkzeug's securecookie
|
|
||||||
implementation. The only methods you have to implement are
|
|
||||||
:meth:`open_session` and :meth:`save_session`, the others have
|
|
||||||
useful defaults which you don't need to change.
|
|
||||||
|
|
||||||
The session object returned by the :meth:`open_session` method has to
|
|
||||||
provide a dictionary like interface plus the properties and methods
|
|
||||||
from the :class:`SessionMixin`. We recommend just subclassing a dict
|
|
||||||
and adding that mixin::
|
|
||||||
|
|
||||||
class Session(dict, SessionMixin):
|
|
||||||
pass
|
|
||||||
|
|
||||||
If :meth:`open_session` returns `None` Flask will call into
|
|
||||||
:meth:`make_null_session` to create a session that acts as replacement
|
|
||||||
if the session support cannot work because some requirement is not
|
|
||||||
fulfilled. The default :class:`NullSession` class that is created
|
|
||||||
will complain that the secret key was not set.
|
|
||||||
|
|
||||||
To replace the session interface on an application all you have to do
|
|
||||||
is to assign :attr:`flask.Flask.session_interface`::
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
|
||||||
app.session_interface = MySessionInterface()
|
|
||||||
|
|
||||||
.. versionadded:: 0.8
|
|
||||||
"""
|
|
||||||
|
|
||||||
#: :meth:`make_null_session` will look here for the class that should
|
|
||||||
#: be created when a null session is requested. Likewise the
|
|
||||||
#: :meth:`is_null_session` method will perform a typecheck against
|
|
||||||
#: this type.
|
|
||||||
null_session_class = NullSession
|
|
||||||
|
|
||||||
def make_null_session(self, app):
|
|
||||||
"""Creates a null session which acts as a replacement object if the
|
|
||||||
real session support could not be loaded due to a configuration
|
|
||||||
error. This mainly aids the user experience because the job of the
|
|
||||||
null session is to still support lookup without complaining but
|
|
||||||
modifications are answered with a helpful error message of what
|
|
||||||
failed.
|
|
||||||
|
|
||||||
This creates an instance of :attr:`null_session_class` by default.
|
|
||||||
"""
|
|
||||||
return self.null_session_class()
|
|
||||||
|
|
||||||
def is_null_session(self, obj):
|
|
||||||
"""Checks if a given object is a null session. Null sessions are
|
|
||||||
not asked to be saved.
|
|
||||||
|
|
||||||
This checks if the object is an instance of :attr:`null_session_class`
|
|
||||||
by default.
|
|
||||||
"""
|
|
||||||
return isinstance(obj, self.null_session_class)
|
|
||||||
|
|
||||||
def get_cookie_domain(self, app):
|
|
||||||
"""Helpful helper method that returns the cookie domain that should
|
|
||||||
be used for the session cookie if session cookies are used.
|
|
||||||
"""
|
|
||||||
if app.config['SESSION_COOKIE_DOMAIN'] is not None:
|
|
||||||
return app.config['SESSION_COOKIE_DOMAIN']
|
|
||||||
if app.config['SERVER_NAME'] is not None:
|
|
||||||
# chop of the port which is usually not supported by browsers
|
|
||||||
return '.' + app.config['SERVER_NAME'].rsplit(':', 1)[0]
|
|
||||||
|
|
||||||
def get_cookie_path(self, app):
|
|
||||||
"""Returns the path for which the cookie should be valid. The
|
|
||||||
default implementation uses the value from the SESSION_COOKIE_PATH``
|
|
||||||
config var if it's set, and falls back to ``APPLICATION_ROOT`` or
|
|
||||||
uses ``/`` if it's `None`.
|
|
||||||
"""
|
|
||||||
return app.config['SESSION_COOKIE_PATH'] or \
|
|
||||||
app.config['APPLICATION_ROOT'] or '/'
|
|
||||||
|
|
||||||
def get_cookie_httponly(self, app):
|
|
||||||
"""Returns True if the session cookie should be httponly. This
|
|
||||||
currently just returns the value of the ``SESSION_COOKIE_HTTPONLY``
|
|
||||||
config var.
|
|
||||||
"""
|
|
||||||
return app.config['SESSION_COOKIE_HTTPONLY']
|
|
||||||
|
|
||||||
def get_cookie_secure(self, app):
|
|
||||||
"""Returns True if the cookie should be secure. This currently
|
|
||||||
just returns the value of the ``SESSION_COOKIE_SECURE`` setting.
|
|
||||||
"""
|
|
||||||
return app.config['SESSION_COOKIE_SECURE']
|
|
||||||
|
|
||||||
def get_expiration_time(self, app, session):
|
|
||||||
"""A helper method that returns an expiration date for the session
|
|
||||||
or `None` if the session is linked to the browser session. The
|
|
||||||
default implementation returns now + the permanent session
|
|
||||||
lifetime configured on the application.
|
|
||||||
"""
|
|
||||||
if session.permanent:
|
|
||||||
return datetime.utcnow() + app.permanent_session_lifetime
|
|
||||||
|
|
||||||
def open_session(self, app, request):
|
|
||||||
"""This method has to be implemented and must either return `None`
|
|
||||||
in case the loading failed because of a configuration error or an
|
|
||||||
instance of a session object which implements a dictionary like
|
|
||||||
interface + the methods and attributes on :class:`SessionMixin`.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def save_session(self, app, session, response):
|
|
||||||
"""This is called for actual sessions returned by :meth:`open_session`
|
|
||||||
at the end of the request. This is still called during a request
|
|
||||||
context so if you absolutely need access to the request you can do
|
|
||||||
that.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
|
|
||||||
class SecureCookieSessionInterface(SessionInterface):
|
|
||||||
"""The cookie session interface that uses the Werkzeug securecookie
|
|
||||||
as client side session backend.
|
|
||||||
"""
|
|
||||||
session_class = SecureCookieSession
|
|
||||||
|
|
||||||
def open_session(self, app, request):
|
|
||||||
key = app.secret_key
|
|
||||||
if key is not None:
|
|
||||||
return self.session_class.load_cookie(request,
|
|
||||||
app.session_cookie_name,
|
|
||||||
secret_key=key)
|
|
||||||
|
|
||||||
def save_session(self, app, session, response):
|
|
||||||
expires = self.get_expiration_time(app, session)
|
|
||||||
domain = self.get_cookie_domain(app)
|
|
||||||
path = self.get_cookie_path(app)
|
|
||||||
httponly = self.get_cookie_httponly(app)
|
|
||||||
secure = self.get_cookie_secure(app)
|
|
||||||
if session.modified and not session:
|
|
||||||
response.delete_cookie(app.session_cookie_name, path=path,
|
|
||||||
domain=domain)
|
|
||||||
else:
|
|
||||||
session.save_cookie(response, app.session_cookie_name, path=path,
|
|
||||||
expires=expires, httponly=httponly,
|
|
||||||
secure=secure, domain=domain)
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.signals
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Implements signals based on blinker if available, otherwise
|
|
||||||
falls silently back to a noop
|
|
||||||
|
|
||||||
:copyright: (c) 2011 by Armin Ronacher.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
signals_available = False
|
|
||||||
try:
|
|
||||||
from blinker import Namespace
|
|
||||||
signals_available = True
|
|
||||||
except ImportError:
|
|
||||||
class Namespace(object):
|
|
||||||
def signal(self, name, doc=None):
|
|
||||||
return _FakeSignal(name, doc)
|
|
||||||
|
|
||||||
class _FakeSignal(object):
|
|
||||||
"""If blinker is unavailable, create a fake class with the same
|
|
||||||
interface that allows sending of signals but will fail with an
|
|
||||||
error on anything else. Instead of doing anything on send, it
|
|
||||||
will just ignore the arguments and do nothing instead.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, name, doc=None):
|
|
||||||
self.name = name
|
|
||||||
self.__doc__ = doc
|
|
||||||
def _fail(self, *args, **kwargs):
|
|
||||||
raise RuntimeError('signalling support is unavailable '
|
|
||||||
'because the blinker library is '
|
|
||||||
'not installed.')
|
|
||||||
send = lambda *a, **kw: None
|
|
||||||
connect = disconnect = has_receivers_for = receivers_for = \
|
|
||||||
temporarily_connected_to = connected_to = _fail
|
|
||||||
del _fail
|
|
||||||
|
|
||||||
# the namespace for code signals. If you are not flask code, do
|
|
||||||
# not put signals in here. Create your own namespace instead.
|
|
||||||
_signals = Namespace()
|
|
||||||
|
|
||||||
|
|
||||||
# core signals. For usage examples grep the sourcecode or consult
|
|
||||||
# the API documentation in docs/api.rst as well as docs/signals.rst
|
|
||||||
template_rendered = _signals.signal('template-rendered')
|
|
||||||
request_started = _signals.signal('request-started')
|
|
||||||
request_finished = _signals.signal('request-finished')
|
|
||||||
request_tearing_down = _signals.signal('request-tearing-down')
|
|
||||||
got_request_exception = _signals.signal('got-request-exception')
|
|
||||||
appcontext_tearing_down = _signals.signal('appcontext-tearing-down')
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.templating
|
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Implements the bridge to Jinja2.
|
|
||||||
|
|
||||||
:copyright: (c) 2011 by Armin Ronacher.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
import posixpath
|
|
||||||
from jinja2 import BaseLoader, Environment as BaseEnvironment, \
|
|
||||||
TemplateNotFound
|
|
||||||
|
|
||||||
from .globals import _request_ctx_stack
|
|
||||||
from .signals import template_rendered
|
|
||||||
from .module import blueprint_is_module
|
|
||||||
|
|
||||||
|
|
||||||
def _default_template_ctx_processor():
|
|
||||||
"""Default template context processor. Injects `request`,
|
|
||||||
`session` and `g`.
|
|
||||||
"""
|
|
||||||
reqctx = _request_ctx_stack.top
|
|
||||||
return dict(
|
|
||||||
config=reqctx.app.config,
|
|
||||||
request=reqctx.request,
|
|
||||||
session=reqctx.session,
|
|
||||||
g=reqctx.g
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Environment(BaseEnvironment):
|
|
||||||
"""Works like a regular Jinja2 environment but has some additional
|
|
||||||
knowledge of how Flask's blueprint works so that it can prepend the
|
|
||||||
name of the blueprint to referenced templates if necessary.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, app, **options):
|
|
||||||
if 'loader' not in options:
|
|
||||||
options['loader'] = app.create_global_jinja_loader()
|
|
||||||
BaseEnvironment.__init__(self, **options)
|
|
||||||
self.app = app
|
|
||||||
|
|
||||||
|
|
||||||
class DispatchingJinjaLoader(BaseLoader):
|
|
||||||
"""A loader that looks for templates in the application and all
|
|
||||||
the blueprint folders.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, app):
|
|
||||||
self.app = app
|
|
||||||
|
|
||||||
def get_source(self, environment, template):
|
|
||||||
for loader, local_name in self._iter_loaders(template):
|
|
||||||
try:
|
|
||||||
return loader.get_source(environment, local_name)
|
|
||||||
except TemplateNotFound:
|
|
||||||
pass
|
|
||||||
|
|
||||||
raise TemplateNotFound(template)
|
|
||||||
|
|
||||||
def _iter_loaders(self, template):
|
|
||||||
loader = self.app.jinja_loader
|
|
||||||
if loader is not None:
|
|
||||||
yield loader, template
|
|
||||||
|
|
||||||
# old style module based loaders in case we are dealing with a
|
|
||||||
# blueprint that is an old style module
|
|
||||||
try:
|
|
||||||
module, local_name = posixpath.normpath(template).split('/', 1)
|
|
||||||
blueprint = self.app.blueprints[module]
|
|
||||||
if blueprint_is_module(blueprint):
|
|
||||||
loader = blueprint.jinja_loader
|
|
||||||
if loader is not None:
|
|
||||||
yield loader, local_name
|
|
||||||
except (ValueError, KeyError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
for blueprint in self.app.blueprints.itervalues():
|
|
||||||
if blueprint_is_module(blueprint):
|
|
||||||
continue
|
|
||||||
loader = blueprint.jinja_loader
|
|
||||||
if loader is not None:
|
|
||||||
yield loader, template
|
|
||||||
|
|
||||||
def list_templates(self):
|
|
||||||
result = set()
|
|
||||||
loader = self.app.jinja_loader
|
|
||||||
if loader is not None:
|
|
||||||
result.update(loader.list_templates())
|
|
||||||
|
|
||||||
for name, blueprint in self.app.blueprints.iteritems():
|
|
||||||
loader = blueprint.jinja_loader
|
|
||||||
if loader is not None:
|
|
||||||
for template in loader.list_templates():
|
|
||||||
prefix = ''
|
|
||||||
if blueprint_is_module(blueprint):
|
|
||||||
prefix = name + '/'
|
|
||||||
result.add(prefix + template)
|
|
||||||
|
|
||||||
return list(result)
|
|
||||||
|
|
||||||
|
|
||||||
def _render(template, context, app):
|
|
||||||
"""Renders the template and fires the signal"""
|
|
||||||
rv = template.render(context)
|
|
||||||
template_rendered.send(app, template=template, context=context)
|
|
||||||
return rv
|
|
||||||
|
|
||||||
|
|
||||||
def render_template(template_name_or_list, **context):
|
|
||||||
"""Renders a template from the template folder with the given
|
|
||||||
context.
|
|
||||||
|
|
||||||
:param template_name_or_list: the name of the template to be
|
|
||||||
rendered, or an iterable with template names
|
|
||||||
the first one existing will be rendered
|
|
||||||
:param context: the variables that should be available in the
|
|
||||||
context of the template.
|
|
||||||
"""
|
|
||||||
ctx = _request_ctx_stack.top
|
|
||||||
ctx.app.update_template_context(context)
|
|
||||||
return _render(ctx.app.jinja_env.get_or_select_template(template_name_or_list),
|
|
||||||
context, ctx.app)
|
|
||||||
|
|
||||||
|
|
||||||
def render_template_string(source, **context):
|
|
||||||
"""Renders a template from the given template source string
|
|
||||||
with the given context.
|
|
||||||
|
|
||||||
:param template_name: the sourcecode of the template to be
|
|
||||||
rendered
|
|
||||||
:param context: the variables that should be available in the
|
|
||||||
context of the template.
|
|
||||||
"""
|
|
||||||
ctx = _request_ctx_stack.top
|
|
||||||
ctx.app.update_template_context(context)
|
|
||||||
return _render(ctx.app.jinja_env.from_string(source),
|
|
||||||
context, ctx.app)
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.testing
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Implements test support helpers. This module is lazily imported
|
|
||||||
and usually not used in production environments.
|
|
||||||
|
|
||||||
:copyright: (c) 2011 by Armin Ronacher.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
from contextlib import contextmanager
|
|
||||||
from werkzeug.test import Client, EnvironBuilder
|
|
||||||
from flask import _request_ctx_stack
|
|
||||||
|
|
||||||
|
|
||||||
def make_test_environ_builder(app, path='/', base_url=None, *args, **kwargs):
|
|
||||||
"""Creates a new test builder with some application defaults thrown in."""
|
|
||||||
http_host = app.config.get('SERVER_NAME')
|
|
||||||
app_root = app.config.get('APPLICATION_ROOT')
|
|
||||||
if base_url is None:
|
|
||||||
base_url = 'http://%s/' % (http_host or 'localhost')
|
|
||||||
if app_root:
|
|
||||||
base_url += app_root.lstrip('/')
|
|
||||||
return EnvironBuilder(path, base_url, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class FlaskClient(Client):
|
|
||||||
"""Works like a regular Werkzeug test client but has some knowledge about
|
|
||||||
how Flask works to defer the cleanup of the request context stack to the
|
|
||||||
end of a with body when used in a with statement. For general information
|
|
||||||
about how to use this class refer to :class:`werkzeug.test.Client`.
|
|
||||||
|
|
||||||
Basic usage is outlined in the :ref:`testing` chapter.
|
|
||||||
"""
|
|
||||||
|
|
||||||
preserve_context = False
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def session_transaction(self, *args, **kwargs):
|
|
||||||
"""When used in combination with a with statement this opens a
|
|
||||||
session transaction. This can be used to modify the session that
|
|
||||||
the test client uses. Once the with block is left the session is
|
|
||||||
stored back.
|
|
||||||
|
|
||||||
with client.session_transaction() as session:
|
|
||||||
session['value'] = 42
|
|
||||||
|
|
||||||
Internally this is implemented by going through a temporary test
|
|
||||||
request context and since session handling could depend on
|
|
||||||
request variables this function accepts the same arguments as
|
|
||||||
:meth:`~flask.Flask.test_request_context` which are directly
|
|
||||||
passed through.
|
|
||||||
"""
|
|
||||||
if self.cookie_jar is None:
|
|
||||||
raise RuntimeError('Session transactions only make sense '
|
|
||||||
'with cookies enabled.')
|
|
||||||
app = self.application
|
|
||||||
environ_overrides = kwargs.setdefault('environ_overrides', {})
|
|
||||||
self.cookie_jar.inject_wsgi(environ_overrides)
|
|
||||||
outer_reqctx = _request_ctx_stack.top
|
|
||||||
with app.test_request_context(*args, **kwargs) as c:
|
|
||||||
sess = app.open_session(c.request)
|
|
||||||
if sess is None:
|
|
||||||
raise RuntimeError('Session backend did not open a session. '
|
|
||||||
'Check the configuration')
|
|
||||||
|
|
||||||
# Since we have to open a new request context for the session
|
|
||||||
# handling we want to make sure that we hide out own context
|
|
||||||
# from the caller. By pushing the original request context
|
|
||||||
# (or None) on top of this and popping it we get exactly that
|
|
||||||
# behavior. It's important to not use the push and pop
|
|
||||||
# methods of the actual request context object since that would
|
|
||||||
# mean that cleanup handlers are called
|
|
||||||
_request_ctx_stack.push(outer_reqctx)
|
|
||||||
try:
|
|
||||||
yield sess
|
|
||||||
finally:
|
|
||||||
_request_ctx_stack.pop()
|
|
||||||
|
|
||||||
resp = app.response_class()
|
|
||||||
if not app.session_interface.is_null_session(sess):
|
|
||||||
app.save_session(sess, resp)
|
|
||||||
headers = resp.get_wsgi_headers(c.request.environ)
|
|
||||||
self.cookie_jar.extract_wsgi(c.request.environ, headers)
|
|
||||||
|
|
||||||
def open(self, *args, **kwargs):
|
|
||||||
kwargs.setdefault('environ_overrides', {}) \
|
|
||||||
['flask._preserve_context'] = self.preserve_context
|
|
||||||
|
|
||||||
as_tuple = kwargs.pop('as_tuple', False)
|
|
||||||
buffered = kwargs.pop('buffered', False)
|
|
||||||
follow_redirects = kwargs.pop('follow_redirects', False)
|
|
||||||
builder = make_test_environ_builder(self.application, *args, **kwargs)
|
|
||||||
|
|
||||||
return Client.open(self, builder,
|
|
||||||
as_tuple=as_tuple,
|
|
||||||
buffered=buffered,
|
|
||||||
follow_redirects=follow_redirects)
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
if self.preserve_context:
|
|
||||||
raise RuntimeError('Cannot nest client invocations')
|
|
||||||
self.preserve_context = True
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, tb):
|
|
||||||
self.preserve_context = False
|
|
||||||
|
|
||||||
# on exit we want to clean up earlier. Normally the request context
|
|
||||||
# stays preserved until the next request in the same thread comes
|
|
||||||
# in. See RequestGlobals.push() for the general behavior.
|
|
||||||
top = _request_ctx_stack.top
|
|
||||||
if top is not None and top.preserved:
|
|
||||||
top.pop()
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.views
|
|
||||||
~~~~~~~~~~~
|
|
||||||
|
|
||||||
This module provides class-based views inspired by the ones in Django.
|
|
||||||
|
|
||||||
:copyright: (c) 2011 by Armin Ronacher.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
from .globals import request
|
|
||||||
|
|
||||||
|
|
||||||
http_method_funcs = frozenset(['get', 'post', 'head', 'options',
|
|
||||||
'delete', 'put', 'trace', 'patch'])
|
|
||||||
|
|
||||||
|
|
||||||
class View(object):
|
|
||||||
"""Alternative way to use view functions. A subclass has to implement
|
|
||||||
:meth:`dispatch_request` which is called with the view arguments from
|
|
||||||
the URL routing system. If :attr:`methods` is provided the methods
|
|
||||||
do not have to be passed to the :meth:`~flask.Flask.add_url_rule`
|
|
||||||
method explicitly::
|
|
||||||
|
|
||||||
class MyView(View):
|
|
||||||
methods = ['GET']
|
|
||||||
|
|
||||||
def dispatch_request(self, name):
|
|
||||||
return 'Hello %s!' % name
|
|
||||||
|
|
||||||
app.add_url_rule('/hello/<name>', view_func=MyView.as_view('myview'))
|
|
||||||
|
|
||||||
When you want to decorate a pluggable view you will have to either do that
|
|
||||||
when the view function is created (by wrapping the return value of
|
|
||||||
:meth:`as_view`) or you can use the :attr:`decorators` attribute::
|
|
||||||
|
|
||||||
class SecretView(View):
|
|
||||||
methods = ['GET']
|
|
||||||
decorators = [superuser_required]
|
|
||||||
|
|
||||||
def dispatch_request(self):
|
|
||||||
...
|
|
||||||
|
|
||||||
The decorators stored in the decorators list are applied one after another
|
|
||||||
when the view function is created. Note that you can *not* use the class
|
|
||||||
based decorators since those would decorate the view class and not the
|
|
||||||
generated view function!
|
|
||||||
"""
|
|
||||||
|
|
||||||
#: A for which methods this pluggable view can handle.
|
|
||||||
methods = None
|
|
||||||
|
|
||||||
#: The canonical way to decorate class-based views is to decorate the
|
|
||||||
#: return value of as_view(). However since this moves parts of the
|
|
||||||
#: logic from the class declaration to the place where it's hooked
|
|
||||||
#: into the routing system.
|
|
||||||
#:
|
|
||||||
#: You can place one or more decorators in this list and whenever the
|
|
||||||
#: view function is created the result is automatically decorated.
|
|
||||||
#:
|
|
||||||
#: .. versionadded:: 0.8
|
|
||||||
decorators = []
|
|
||||||
|
|
||||||
def dispatch_request(self):
|
|
||||||
"""Subclasses have to override this method to implement the
|
|
||||||
actual view function code. This method is called with all
|
|
||||||
the arguments from the URL rule.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def as_view(cls, name, *class_args, **class_kwargs):
|
|
||||||
"""Converts the class into an actual view function that can be used
|
|
||||||
with the routing system. Internally this generates a function on the
|
|
||||||
fly which will instantiate the :class:`View` on each request and call
|
|
||||||
the :meth:`dispatch_request` method on it.
|
|
||||||
|
|
||||||
The arguments passed to :meth:`as_view` are forwarded to the
|
|
||||||
constructor of the class.
|
|
||||||
"""
|
|
||||||
def view(*args, **kwargs):
|
|
||||||
self = view.view_class(*class_args, **class_kwargs)
|
|
||||||
return self.dispatch_request(*args, **kwargs)
|
|
||||||
|
|
||||||
if cls.decorators:
|
|
||||||
view.__name__ = name
|
|
||||||
view.__module__ = cls.__module__
|
|
||||||
for decorator in cls.decorators:
|
|
||||||
view = decorator(view)
|
|
||||||
|
|
||||||
# we attach the view class to the view function for two reasons:
|
|
||||||
# first of all it allows us to easily figure out what class-based
|
|
||||||
# view this thing came from, secondly it's also used for instantiating
|
|
||||||
# the view class so you can actually replace it with something else
|
|
||||||
# for testing purposes and debugging.
|
|
||||||
view.view_class = cls
|
|
||||||
view.__name__ = name
|
|
||||||
view.__doc__ = cls.__doc__
|
|
||||||
view.__module__ = cls.__module__
|
|
||||||
view.methods = cls.methods
|
|
||||||
return view
|
|
||||||
|
|
||||||
|
|
||||||
class MethodViewType(type):
|
|
||||||
|
|
||||||
def __new__(cls, name, bases, d):
|
|
||||||
rv = type.__new__(cls, name, bases, d)
|
|
||||||
if 'methods' not in d:
|
|
||||||
methods = set(rv.methods or [])
|
|
||||||
for key in d:
|
|
||||||
if key in http_method_funcs:
|
|
||||||
methods.add(key.upper())
|
|
||||||
# if we have no method at all in there we don't want to
|
|
||||||
# add a method list. (This is for instance the case for
|
|
||||||
# the baseclass or another subclass of a base method view
|
|
||||||
# that does not introduce new methods).
|
|
||||||
if methods:
|
|
||||||
rv.methods = sorted(methods)
|
|
||||||
return rv
|
|
||||||
|
|
||||||
|
|
||||||
class MethodView(View):
|
|
||||||
"""Like a regular class-based view but that dispatches requests to
|
|
||||||
particular methods. For instance if you implement a method called
|
|
||||||
:meth:`get` it means you will response to ``'GET'`` requests and
|
|
||||||
the :meth:`dispatch_request` implementation will automatically
|
|
||||||
forward your request to that. Also :attr:`options` is set for you
|
|
||||||
automatically::
|
|
||||||
|
|
||||||
class CounterAPI(MethodView):
|
|
||||||
|
|
||||||
def get(self):
|
|
||||||
return session.get('counter', 0)
|
|
||||||
|
|
||||||
def post(self):
|
|
||||||
session['counter'] = session.get('counter', 0) + 1
|
|
||||||
return 'OK'
|
|
||||||
|
|
||||||
app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter'))
|
|
||||||
"""
|
|
||||||
__metaclass__ = MethodViewType
|
|
||||||
|
|
||||||
def dispatch_request(self, *args, **kwargs):
|
|
||||||
meth = getattr(self, request.method.lower(), None)
|
|
||||||
# if the request method is HEAD and we don't have a handler for it
|
|
||||||
# retry with GET
|
|
||||||
if meth is None and request.method == 'HEAD':
|
|
||||||
meth = getattr(self, 'get', None)
|
|
||||||
assert meth is not None, 'Unimplemented method %r' % request.method
|
|
||||||
return meth(*args, **kwargs)
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.wrappers
|
|
||||||
~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Implements the WSGI wrappers (request and response).
|
|
||||||
|
|
||||||
:copyright: (c) 2011 by Armin Ronacher.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from werkzeug.wrappers import Request as RequestBase, Response as ResponseBase
|
|
||||||
from werkzeug.utils import cached_property
|
|
||||||
|
|
||||||
from .exceptions import JSONBadRequest
|
|
||||||
from .debughelpers import attach_enctype_error_multidict
|
|
||||||
from .helpers import json, _assert_have_json
|
|
||||||
from .globals import _request_ctx_stack
|
|
||||||
|
|
||||||
|
|
||||||
class Request(RequestBase):
|
|
||||||
"""The request object used by default in Flask. Remembers the
|
|
||||||
matched endpoint and view arguments.
|
|
||||||
|
|
||||||
It is what ends up as :class:`~flask.request`. If you want to replace
|
|
||||||
the request object used you can subclass this and set
|
|
||||||
:attr:`~flask.Flask.request_class` to your subclass.
|
|
||||||
|
|
||||||
The request object is a :class:`~werkzeug.wrappers.Request` subclass and
|
|
||||||
provides all of the attributes Werkzeug defines plus a few Flask
|
|
||||||
specific ones.
|
|
||||||
"""
|
|
||||||
|
|
||||||
#: the internal URL rule that matched the request. This can be
|
|
||||||
#: useful to inspect which methods are allowed for the URL from
|
|
||||||
#: a before/after handler (``request.url_rule.methods``) etc.
|
|
||||||
#:
|
|
||||||
#: .. versionadded:: 0.6
|
|
||||||
url_rule = None
|
|
||||||
|
|
||||||
#: a dict of view arguments that matched the request. If an exception
|
|
||||||
#: happened when matching, this will be `None`.
|
|
||||||
view_args = None
|
|
||||||
|
|
||||||
#: if matching the URL failed, this is the exception that will be
|
|
||||||
#: raised / was raised as part of the request handling. This is
|
|
||||||
#: usually a :exc:`~werkzeug.exceptions.NotFound` exception or
|
|
||||||
#: something similar.
|
|
||||||
routing_exception = None
|
|
||||||
|
|
||||||
# switched by the request context until 1.0 to opt in deprecated
|
|
||||||
# module functionality
|
|
||||||
_is_old_module = False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def max_content_length(self):
|
|
||||||
"""Read-only view of the `MAX_CONTENT_LENGTH` config key."""
|
|
||||||
ctx = _request_ctx_stack.top
|
|
||||||
if ctx is not None:
|
|
||||||
return ctx.app.config['MAX_CONTENT_LENGTH']
|
|
||||||
|
|
||||||
@property
|
|
||||||
def endpoint(self):
|
|
||||||
"""The endpoint that matched the request. This in combination with
|
|
||||||
:attr:`view_args` can be used to reconstruct the same or a
|
|
||||||
modified URL. If an exception happened when matching, this will
|
|
||||||
be `None`.
|
|
||||||
"""
|
|
||||||
if self.url_rule is not None:
|
|
||||||
return self.url_rule.endpoint
|
|
||||||
|
|
||||||
@property
|
|
||||||
def module(self):
|
|
||||||
"""The name of the current module if the request was dispatched
|
|
||||||
to an actual module. This is deprecated functionality, use blueprints
|
|
||||||
instead.
|
|
||||||
"""
|
|
||||||
from warnings import warn
|
|
||||||
warn(DeprecationWarning('modules were deprecated in favor of '
|
|
||||||
'blueprints. Use request.blueprint '
|
|
||||||
'instead.'), stacklevel=2)
|
|
||||||
if self._is_old_module:
|
|
||||||
return self.blueprint
|
|
||||||
|
|
||||||
@property
|
|
||||||
def blueprint(self):
|
|
||||||
"""The name of the current blueprint"""
|
|
||||||
if self.url_rule and '.' in self.url_rule.endpoint:
|
|
||||||
return self.url_rule.endpoint.rsplit('.', 1)[0]
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def json(self):
|
|
||||||
"""If the mimetype is `application/json` this will contain the
|
|
||||||
parsed JSON data. Otherwise this will be `None`.
|
|
||||||
|
|
||||||
This requires Python 2.6 or an installed version of simplejson.
|
|
||||||
"""
|
|
||||||
if __debug__:
|
|
||||||
_assert_have_json()
|
|
||||||
if self.mimetype == 'application/json':
|
|
||||||
request_charset = self.mimetype_params.get('charset')
|
|
||||||
try:
|
|
||||||
if request_charset is not None:
|
|
||||||
return json.loads(self.data, encoding=request_charset)
|
|
||||||
return json.loads(self.data)
|
|
||||||
except ValueError, e:
|
|
||||||
return self.on_json_loading_failed(e)
|
|
||||||
|
|
||||||
def on_json_loading_failed(self, e):
|
|
||||||
"""Called if decoding of the JSON data failed. The return value of
|
|
||||||
this method is used by :attr:`json` when an error ocurred. The default
|
|
||||||
implementation raises a :class:`JSONBadRequest`, which is a subclass of
|
|
||||||
:class:`~werkzeug.exceptions.BadRequest` which sets the
|
|
||||||
``Content-Type`` to ``application/json`` and provides a JSON-formatted
|
|
||||||
error description::
|
|
||||||
|
|
||||||
{"description": "The browser (or proxy) sent a request that \
|
|
||||||
this server could not understand."}
|
|
||||||
|
|
||||||
.. versionchanged:: 0.9
|
|
||||||
Return a :class:`JSONBadRequest` instead of a
|
|
||||||
:class:`~werkzeug.exceptions.BadRequest` by default.
|
|
||||||
|
|
||||||
.. versionadded:: 0.8
|
|
||||||
"""
|
|
||||||
raise JSONBadRequest()
|
|
||||||
|
|
||||||
def _load_form_data(self):
|
|
||||||
RequestBase._load_form_data(self)
|
|
||||||
|
|
||||||
# in debug mode we're replacing the files multidict with an ad-hoc
|
|
||||||
# subclass that raises a different error for key errors.
|
|
||||||
ctx = _request_ctx_stack.top
|
|
||||||
if ctx is not None and ctx.app.debug and \
|
|
||||||
self.mimetype != 'multipart/form-data' and not self.files:
|
|
||||||
attach_enctype_error_multidict(self)
|
|
||||||
|
|
||||||
|
|
||||||
class Response(ResponseBase):
|
|
||||||
"""The response object that is used by default in Flask. Works like the
|
|
||||||
response object from Werkzeug but is set to have an HTML mimetype by
|
|
||||||
default. Quite often you don't have to create this object yourself because
|
|
||||||
:meth:`~flask.Flask.make_response` will take care of that for you.
|
|
||||||
|
|
||||||
If you want to replace the response object used you can subclass this and
|
|
||||||
set :attr:`~flask.Flask.response_class` to your subclass.
|
|
||||||
"""
|
|
||||||
default_mimetype = 'text/html'
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
jinja2
|
|
||||||
~~~~~~
|
|
||||||
|
|
||||||
Jinja2 is a template engine written in pure Python. It provides a
|
|
||||||
Django inspired non-XML syntax but supports inline expressions and
|
|
||||||
an optional sandboxed environment.
|
|
||||||
|
|
||||||
Nutshell
|
|
||||||
--------
|
|
||||||
|
|
||||||
Here a small example of a Jinja2 template::
|
|
||||||
|
|
||||||
{% extends 'base.html' %}
|
|
||||||
{% block title %}Memberlist{% endblock %}
|
|
||||||
{% block content %}
|
|
||||||
<ul>
|
|
||||||
{% for user in users %}
|
|
||||||
<li><a href="{{ user.url }}">{{ user.username }}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
:copyright: (c) 2010 by the Jinja Team.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
__docformat__ = 'restructuredtext en'
|
|
||||||
__version__ = '2.6'
|
|
||||||
|
|
||||||
# high level interface
|
|
||||||
from jinja2.environment import Environment, Template
|
|
||||||
|
|
||||||
# loaders
|
|
||||||
from jinja2.loaders import BaseLoader, FileSystemLoader, PackageLoader, \
|
|
||||||
DictLoader, FunctionLoader, PrefixLoader, ChoiceLoader, \
|
|
||||||
ModuleLoader
|
|
||||||
|
|
||||||
# bytecode caches
|
|
||||||
from jinja2.bccache import BytecodeCache, FileSystemBytecodeCache, \
|
|
||||||
MemcachedBytecodeCache
|
|
||||||
|
|
||||||
# undefined types
|
|
||||||
from jinja2.runtime import Undefined, DebugUndefined, StrictUndefined
|
|
||||||
|
|
||||||
# exceptions
|
|
||||||
from jinja2.exceptions import TemplateError, UndefinedError, \
|
|
||||||
TemplateNotFound, TemplatesNotFound, TemplateSyntaxError, \
|
|
||||||
TemplateAssertionError
|
|
||||||
|
|
||||||
# decorators and public utilities
|
|
||||||
from jinja2.filters import environmentfilter, contextfilter, \
|
|
||||||
evalcontextfilter
|
|
||||||
from jinja2.utils import Markup, escape, clear_caches, \
|
|
||||||
environmentfunction, evalcontextfunction, contextfunction, \
|
|
||||||
is_undefined
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'Environment', 'Template', 'BaseLoader', 'FileSystemLoader',
|
|
||||||
'PackageLoader', 'DictLoader', 'FunctionLoader', 'PrefixLoader',
|
|
||||||
'ChoiceLoader', 'BytecodeCache', 'FileSystemBytecodeCache',
|
|
||||||
'MemcachedBytecodeCache', 'Undefined', 'DebugUndefined',
|
|
||||||
'StrictUndefined', 'TemplateError', 'UndefinedError', 'TemplateNotFound',
|
|
||||||
'TemplatesNotFound', 'TemplateSyntaxError', 'TemplateAssertionError',
|
|
||||||
'ModuleLoader', 'environmentfilter', 'contextfilter', 'Markup', 'escape',
|
|
||||||
'environmentfunction', 'contextfunction', 'clear_caches', 'is_undefined',
|
|
||||||
'evalcontextfilter', 'evalcontextfunction'
|
|
||||||
]
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
/**
|
|
||||||
* jinja2._debugsupport
|
|
||||||
* ~~~~~~~~~~~~~~~~~~~~
|
|
||||||
*
|
|
||||||
* C implementation of `tb_set_next`.
|
|
||||||
*
|
|
||||||
* :copyright: (c) 2010 by the Jinja Team.
|
|
||||||
* :license: BSD.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <Python.h>
|
|
||||||
|
|
||||||
|
|
||||||
static PyObject*
|
|
||||||
tb_set_next(PyObject *self, PyObject *args)
|
|
||||||
{
|
|
||||||
PyTracebackObject *tb, *old;
|
|
||||||
PyObject *next;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O!O:tb_set_next", &PyTraceBack_Type, &tb, &next))
|
|
||||||
return NULL;
|
|
||||||
if (next == Py_None)
|
|
||||||
next = NULL;
|
|
||||||
else if (!PyTraceBack_Check(next)) {
|
|
||||||
PyErr_SetString(PyExc_TypeError,
|
|
||||||
"tb_set_next arg 2 must be traceback or None");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
Py_INCREF(next);
|
|
||||||
|
|
||||||
old = tb->tb_next;
|
|
||||||
tb->tb_next = (PyTracebackObject*)next;
|
|
||||||
Py_XDECREF(old);
|
|
||||||
|
|
||||||
Py_INCREF(Py_None);
|
|
||||||
return Py_None;
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyMethodDef module_methods[] = {
|
|
||||||
{"tb_set_next", (PyCFunction)tb_set_next, METH_VARARGS,
|
|
||||||
"Set the tb_next member of a traceback object."},
|
|
||||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#if PY_MAJOR_VERSION < 3
|
|
||||||
|
|
||||||
#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */
|
|
||||||
#define PyMODINIT_FUNC void
|
|
||||||
#endif
|
|
||||||
PyMODINIT_FUNC
|
|
||||||
init_debugsupport(void)
|
|
||||||
{
|
|
||||||
Py_InitModule3("jinja2._debugsupport", module_methods, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
#else /* Python 3.x module initialization */
|
|
||||||
|
|
||||||
static struct PyModuleDef module_definition = {
|
|
||||||
PyModuleDef_HEAD_INIT,
|
|
||||||
"jinja2._debugsupport",
|
|
||||||
NULL,
|
|
||||||
-1,
|
|
||||||
module_methods,
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
PyMODINIT_FUNC
|
|
||||||
PyInit__debugsupport(void)
|
|
||||||
{
|
|
||||||
return PyModule_Create(&module_definition);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,225 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
markupsafe
|
|
||||||
~~~~~~~~~~
|
|
||||||
|
|
||||||
Implements a Markup string.
|
|
||||||
|
|
||||||
:copyright: (c) 2010 by Armin Ronacher.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
import re
|
|
||||||
from itertools import imap
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['Markup', 'soft_unicode', 'escape', 'escape_silent']
|
|
||||||
|
|
||||||
|
|
||||||
_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
|
|
||||||
_entity_re = re.compile(r'&([^;]+);')
|
|
||||||
|
|
||||||
|
|
||||||
class Markup(unicode):
|
|
||||||
r"""Marks a string as being safe for inclusion in HTML/XML output without
|
|
||||||
needing to be escaped. This implements the `__html__` interface a couple
|
|
||||||
of frameworks and web applications use. :class:`Markup` is a direct
|
|
||||||
subclass of `unicode` and provides all the methods of `unicode` just that
|
|
||||||
it escapes arguments passed and always returns `Markup`.
|
|
||||||
|
|
||||||
The `escape` function returns markup objects so that double escaping can't
|
|
||||||
happen.
|
|
||||||
|
|
||||||
The constructor of the :class:`Markup` class can be used for three
|
|
||||||
different things: When passed an unicode object it's assumed to be safe,
|
|
||||||
when passed an object with an HTML representation (has an `__html__`
|
|
||||||
method) that representation is used, otherwise the object passed is
|
|
||||||
converted into a unicode string and then assumed to be safe:
|
|
||||||
|
|
||||||
>>> Markup("Hello <em>World</em>!")
|
|
||||||
Markup(u'Hello <em>World</em>!')
|
|
||||||
>>> class Foo(object):
|
|
||||||
... def __html__(self):
|
|
||||||
... return '<a href="#">foo</a>'
|
|
||||||
...
|
|
||||||
>>> Markup(Foo())
|
|
||||||
Markup(u'<a href="#">foo</a>')
|
|
||||||
|
|
||||||
If you want object passed being always treated as unsafe you can use the
|
|
||||||
:meth:`escape` classmethod to create a :class:`Markup` object:
|
|
||||||
|
|
||||||
>>> Markup.escape("Hello <em>World</em>!")
|
|
||||||
Markup(u'Hello <em>World</em>!')
|
|
||||||
|
|
||||||
Operations on a markup string are markup aware which means that all
|
|
||||||
arguments are passed through the :func:`escape` function:
|
|
||||||
|
|
||||||
>>> em = Markup("<em>%s</em>")
|
|
||||||
>>> em % "foo & bar"
|
|
||||||
Markup(u'<em>foo & bar</em>')
|
|
||||||
>>> strong = Markup("<strong>%(text)s</strong>")
|
|
||||||
>>> strong % {'text': '<blink>hacker here</blink>'}
|
|
||||||
Markup(u'<strong><blink>hacker here</blink></strong>')
|
|
||||||
>>> Markup("<em>Hello</em> ") + "<foo>"
|
|
||||||
Markup(u'<em>Hello</em> <foo>')
|
|
||||||
"""
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
def __new__(cls, base=u'', encoding=None, errors='strict'):
|
|
||||||
if hasattr(base, '__html__'):
|
|
||||||
base = base.__html__()
|
|
||||||
if encoding is None:
|
|
||||||
return unicode.__new__(cls, base)
|
|
||||||
return unicode.__new__(cls, base, encoding, errors)
|
|
||||||
|
|
||||||
def __html__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __add__(self, other):
|
|
||||||
if hasattr(other, '__html__') or isinstance(other, basestring):
|
|
||||||
return self.__class__(unicode(self) + unicode(escape(other)))
|
|
||||||
return NotImplemented
|
|
||||||
|
|
||||||
def __radd__(self, other):
|
|
||||||
if hasattr(other, '__html__') or isinstance(other, basestring):
|
|
||||||
return self.__class__(unicode(escape(other)) + unicode(self))
|
|
||||||
return NotImplemented
|
|
||||||
|
|
||||||
def __mul__(self, num):
|
|
||||||
if isinstance(num, (int, long)):
|
|
||||||
return self.__class__(unicode.__mul__(self, num))
|
|
||||||
return NotImplemented
|
|
||||||
__rmul__ = __mul__
|
|
||||||
|
|
||||||
def __mod__(self, arg):
|
|
||||||
if isinstance(arg, tuple):
|
|
||||||
arg = tuple(imap(_MarkupEscapeHelper, arg))
|
|
||||||
else:
|
|
||||||
arg = _MarkupEscapeHelper(arg)
|
|
||||||
return self.__class__(unicode.__mod__(self, arg))
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '%s(%s)' % (
|
|
||||||
self.__class__.__name__,
|
|
||||||
unicode.__repr__(self)
|
|
||||||
)
|
|
||||||
|
|
||||||
def join(self, seq):
|
|
||||||
return self.__class__(unicode.join(self, imap(escape, seq)))
|
|
||||||
join.__doc__ = unicode.join.__doc__
|
|
||||||
|
|
||||||
def split(self, *args, **kwargs):
|
|
||||||
return map(self.__class__, unicode.split(self, *args, **kwargs))
|
|
||||||
split.__doc__ = unicode.split.__doc__
|
|
||||||
|
|
||||||
def rsplit(self, *args, **kwargs):
|
|
||||||
return map(self.__class__, unicode.rsplit(self, *args, **kwargs))
|
|
||||||
rsplit.__doc__ = unicode.rsplit.__doc__
|
|
||||||
|
|
||||||
def splitlines(self, *args, **kwargs):
|
|
||||||
return map(self.__class__, unicode.splitlines(self, *args, **kwargs))
|
|
||||||
splitlines.__doc__ = unicode.splitlines.__doc__
|
|
||||||
|
|
||||||
def unescape(self):
|
|
||||||
r"""Unescape markup again into an unicode string. This also resolves
|
|
||||||
known HTML4 and XHTML entities:
|
|
||||||
|
|
||||||
>>> Markup("Main » <em>About</em>").unescape()
|
|
||||||
u'Main \xbb <em>About</em>'
|
|
||||||
"""
|
|
||||||
from jinja2._markupsafe._constants import HTML_ENTITIES
|
|
||||||
def handle_match(m):
|
|
||||||
name = m.group(1)
|
|
||||||
if name in HTML_ENTITIES:
|
|
||||||
return unichr(HTML_ENTITIES[name])
|
|
||||||
try:
|
|
||||||
if name[:2] in ('#x', '#X'):
|
|
||||||
return unichr(int(name[2:], 16))
|
|
||||||
elif name.startswith('#'):
|
|
||||||
return unichr(int(name[1:]))
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
return u''
|
|
||||||
return _entity_re.sub(handle_match, unicode(self))
|
|
||||||
|
|
||||||
def striptags(self):
|
|
||||||
r"""Unescape markup into an unicode string and strip all tags. This
|
|
||||||
also resolves known HTML4 and XHTML entities. Whitespace is
|
|
||||||
normalized to one:
|
|
||||||
|
|
||||||
>>> Markup("Main » <em>About</em>").striptags()
|
|
||||||
u'Main \xbb About'
|
|
||||||
"""
|
|
||||||
stripped = u' '.join(_striptags_re.sub('', self).split())
|
|
||||||
return Markup(stripped).unescape()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def escape(cls, s):
|
|
||||||
"""Escape the string. Works like :func:`escape` with the difference
|
|
||||||
that for subclasses of :class:`Markup` this function would return the
|
|
||||||
correct subclass.
|
|
||||||
"""
|
|
||||||
rv = escape(s)
|
|
||||||
if rv.__class__ is not cls:
|
|
||||||
return cls(rv)
|
|
||||||
return rv
|
|
||||||
|
|
||||||
def make_wrapper(name):
|
|
||||||
orig = getattr(unicode, name)
|
|
||||||
def func(self, *args, **kwargs):
|
|
||||||
args = _escape_argspec(list(args), enumerate(args))
|
|
||||||
_escape_argspec(kwargs, kwargs.iteritems())
|
|
||||||
return self.__class__(orig(self, *args, **kwargs))
|
|
||||||
func.__name__ = orig.__name__
|
|
||||||
func.__doc__ = orig.__doc__
|
|
||||||
return func
|
|
||||||
|
|
||||||
for method in '__getitem__', 'capitalize', \
|
|
||||||
'title', 'lower', 'upper', 'replace', 'ljust', \
|
|
||||||
'rjust', 'lstrip', 'rstrip', 'center', 'strip', \
|
|
||||||
'translate', 'expandtabs', 'swapcase', 'zfill':
|
|
||||||
locals()[method] = make_wrapper(method)
|
|
||||||
|
|
||||||
# new in python 2.5
|
|
||||||
if hasattr(unicode, 'partition'):
|
|
||||||
partition = make_wrapper('partition'),
|
|
||||||
rpartition = make_wrapper('rpartition')
|
|
||||||
|
|
||||||
# new in python 2.6
|
|
||||||
if hasattr(unicode, 'format'):
|
|
||||||
format = make_wrapper('format')
|
|
||||||
|
|
||||||
# not in python 3
|
|
||||||
if hasattr(unicode, '__getslice__'):
|
|
||||||
__getslice__ = make_wrapper('__getslice__')
|
|
||||||
|
|
||||||
del method, make_wrapper
|
|
||||||
|
|
||||||
|
|
||||||
def _escape_argspec(obj, iterable):
|
|
||||||
"""Helper for various string-wrapped functions."""
|
|
||||||
for key, value in iterable:
|
|
||||||
if hasattr(value, '__html__') or isinstance(value, basestring):
|
|
||||||
obj[key] = escape(value)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
|
||||||
class _MarkupEscapeHelper(object):
|
|
||||||
"""Helper for Markup.__mod__"""
|
|
||||||
|
|
||||||
def __init__(self, obj):
|
|
||||||
self.obj = obj
|
|
||||||
|
|
||||||
__getitem__ = lambda s, x: _MarkupEscapeHelper(s.obj[x])
|
|
||||||
__str__ = lambda s: str(escape(s.obj))
|
|
||||||
__unicode__ = lambda s: unicode(escape(s.obj))
|
|
||||||
__repr__ = lambda s: str(escape(repr(s.obj)))
|
|
||||||
__int__ = lambda s: int(s.obj)
|
|
||||||
__float__ = lambda s: float(s.obj)
|
|
||||||
|
|
||||||
|
|
||||||
# we have to import it down here as the speedups and native
|
|
||||||
# modules imports the markup type which is define above.
|
|
||||||
try:
|
|
||||||
from jinja2._markupsafe._speedups import escape, escape_silent, soft_unicode
|
|
||||||
except ImportError:
|
|
||||||
from jinja2._markupsafe._native import escape, escape_silent, soft_unicode
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
jinja2._markupsafe._bundle
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
This script pulls in markupsafe from a source folder and
|
|
||||||
bundles it with Jinja2. It does not pull in the speedups
|
|
||||||
module though.
|
|
||||||
|
|
||||||
:copyright: Copyright 2010 by the Jinja team, see AUTHORS.
|
|
||||||
:license: BSD, see LICENSE for details.
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
|
|
||||||
|
|
||||||
def rewrite_imports(lines):
|
|
||||||
for idx, line in enumerate(lines):
|
|
||||||
new_line = re.sub(r'(import|from)\s+markupsafe\b',
|
|
||||||
r'\1 jinja2._markupsafe', line)
|
|
||||||
if new_line != line:
|
|
||||||
lines[idx] = new_line
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if len(sys.argv) != 2:
|
|
||||||
print 'error: only argument is path to markupsafe'
|
|
||||||
sys.exit(1)
|
|
||||||
basedir = os.path.dirname(__file__)
|
|
||||||
markupdir = sys.argv[1]
|
|
||||||
for filename in os.listdir(markupdir):
|
|
||||||
if filename.endswith('.py'):
|
|
||||||
f = open(os.path.join(markupdir, filename))
|
|
||||||
try:
|
|
||||||
lines = list(f)
|
|
||||||
finally:
|
|
||||||
f.close()
|
|
||||||
rewrite_imports(lines)
|
|
||||||
f = open(os.path.join(basedir, filename), 'w')
|
|
||||||
try:
|
|
||||||
for line in lines:
|
|
||||||
f.write(line)
|
|
||||||
finally:
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
@@ -1,267 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
markupsafe._constants
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Highlevel implementation of the Markup string.
|
|
||||||
|
|
||||||
:copyright: (c) 2010 by Armin Ronacher.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
HTML_ENTITIES = {
|
|
||||||
'AElig': 198,
|
|
||||||
'Aacute': 193,
|
|
||||||
'Acirc': 194,
|
|
||||||
'Agrave': 192,
|
|
||||||
'Alpha': 913,
|
|
||||||
'Aring': 197,
|
|
||||||
'Atilde': 195,
|
|
||||||
'Auml': 196,
|
|
||||||
'Beta': 914,
|
|
||||||
'Ccedil': 199,
|
|
||||||
'Chi': 935,
|
|
||||||
'Dagger': 8225,
|
|
||||||
'Delta': 916,
|
|
||||||
'ETH': 208,
|
|
||||||
'Eacute': 201,
|
|
||||||
'Ecirc': 202,
|
|
||||||
'Egrave': 200,
|
|
||||||
'Epsilon': 917,
|
|
||||||
'Eta': 919,
|
|
||||||
'Euml': 203,
|
|
||||||
'Gamma': 915,
|
|
||||||
'Iacute': 205,
|
|
||||||
'Icirc': 206,
|
|
||||||
'Igrave': 204,
|
|
||||||
'Iota': 921,
|
|
||||||
'Iuml': 207,
|
|
||||||
'Kappa': 922,
|
|
||||||
'Lambda': 923,
|
|
||||||
'Mu': 924,
|
|
||||||
'Ntilde': 209,
|
|
||||||
'Nu': 925,
|
|
||||||
'OElig': 338,
|
|
||||||
'Oacute': 211,
|
|
||||||
'Ocirc': 212,
|
|
||||||
'Ograve': 210,
|
|
||||||
'Omega': 937,
|
|
||||||
'Omicron': 927,
|
|
||||||
'Oslash': 216,
|
|
||||||
'Otilde': 213,
|
|
||||||
'Ouml': 214,
|
|
||||||
'Phi': 934,
|
|
||||||
'Pi': 928,
|
|
||||||
'Prime': 8243,
|
|
||||||
'Psi': 936,
|
|
||||||
'Rho': 929,
|
|
||||||
'Scaron': 352,
|
|
||||||
'Sigma': 931,
|
|
||||||
'THORN': 222,
|
|
||||||
'Tau': 932,
|
|
||||||
'Theta': 920,
|
|
||||||
'Uacute': 218,
|
|
||||||
'Ucirc': 219,
|
|
||||||
'Ugrave': 217,
|
|
||||||
'Upsilon': 933,
|
|
||||||
'Uuml': 220,
|
|
||||||
'Xi': 926,
|
|
||||||
'Yacute': 221,
|
|
||||||
'Yuml': 376,
|
|
||||||
'Zeta': 918,
|
|
||||||
'aacute': 225,
|
|
||||||
'acirc': 226,
|
|
||||||
'acute': 180,
|
|
||||||
'aelig': 230,
|
|
||||||
'agrave': 224,
|
|
||||||
'alefsym': 8501,
|
|
||||||
'alpha': 945,
|
|
||||||
'amp': 38,
|
|
||||||
'and': 8743,
|
|
||||||
'ang': 8736,
|
|
||||||
'apos': 39,
|
|
||||||
'aring': 229,
|
|
||||||
'asymp': 8776,
|
|
||||||
'atilde': 227,
|
|
||||||
'auml': 228,
|
|
||||||
'bdquo': 8222,
|
|
||||||
'beta': 946,
|
|
||||||
'brvbar': 166,
|
|
||||||
'bull': 8226,
|
|
||||||
'cap': 8745,
|
|
||||||
'ccedil': 231,
|
|
||||||
'cedil': 184,
|
|
||||||
'cent': 162,
|
|
||||||
'chi': 967,
|
|
||||||
'circ': 710,
|
|
||||||
'clubs': 9827,
|
|
||||||
'cong': 8773,
|
|
||||||
'copy': 169,
|
|
||||||
'crarr': 8629,
|
|
||||||
'cup': 8746,
|
|
||||||
'curren': 164,
|
|
||||||
'dArr': 8659,
|
|
||||||
'dagger': 8224,
|
|
||||||
'darr': 8595,
|
|
||||||
'deg': 176,
|
|
||||||
'delta': 948,
|
|
||||||
'diams': 9830,
|
|
||||||
'divide': 247,
|
|
||||||
'eacute': 233,
|
|
||||||
'ecirc': 234,
|
|
||||||
'egrave': 232,
|
|
||||||
'empty': 8709,
|
|
||||||
'emsp': 8195,
|
|
||||||
'ensp': 8194,
|
|
||||||
'epsilon': 949,
|
|
||||||
'equiv': 8801,
|
|
||||||
'eta': 951,
|
|
||||||
'eth': 240,
|
|
||||||
'euml': 235,
|
|
||||||
'euro': 8364,
|
|
||||||
'exist': 8707,
|
|
||||||
'fnof': 402,
|
|
||||||
'forall': 8704,
|
|
||||||
'frac12': 189,
|
|
||||||
'frac14': 188,
|
|
||||||
'frac34': 190,
|
|
||||||
'frasl': 8260,
|
|
||||||
'gamma': 947,
|
|
||||||
'ge': 8805,
|
|
||||||
'gt': 62,
|
|
||||||
'hArr': 8660,
|
|
||||||
'harr': 8596,
|
|
||||||
'hearts': 9829,
|
|
||||||
'hellip': 8230,
|
|
||||||
'iacute': 237,
|
|
||||||
'icirc': 238,
|
|
||||||
'iexcl': 161,
|
|
||||||
'igrave': 236,
|
|
||||||
'image': 8465,
|
|
||||||
'infin': 8734,
|
|
||||||
'int': 8747,
|
|
||||||
'iota': 953,
|
|
||||||
'iquest': 191,
|
|
||||||
'isin': 8712,
|
|
||||||
'iuml': 239,
|
|
||||||
'kappa': 954,
|
|
||||||
'lArr': 8656,
|
|
||||||
'lambda': 955,
|
|
||||||
'lang': 9001,
|
|
||||||
'laquo': 171,
|
|
||||||
'larr': 8592,
|
|
||||||
'lceil': 8968,
|
|
||||||
'ldquo': 8220,
|
|
||||||
'le': 8804,
|
|
||||||
'lfloor': 8970,
|
|
||||||
'lowast': 8727,
|
|
||||||
'loz': 9674,
|
|
||||||
'lrm': 8206,
|
|
||||||
'lsaquo': 8249,
|
|
||||||
'lsquo': 8216,
|
|
||||||
'lt': 60,
|
|
||||||
'macr': 175,
|
|
||||||
'mdash': 8212,
|
|
||||||
'micro': 181,
|
|
||||||
'middot': 183,
|
|
||||||
'minus': 8722,
|
|
||||||
'mu': 956,
|
|
||||||
'nabla': 8711,
|
|
||||||
'nbsp': 160,
|
|
||||||
'ndash': 8211,
|
|
||||||
'ne': 8800,
|
|
||||||
'ni': 8715,
|
|
||||||
'not': 172,
|
|
||||||
'notin': 8713,
|
|
||||||
'nsub': 8836,
|
|
||||||
'ntilde': 241,
|
|
||||||
'nu': 957,
|
|
||||||
'oacute': 243,
|
|
||||||
'ocirc': 244,
|
|
||||||
'oelig': 339,
|
|
||||||
'ograve': 242,
|
|
||||||
'oline': 8254,
|
|
||||||
'omega': 969,
|
|
||||||
'omicron': 959,
|
|
||||||
'oplus': 8853,
|
|
||||||
'or': 8744,
|
|
||||||
'ordf': 170,
|
|
||||||
'ordm': 186,
|
|
||||||
'oslash': 248,
|
|
||||||
'otilde': 245,
|
|
||||||
'otimes': 8855,
|
|
||||||
'ouml': 246,
|
|
||||||
'para': 182,
|
|
||||||
'part': 8706,
|
|
||||||
'permil': 8240,
|
|
||||||
'perp': 8869,
|
|
||||||
'phi': 966,
|
|
||||||
'pi': 960,
|
|
||||||
'piv': 982,
|
|
||||||
'plusmn': 177,
|
|
||||||
'pound': 163,
|
|
||||||
'prime': 8242,
|
|
||||||
'prod': 8719,
|
|
||||||
'prop': 8733,
|
|
||||||
'psi': 968,
|
|
||||||
'quot': 34,
|
|
||||||
'rArr': 8658,
|
|
||||||
'radic': 8730,
|
|
||||||
'rang': 9002,
|
|
||||||
'raquo': 187,
|
|
||||||
'rarr': 8594,
|
|
||||||
'rceil': 8969,
|
|
||||||
'rdquo': 8221,
|
|
||||||
'real': 8476,
|
|
||||||
'reg': 174,
|
|
||||||
'rfloor': 8971,
|
|
||||||
'rho': 961,
|
|
||||||
'rlm': 8207,
|
|
||||||
'rsaquo': 8250,
|
|
||||||
'rsquo': 8217,
|
|
||||||
'sbquo': 8218,
|
|
||||||
'scaron': 353,
|
|
||||||
'sdot': 8901,
|
|
||||||
'sect': 167,
|
|
||||||
'shy': 173,
|
|
||||||
'sigma': 963,
|
|
||||||
'sigmaf': 962,
|
|
||||||
'sim': 8764,
|
|
||||||
'spades': 9824,
|
|
||||||
'sub': 8834,
|
|
||||||
'sube': 8838,
|
|
||||||
'sum': 8721,
|
|
||||||
'sup': 8835,
|
|
||||||
'sup1': 185,
|
|
||||||
'sup2': 178,
|
|
||||||
'sup3': 179,
|
|
||||||
'supe': 8839,
|
|
||||||
'szlig': 223,
|
|
||||||
'tau': 964,
|
|
||||||
'there4': 8756,
|
|
||||||
'theta': 952,
|
|
||||||
'thetasym': 977,
|
|
||||||
'thinsp': 8201,
|
|
||||||
'thorn': 254,
|
|
||||||
'tilde': 732,
|
|
||||||
'times': 215,
|
|
||||||
'trade': 8482,
|
|
||||||
'uArr': 8657,
|
|
||||||
'uacute': 250,
|
|
||||||
'uarr': 8593,
|
|
||||||
'ucirc': 251,
|
|
||||||
'ugrave': 249,
|
|
||||||
'uml': 168,
|
|
||||||
'upsih': 978,
|
|
||||||
'upsilon': 965,
|
|
||||||
'uuml': 252,
|
|
||||||
'weierp': 8472,
|
|
||||||
'xi': 958,
|
|
||||||
'yacute': 253,
|
|
||||||
'yen': 165,
|
|
||||||
'yuml': 255,
|
|
||||||
'zeta': 950,
|
|
||||||
'zwj': 8205,
|
|
||||||
'zwnj': 8204
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
markupsafe._native
|
|
||||||
~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Native Python implementation the C module is not compiled.
|
|
||||||
|
|
||||||
:copyright: (c) 2010 by Armin Ronacher.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
from jinja2._markupsafe import Markup
|
|
||||||
|
|
||||||
|
|
||||||
def escape(s):
|
|
||||||
"""Convert the characters &, <, >, ' and " in string s to HTML-safe
|
|
||||||
sequences. Use this if you need to display text that might contain
|
|
||||||
such characters in HTML. Marks return value as markup string.
|
|
||||||
"""
|
|
||||||
if hasattr(s, '__html__'):
|
|
||||||
return s.__html__()
|
|
||||||
return Markup(unicode(s)
|
|
||||||
.replace('&', '&')
|
|
||||||
.replace('>', '>')
|
|
||||||
.replace('<', '<')
|
|
||||||
.replace("'", ''')
|
|
||||||
.replace('"', '"')
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def escape_silent(s):
|
|
||||||
"""Like :func:`escape` but converts `None` into an empty
|
|
||||||
markup string.
|
|
||||||
"""
|
|
||||||
if s is None:
|
|
||||||
return Markup()
|
|
||||||
return escape(s)
|
|
||||||
|
|
||||||
|
|
||||||
def soft_unicode(s):
|
|
||||||
"""Make a string unicode if it isn't already. That way a markup
|
|
||||||
string is not converted back to unicode.
|
|
||||||
"""
|
|
||||||
if not isinstance(s, unicode):
|
|
||||||
s = unicode(s)
|
|
||||||
return s
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
import gc
|
|
||||||
import unittest
|
|
||||||
from jinja2._markupsafe import Markup, escape, escape_silent
|
|
||||||
|
|
||||||
|
|
||||||
class MarkupTestCase(unittest.TestCase):
|
|
||||||
|
|
||||||
def test_markup_operations(self):
|
|
||||||
# adding two strings should escape the unsafe one
|
|
||||||
unsafe = '<script type="application/x-some-script">alert("foo");</script>'
|
|
||||||
safe = Markup('<em>username</em>')
|
|
||||||
assert unsafe + safe == unicode(escape(unsafe)) + unicode(safe)
|
|
||||||
|
|
||||||
# string interpolations are safe to use too
|
|
||||||
assert Markup('<em>%s</em>') % '<bad user>' == \
|
|
||||||
'<em><bad user></em>'
|
|
||||||
assert Markup('<em>%(username)s</em>') % {
|
|
||||||
'username': '<bad user>'
|
|
||||||
} == '<em><bad user></em>'
|
|
||||||
|
|
||||||
# an escaped object is markup too
|
|
||||||
assert type(Markup('foo') + 'bar') is Markup
|
|
||||||
|
|
||||||
# and it implements __html__ by returning itself
|
|
||||||
x = Markup("foo")
|
|
||||||
assert x.__html__() is x
|
|
||||||
|
|
||||||
# it also knows how to treat __html__ objects
|
|
||||||
class Foo(object):
|
|
||||||
def __html__(self):
|
|
||||||
return '<em>awesome</em>'
|
|
||||||
def __unicode__(self):
|
|
||||||
return 'awesome'
|
|
||||||
assert Markup(Foo()) == '<em>awesome</em>'
|
|
||||||
assert Markup('<strong>%s</strong>') % Foo() == \
|
|
||||||
'<strong><em>awesome</em></strong>'
|
|
||||||
|
|
||||||
# escaping and unescaping
|
|
||||||
assert escape('"<>&\'') == '"<>&''
|
|
||||||
assert Markup("<em>Foo & Bar</em>").striptags() == "Foo & Bar"
|
|
||||||
assert Markup("<test>").unescape() == "<test>"
|
|
||||||
|
|
||||||
def test_all_set(self):
|
|
||||||
import jinja2._markupsafe as markup
|
|
||||||
for item in markup.__all__:
|
|
||||||
getattr(markup, item)
|
|
||||||
|
|
||||||
def test_escape_silent(self):
|
|
||||||
assert escape_silent(None) == Markup()
|
|
||||||
assert escape(None) == Markup(None)
|
|
||||||
assert escape_silent('<foo>') == Markup(u'<foo>')
|
|
||||||
|
|
||||||
|
|
||||||
class MarkupLeakTestCase(unittest.TestCase):
|
|
||||||
|
|
||||||
def test_markup_leaks(self):
|
|
||||||
counts = set()
|
|
||||||
for count in xrange(20):
|
|
||||||
for item in xrange(1000):
|
|
||||||
escape("foo")
|
|
||||||
escape("<foo>")
|
|
||||||
escape(u"foo")
|
|
||||||
escape(u"<foo>")
|
|
||||||
counts.add(len(gc.get_objects()))
|
|
||||||
assert len(counts) == 1, 'ouch, c extension seems to leak objects'
|
|
||||||
|
|
||||||
|
|
||||||
def suite():
|
|
||||||
suite = unittest.TestSuite()
|
|
||||||
suite.addTest(unittest.makeSuite(MarkupTestCase))
|
|
||||||
|
|
||||||
# this test only tests the c extension
|
|
||||||
if not hasattr(escape, 'func_code'):
|
|
||||||
suite.addTest(unittest.makeSuite(MarkupLeakTestCase))
|
|
||||||
|
|
||||||
return suite
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main(defaultTest='suite')
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
jinja2._stringdefs
|
|
||||||
~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Strings of all Unicode characters of a certain category.
|
|
||||||
Used for matching in Unicode-aware languages. Run to regenerate.
|
|
||||||
|
|
||||||
Inspired by chartypes_create.py from the MoinMoin project, original
|
|
||||||
implementation from Pygments.
|
|
||||||
|
|
||||||
:copyright: Copyright 2006-2009 by the Jinja team, see AUTHORS.
|
|
||||||
:license: BSD, see LICENSE for details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
Cc = u'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f'
|
|
||||||
|
|
||||||
Cf = u'\xad\u0600\u0601\u0602\u0603\u06dd\u070f\u17b4\u17b5\u200b\u200c\u200d\u200e\u200f\u202a\u202b\u202c\u202d\u202e\u2060\u2061\u2062\u2063\u206a\u206b\u206c\u206d\u206e\u206f\ufeff\ufff9\ufffa\ufffb'
|
|
||||||
|
|
||||||
Cn = u'\u0242\u0243\u0244\u0245\u0246\u0247\u0248\u0249\u024a\u024b\u024c\u024d\u024e\u024f\u0370\u0371\u0372\u0373\u0376\u0377\u0378\u0379\u037b\u037c\u037d\u037f\u0380\u0381\u0382\u0383\u038b\u038d\u03a2\u03cf\u0487\u04cf\u04fa\u04fb\u04fc\u04fd\u04fe\u04ff\u0510\u0511\u0512\u0513\u0514\u0515\u0516\u0517\u0518\u0519\u051a\u051b\u051c\u051d\u051e\u051f\u0520\u0521\u0522\u0523\u0524\u0525\u0526\u0527\u0528\u0529\u052a\u052b\u052c\u052d\u052e\u052f\u0530\u0557\u0558\u0560\u0588\u058b\u058c\u058d\u058e\u058f\u0590\u05ba\u05c8\u05c9\u05ca\u05cb\u05cc\u05cd\u05ce\u05cf\u05eb\u05ec\u05ed\u05ee\u05ef\u05f5\u05f6\u05f7\u05f8\u05f9\u05fa\u05fb\u05fc\u05fd\u05fe\u05ff\u0604\u0605\u0606\u0607\u0608\u0609\u060a\u0616\u0617\u0618\u0619\u061a\u061c\u061d\u0620\u063b\u063c\u063d\u063e\u063f\u065f\u070e\u074b\u074c\u076e\u076f\u0770\u0771\u0772\u0773\u0774\u0775\u0776\u0777\u0778\u0779\u077a\u077b\u077c\u077d\u077e\u077f\u07b2\u07b3\u07b4\u07b5\u07b6\u07b7\u07b8\u07b9\u07ba\u07bb\u07bc\u07bd\u07be\u07bf\u07c0\u07c1\u07c2\u07c3\u07c4\u07c5\u07c6\u07c7\u07c8\u07c9\u07ca\u07cb\u07cc\u07cd\u07ce\u07cf\u07d0\u07d1\u07d2\u07d3\u07d4\u07d5\u07d6\u07d7\u07d8\u07d9\u07da\u07db\u07dc\u07dd\u07de\u07df\u07e0\u07e1\u07e2\u07e3\u07e4\u07e5\u07e6\u07e7\u07e8\u07e9\u07ea\u07eb\u07ec\u07ed\u07ee\u07ef\u07f0\u07f1\u07f2\u07f3\u07f4\u07f5\u07f6\u07f7\u07f8\u07f9\u07fa\u07fb\u07fc\u07fd\u07fe\u07ff\u0800\u0801\u0802\u0803\u0804\u0805\u0806\u0807\u0808\u0809\u080a\u080b\u080c\u080d\u080e\u080f\u0810\u0811\u0812\u0813\u0814\u0815\u0816\u0817\u0818\u0819\u081a\u081b\u081c\u081d\u081e\u081f\u0820\u0821\u0822\u0823\u0824\u0825\u0826\u0827\u0828\u0829\u082a\u082b\u082c\u082d\u082e\u082f\u0830\u0831\u0832\u0833\u0834\u0835\u0836\u0837\u0838\u0839\u083a\u083b\u083c\u083d\u083e\u083f\u0840\u0841\u0842\u0843\u0844\u0845\u0846\u0847\u0848\u0849\u084a\u084b\u084c\u084d\u084e\u084f\u0850\u0851\u0852\u0853\u0854\u0855\u0856\u0857\u0858\u0859\u085a\u085b\u085c\u085d\u085e\u085f\u0860\u0861\u0862\u0863\u0864\u0865\u0866\u0867\u0868\u0869\u086a\u086b\u086c\u086d\u086e\u086f\u0870\u0871\u0872\u0873\u0874\u0875\u0876\u0877\u0878\u0879\u087a\u087b\u087c\u087d\u087e\u087f\u0880\u0881\u0882\u0883\u0884\u0885\u0886\u0887\u0888\u0889\u088a\u088b\u088c\u088d\u088e\u088f\u0890\u0891\u0892\u0893\u0894\u0895\u0896\u0897\u0898\u0899\u089a\u089b\u089c\u089d\u089e\u089f\u08a0\u08a1\u08a2\u08a3\u08a4\u08a5\u08a6\u08a7\u08a8\u08a9\u08aa\u08ab\u08ac\u08ad\u08ae\u08af\u08b0\u08b1\u08b2\u08b3\u08b4\u08b5\u08b6\u08b7\u08b8\u08b9\u08ba\u08bb\u08bc\u08bd\u08be\u08bf\u08c0\u08c1\u08c2\u08c3\u08c4\u08c5\u08c6\u08c7\u08c8\u08c9\u08ca\u08cb\u08cc\u08cd\u08ce\u08cf\u08d0\u08d1\u08d2\u08d3\u08d4\u08d5\u08d6\u08d7\u08d8\u08d9\u08da\u08db\u08dc\u08dd\u08de\u08df\u08e0\u08e1\u08e2\u08e3\u08e4\u08e5\u08e6\u08e7\u08e8\u08e9\u08ea\u08eb\u08ec\u08ed\u08ee\u08ef\u08f0\u08f1\u08f2\u08f3\u08f4\u08f5\u08f6\u08f7\u08f8\u08f9\u08fa\u08fb\u08fc\u08fd\u08fe\u08ff\u0900\u093a\u093b\u094e\u094f\u0955\u0956\u0957\u0971\u0972\u0973\u0974\u0975\u0976\u0977\u0978\u0979\u097a\u097b\u097c\u097e\u097f\u0980\u0984\u098d\u098e\u0991\u0992\u09a9\u09b1\u09b3\u09b4\u09b5\u09ba\u09bb\u09c5\u09c6\u09c9\u09ca\u09cf\u09d0\u09d1\u09d2\u09d3\u09d4\u09d5\u09d6\u09d8\u09d9\u09da\u09db\u09de\u09e4\u09e5\u09fb\u09fc\u09fd\u09fe\u09ff\u0a00\u0a04\u0a0b\u0a0c\u0a0d\u0a0e\u0a11\u0a12\u0a29\u0a31\u0a34\u0a37\u0a3a\u0a3b\u0a3d\u0a43\u0a44\u0a45\u0a46\u0a49\u0a4a\u0a4e\u0a4f\u0a50\u0a51\u0a52\u0a53\u0a54\u0a55\u0a56\u0a57\u0a58\u0a5d\u0a5f\u0a60\u0a61\u0a62\u0a63\u0a64\u0a65\u0a75\u0a76\u0a77\u0a78\u0a79\u0a7a\u0a7b\u0a7c\u0a7d\u0a7e\u0a7f\u0a80\u0a84\u0a8e\u0a92\u0aa9\u0ab1\u0ab4\u0aba\u0abb\u0ac6\u0aca\u0ace\u0acf\u0ad1\u0ad2\u0ad3\u0ad4\u0ad5\u0ad6\u0ad7\u0ad8\u0ad9\u0ada\u0adb\u0adc\u0add\u0ade\u0adf\u0ae4\u0ae5\u0af0\u0af2\u0af3\u0af4\u0af5\u0af6\u0af7\u0af8\u0af9\u0afa\u0afb\u0afc\u0afd\u0afe\u0aff\u0b00\u0b04\u0b0d\u0b0e\u0b11\u0b12\u0b29\u0b31\u0b34\u0b3a\u0b3b\u0b44\u0b45\u0b46\u0b49\u0b4a\u0b4e\u0b4f\u0b50\u0b51\u0b52\u0b53\u0b54\u0b55\u0b58\u0b59\u0b5a\u0b5b\u0b5e\u0b62\u0b63\u0b64\u0b65\u0b72\u0b73\u0b74\u0b75\u0b76\u0b77\u0b78\u0b79\u0b7a\u0b7b\u0b7c\u0b7d\u0b7e\u0b7f\u0b80\u0b81\u0b84\u0b8b\u0b8c\u0b8d\u0b91\u0b96\u0b97\u0b98\u0b9b\u0b9d\u0ba0\u0ba1\u0ba2\u0ba5\u0ba6\u0ba7\u0bab\u0bac\u0bad\u0bba\u0bbb\u0bbc\u0bbd\u0bc3\u0bc4\u0bc5\u0bc9\u0bce\u0bcf\u0bd0\u0bd1\u0bd2\u0bd3\u0bd4\u0bd5\u0bd6\u0bd8\u0bd9\u0bda\u0bdb\u0bdc\u0bdd\u0bde\u0bdf\u0be0\u0be1\u0be2\u0be3\u0be4\u0be5\u0bfb\u0bfc\u0bfd\u0bfe\u0bff\u0c00\u0c04\u0c0d\u0c11\u0c29\u0c34\u0c3a\u0c3b\u0c3c\u0c3d\u0c45\u0c49\u0c4e\u0c4f\u0c50\u0c51\u0c52\u0c53\u0c54\u0c57\u0c58\u0c59\u0c5a\u0c5b\u0c5c\u0c5d\u0c5e\u0c5f\u0c62\u0c63\u0c64\u0c65\u0c70\u0c71\u0c72\u0c73\u0c74\u0c75\u0c76\u0c77\u0c78\u0c79\u0c7a\u0c7b\u0c7c\u0c7d\u0c7e\u0c7f\u0c80\u0c81\u0c84\u0c8d\u0c91\u0ca9\u0cb4\u0cba\u0cbb\u0cc5\u0cc9\u0cce\u0ccf\u0cd0\u0cd1\u0cd2\u0cd3\u0cd4\u0cd7\u0cd8\u0cd9\u0cda\u0cdb\u0cdc\u0cdd\u0cdf\u0ce2\u0ce3\u0ce4\u0ce5\u0cf0\u0cf1\u0cf2\u0cf3\u0cf4\u0cf5\u0cf6\u0cf7\u0cf8Line truncated
|
|
||||||
|
|
||||||
Co = u'\ue000\ue001\ue002\ue003\ue004\ue005\ue006\ue007\ue008\ue009\ue00a\ue00b\ue00c\ue00d\ue00e\ue00f\ue010\ue011\ue012\ue013\ue014\ue015\ue016\ue017\ue018\ue019\ue01a\ue01b\ue01c\ue01d\ue01e\ue01f\ue020\ue021\ue022\ue023\ue024\ue025\ue026\ue027\ue028\ue029\ue02a\ue02b\ue02c\ue02d\ue02e\ue02f\ue030\ue031\ue032\ue033\ue034\ue035\ue036\ue037\ue038\ue039\ue03a\ue03b\ue03c\ue03d\ue03e\ue03f\ue040\ue041\ue042\ue043\ue044\ue045\ue046\ue047\ue048\ue049\ue04a\ue04b\ue04c\ue04d\ue04e\ue04f\ue050\ue051\ue052\ue053\ue054\ue055\ue056\ue057\ue058\ue059\ue05a\ue05b\ue05c\ue05d\ue05e\ue05f\ue060\ue061\ue062\ue063\ue064\ue065\ue066\ue067\ue068\ue069\ue06a\ue06b\ue06c\ue06d\ue06e\ue06f\ue070\ue071\ue072\ue073\ue074\ue075\ue076\ue077\ue078\ue079\ue07a\ue07b\ue07c\ue07d\ue07e\ue07f\ue080\ue081\ue082\ue083\ue084\ue085\ue086\ue087\ue088\ue089\ue08a\ue08b\ue08c\ue08d\ue08e\ue08f\ue090\ue091\ue092\ue093\ue094\ue095\ue096\ue097\ue098\ue099\ue09a\ue09b\ue09c\ue09d\ue09e\ue09f\ue0a0\ue0a1\ue0a2\ue0a3\ue0a4\ue0a5\ue0a6\ue0a7\ue0a8\ue0a9\ue0aa\ue0ab\ue0ac\ue0ad\ue0ae\ue0af\ue0b0\ue0b1\ue0b2\ue0b3\ue0b4\ue0b5\ue0b6\ue0b7\ue0b8\ue0b9\ue0ba\ue0bb\ue0bc\ue0bd\ue0be\ue0bf\ue0c0\ue0c1\ue0c2\ue0c3\ue0c4\ue0c5\ue0c6\ue0c7\ue0c8\ue0c9\ue0ca\ue0cb\ue0cc\ue0cd\ue0ce\ue0cf\ue0d0\ue0d1\ue0d2\ue0d3\ue0d4\ue0d5\ue0d6\ue0d7\ue0d8\ue0d9\ue0da\ue0db\ue0dc\ue0dd\ue0de\ue0df\ue0e0\ue0e1\ue0e2\ue0e3\ue0e4\ue0e5\ue0e6\ue0e7\ue0e8\ue0e9\ue0ea\ue0eb\ue0ec\ue0ed\ue0ee\ue0ef\ue0f0\ue0f1\ue0f2\ue0f3\ue0f4\ue0f5\ue0f6\ue0f7\ue0f8\ue0f9\ue0fa\ue0fb\ue0fc\ue0fd\ue0fe\ue0ff\ue100\ue101\ue102\ue103\ue104\ue105\ue106\ue107\ue108\ue109\ue10a\ue10b\ue10c\ue10d\ue10e\ue10f\ue110\ue111\ue112\ue113\ue114\ue115\ue116\ue117\ue118\ue119\ue11a\ue11b\ue11c\ue11d\ue11e\ue11f\ue120\ue121\ue122\ue123\ue124\ue125\ue126\ue127\ue128\ue129\ue12a\ue12b\ue12c\ue12d\ue12e\ue12f\ue130\ue131\ue132\ue133\ue134\ue135\ue136\ue137\ue138\ue139\ue13a\ue13b\ue13c\ue13d\ue13e\ue13f\ue140\ue141\ue142\ue143\ue144\ue145\ue146\ue147\ue148\ue149\ue14a\ue14b\ue14c\ue14d\ue14e\ue14f\ue150\ue151\ue152\ue153\ue154\ue155\ue156\ue157\ue158\ue159\ue15a\ue15b\ue15c\ue15d\ue15e\ue15f\ue160\ue161\ue162\ue163\ue164\ue165\ue166\ue167\ue168\ue169\ue16a\ue16b\ue16c\ue16d\ue16e\ue16f\ue170\ue171\ue172\ue173\ue174\ue175\ue176\ue177\ue178\ue179\ue17a\ue17b\ue17c\ue17d\ue17e\ue17f\ue180\ue181\ue182\ue183\ue184\ue185\ue186\ue187\ue188\ue189\ue18a\ue18b\ue18c\ue18d\ue18e\ue18f\ue190\ue191\ue192\ue193\ue194\ue195\ue196\ue197\ue198\ue199\ue19a\ue19b\ue19c\ue19d\ue19e\ue19f\ue1a0\ue1a1\ue1a2\ue1a3\ue1a4\ue1a5\ue1a6\ue1a7\ue1a8\ue1a9\ue1aa\ue1ab\ue1ac\ue1ad\ue1ae\ue1af\ue1b0\ue1b1\ue1b2\ue1b3\ue1b4\ue1b5\ue1b6\ue1b7\ue1b8\ue1b9\ue1ba\ue1bb\ue1bc\ue1bd\ue1be\ue1bf\ue1c0\ue1c1\ue1c2\ue1c3\ue1c4\ue1c5\ue1c6\ue1c7\ue1c8\ue1c9\ue1ca\ue1cb\ue1cc\ue1cd\ue1ce\ue1cf\ue1d0\ue1d1\ue1d2\ue1d3\ue1d4\ue1d5\ue1d6\ue1d7\ue1d8\ue1d9\ue1da\ue1db\ue1dc\ue1dd\ue1de\ue1df\ue1e0\ue1e1\ue1e2\ue1e3\ue1e4\ue1e5\ue1e6\ue1e7\ue1e8\ue1e9\ue1ea\ue1eb\ue1ec\ue1ed\ue1ee\ue1ef\ue1f0\ue1f1\ue1f2\ue1f3\ue1f4\ue1f5\ue1f6\ue1f7\ue1f8\ue1f9\ue1fa\ue1fb\ue1fc\ue1fd\ue1fe\ue1ff\ue200\ue201\ue202\ue203\ue204\ue205\ue206\ue207\ue208\ue209\ue20a\ue20b\ue20c\ue20d\ue20e\ue20f\ue210\ue211\ue212\ue213\ue214\ue215\ue216\ue217\ue218\ue219\ue21a\ue21b\ue21c\ue21d\ue21e\ue21f\ue220\ue221\ue222\ue223\ue224\ue225\ue226\ue227\ue228\ue229\ue22a\ue22b\ue22c\ue22d\ue22e\ue22f\ue230\ue231\ue232\ue233\ue234\ue235\ue236\ue237\ue238\ue239\ue23a\ue23b\ue23c\ue23d\ue23e\ue23f\ue240\ue241\ue242\ue243\ue244\ue245\ue246\ue247\ue248\ue249\ue24a\ue24b\ue24c\ue24d\ue24e\ue24f\ue250\ue251\ue252\ue253\ue254\ue255\ue256\ue257\ue258\ue259\ue25a\ue25b\ue25c\ue25d\ue25e\ue25f\ue260\ue261\ue262\ue263\ue264\ue265\ue266\ue267\ue268\ue269\ue26a\ue26b\ue26c\ue26d\ue26e\ue26f\ue270\ue271\ue272\ue273\ue274\ue275\ue276\ue277\ue278\ue279\ue27a\ue27b\ue27c\ue27d\ue27e\ue27f\ue280\ue281\ue282\ue283\ue284\ue285\ue286\ue287\ue288\ue289\ue28a\ue28b\ue28c\ue28d\ue28e\ue28f\ue290\ue291\ue292\ue293\ue294\ue295\ue296\ue297\ue298\ue299\ue29a\ue29b\ue29c\ue29d\ue29e\ue29f\ue2a0\ue2a1\ue2a2\ue2a3\ue2a4\ue2a5\ue2a6\ue2a7\ue2a8\ue2a9\ue2aa\ue2ab\ue2ac\ue2ad\ue2ae\ue2af\ue2b0\ue2b1\ue2b2\ue2b3\ue2b4\ue2b5\ue2b6\ue2b7\ue2b8\ue2b9\ue2ba\ue2bb\ue2bc\ue2bd\ue2be\ue2bf\ue2c0\ue2c1\ue2c2\ue2c3\ue2c4\ue2c5\ue2c6\ue2c7\ue2c8\ue2c9\ue2ca\ue2cb\ue2cc\ue2cd\ue2ce\ue2cf\ue2d0\ue2d1\ue2d2\ue2d3\ue2d4\ue2d5\ue2d6\ue2d7\ue2d8\ue2d9\ue2da\ue2db\ue2dc\ue2dd\ue2de\ue2df\ue2e0\ue2e1\ue2e2\ue2e3\ue2e4\ue2e5\ue2e6\ue2e7\ue2e8\ue2e9\ue2ea\ue2eb\ue2ec\ue2ed\ue2ee\ue2ef\ue2f0\ue2f1\ue2f2\ue2f3\ue2f4\ue2f5\ue2f6\ue2f7\ue2f8\ue2f9\ue2fa\ue2fb\ue2fc\ue2fd\ue2fe\ue2ff\ue300\ue301\ue302\ue303\ue304\ue305\ue306\ue307\ue308\ue309\ue30a\ue30b\ue30c\ue30d\ue30e\ue30f\ue310\ue311\ue312\ue313\ue314\ue315\ue316\ue317\ue318\ue319\ue31a\ue31b\ue31c\ue31d\ue31e\ue31f\ue320\ue321\ue322\ue323\ue324\ue325\ue326\ue327\ue328\ue329\ue32a\ue32b\ue32c\ue32d\ue32e\ue32f\ue330\ue331\ue332\ue333\ue334\ue335\ue336\ue337\ue338\ue339\ue33a\ue33b\ue33c\ue33d\ue33e\ue33fLine truncated
|
|
||||||
|
|
||||||
try:
|
|
||||||
Cs = eval(r"'\ud800\ud801\ud802\ud803\ud804\ud805\ud806\ud807\ud808\ud809\ud80a\ud80b\ud80c\ud80d\ud80e\ud80f\ud810\ud811\ud812\ud813\ud814\ud815\ud816\ud817\ud818\ud819\ud81a\ud81b\ud81c\ud81d\ud81e\ud81f\ud820\ud821\ud822\ud823\ud824\ud825\ud826\ud827\ud828\ud829\ud82a\ud82b\ud82c\ud82d\ud82e\ud82f\ud830\ud831\ud832\ud833\ud834\ud835\ud836\ud837\ud838\ud839\ud83a\ud83b\ud83c\ud83d\ud83e\ud83f\ud840\ud841\ud842\ud843\ud844\ud845\ud846\ud847\ud848\ud849\ud84a\ud84b\ud84c\ud84d\ud84e\ud84f\ud850\ud851\ud852\ud853\ud854\ud855\ud856\ud857\ud858\ud859\ud85a\ud85b\ud85c\ud85d\ud85e\ud85f\ud860\ud861\ud862\ud863\ud864\ud865\ud866\ud867\ud868\ud869\ud86a\ud86b\ud86c\ud86d\ud86e\ud86f\ud870\ud871\ud872\ud873\ud874\ud875\ud876\ud877\ud878\ud879\ud87a\ud87b\ud87c\ud87d\ud87e\ud87f\ud880\ud881\ud882\ud883\ud884\ud885\ud886\ud887\ud888\ud889\ud88a\ud88b\ud88c\ud88d\ud88e\ud88f\ud890\ud891\ud892\ud893\ud894\ud895\ud896\ud897\ud898\ud899\ud89a\ud89b\ud89c\ud89d\ud89e\ud89f\ud8a0\ud8a1\ud8a2\ud8a3\ud8a4\ud8a5\ud8a6\ud8a7\ud8a8\ud8a9\ud8aa\ud8ab\ud8ac\ud8ad\ud8ae\ud8af\ud8b0\ud8b1\ud8b2\ud8b3\ud8b4\ud8b5\ud8b6\ud8b7\ud8b8\ud8b9\ud8ba\ud8bb\ud8bc\ud8bd\ud8be\ud8bf\ud8c0\ud8c1\ud8c2\ud8c3\ud8c4\ud8c5\ud8c6\ud8c7\ud8c8\ud8c9\ud8ca\ud8cb\ud8cc\ud8cd\ud8ce\ud8cf\ud8d0\ud8d1\ud8d2\ud8d3\ud8d4\ud8d5\ud8d6\ud8d7\ud8d8\ud8d9\ud8da\ud8db\ud8dc\ud8dd\ud8de\ud8df\ud8e0\ud8e1\ud8e2\ud8e3\ud8e4\ud8e5\ud8e6\ud8e7\ud8e8\ud8e9\ud8ea\ud8eb\ud8ec\ud8ed\ud8ee\ud8ef\ud8f0\ud8f1\ud8f2\ud8f3\ud8f4\ud8f5\ud8f6\ud8f7\ud8f8\ud8f9\ud8fa\ud8fb\ud8fc\ud8fd\ud8fe\ud8ff\ud900\ud901\ud902\ud903\ud904\ud905\ud906\ud907\ud908\ud909\ud90a\ud90b\ud90c\ud90d\ud90e\ud90f\ud910\ud911\ud912\ud913\ud914\ud915\ud916\ud917\ud918\ud919\ud91a\ud91b\ud91c\ud91d\ud91e\ud91f\ud920\ud921\ud922\ud923\ud924\ud925\ud926\ud927\ud928\ud929\ud92a\ud92b\ud92c\ud92d\ud92e\ud92f\ud930\ud931\ud932\ud933\ud934\ud935\ud936\ud937\ud938\ud939\ud93a\ud93b\ud93c\ud93d\ud93e\ud93f\ud940\ud941\ud942\ud943\ud944\ud945\ud946\ud947\ud948\ud949\ud94a\ud94b\ud94c\ud94d\ud94e\ud94f\ud950\ud951\ud952\ud953\ud954\ud955\ud956\ud957\ud958\ud959\ud95a\ud95b\ud95c\ud95d\ud95e\ud95f\ud960\ud961\ud962\ud963\ud964\ud965\ud966\ud967\ud968\ud969\ud96a\ud96b\ud96c\ud96d\ud96e\ud96f\ud970\ud971\ud972\ud973\ud974\ud975\ud976\ud977\ud978\ud979\ud97a\ud97b\ud97c\ud97d\ud97e\ud97f\ud980\ud981\ud982\ud983\ud984\ud985\ud986\ud987\ud988\ud989\ud98a\ud98b\ud98c\ud98d\ud98e\ud98f\ud990\ud991\ud992\ud993\ud994\ud995\ud996\ud997\ud998\ud999\ud99a\ud99b\ud99c\ud99d\ud99e\ud99f\ud9a0\ud9a1\ud9a2\ud9a3\ud9a4\ud9a5\ud9a6\ud9a7\ud9a8\ud9a9\ud9aa\ud9ab\ud9ac\ud9ad\ud9ae\ud9af\ud9b0\ud9b1\ud9b2\ud9b3\ud9b4\ud9b5\ud9b6\ud9b7\ud9b8\ud9b9\ud9ba\ud9bb\ud9bc\ud9bd\ud9be\ud9bf\ud9c0\ud9c1\ud9c2\ud9c3\ud9c4\ud9c5\ud9c6\ud9c7\ud9c8\ud9c9\ud9ca\ud9cb\ud9cc\ud9cd\ud9ce\ud9cf\ud9d0\ud9d1\ud9d2\ud9d3\ud9d4\ud9d5\ud9d6\ud9d7\ud9d8\ud9d9\ud9da\ud9db\ud9dc\ud9dd\ud9de\ud9df\ud9e0\ud9e1\ud9e2\ud9e3\ud9e4\ud9e5\ud9e6\ud9e7\ud9e8\ud9e9\ud9ea\ud9eb\ud9ec\ud9ed\ud9ee\ud9ef\ud9f0\ud9f1\ud9f2\ud9f3\ud9f4\ud9f5\ud9f6\ud9f7\ud9f8\ud9f9\ud9fa\ud9fb\ud9fc\ud9fd\ud9fe\ud9ff\uda00\uda01\uda02\uda03\uda04\uda05\uda06\uda07\uda08\uda09\uda0a\uda0b\uda0c\uda0d\uda0e\uda0f\uda10\uda11\uda12\uda13\uda14\uda15\uda16\uda17\uda18\uda19\uda1a\uda1b\uda1c\uda1d\uda1e\uda1f\uda20\uda21\uda22\uda23\uda24\uda25\uda26\uda27\uda28\uda29\uda2a\uda2b\uda2c\uda2d\uda2e\uda2f\uda30\uda31\uda32\uda33\uda34\uda35\uda36\uda37\uda38\uda39\uda3a\uda3b\uda3c\uda3d\uda3e\uda3f\uda40\uda41\uda42\uda43\uda44\uda45\uda46\uda47\uda48\uda49\uda4a\uda4b\uda4c\uda4d\uda4e\uda4f\uda50\uda51\uda52\uda53\uda54\uda55\uda56\uda57\uda58\uda59\uda5a\uda5b\uda5c\uda5d\uda5e\uda5f\uda60\uda61\uda62\uda63\uda64\uda65\uda66\uda67\uda68\uda69\uda6a\uda6b\uda6c\uda6d\uda6e\uda6f\uda70\uda71\uda72\uda73\uda74\uda75\uda76\uda77\uda78\uda79\uda7a\uda7b\uda7c\uda7d\uda7e\uda7f\uda80\uda81\uda82\uda83\uda84\uda85\uda86\uda87\uda88\uda89\uda8a\uda8b\uda8c\uda8d\uda8e\uda8f\uda90\uda91\uda92\uda93\uda94\uda95\uda96\uda97\uda98\uda99\uda9a\uda9b\uda9c\uda9d\uda9e\uda9f\udaa0\udaa1\udaa2\udaa3\udaa4\udaa5\udaa6\udaa7\udaa8\udaa9\udaaa\udaab\udaac\udaad\udaae\udaaf\udab0\udab1\udab2\udab3\udab4\udab5\udab6\udab7\udab8\udab9\udaba\udabb\udabc\udabd\udabe\udabf\udac0\udac1\udac2\udac3\udac4\udac5\udac6\udac7\udac8\udac9\udaca\udacb\udacc\udacd\udace\udacf\udad0\udad1\udad2\udad3\udad4\udad5\udad6\udad7\udad8\udad9\udada\udadb\udadc\udadd\udade\udadf\udae0\udae1\udae2\udae3\udae4\udae5\udae6\udae7\udae8\udae9\udaea\udaeb\udaec\udaed\udaee\udaef\udaf0\udaf1\udaf2\udaf3\udaf4\udaf5\udaf6\udaf7\udaf8\udaf9\udafa\udafb\udafc\udafd\udafe\udaff\udb00\udb01\udb02\udb03\udb04\udb05\udb06\udb07\udb08\udb09\udb0a\udb0b\udb0c\udb0d\udb0e\udb0f\udb10\udb11\udb12\udb13\udb14\udb15\udb16\udb17\udb18\udb19\udb1a\udb1b\udb1c\udb1d\udb1e\udb1f\udb20\udb21\udb22\udb23\udb24\udb25\udb26\udb27\udb28\udb29\udb2a\udb2b\udb2c\udb2d\udb2e\udb2f\udb30\udb31\udb32\udb33\udb34\udb35\udb36\udb37\udb38\udb39\udb3a\udb3b\udb3c\udb3d\uLine truncated
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
Cs = '' # Jython can't handle isolated surrogates
|
|
||||||
|
|
||||||
Ll = u'abcdefghijklmnopqrstuvwxyz\xaa\xb5\xba\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff\u0101\u0103\u0105\u0107\u0109\u010b\u010d\u010f\u0111\u0113\u0115\u0117\u0119\u011b\u011d\u011f\u0121\u0123\u0125\u0127\u0129\u012b\u012d\u012f\u0131\u0133\u0135\u0137\u0138\u013a\u013c\u013e\u0140\u0142\u0144\u0146\u0148\u0149\u014b\u014d\u014f\u0151\u0153\u0155\u0157\u0159\u015b\u015d\u015f\u0161\u0163\u0165\u0167\u0169\u016b\u016d\u016f\u0171\u0173\u0175\u0177\u017a\u017c\u017e\u017f\u0180\u0183\u0185\u0188\u018c\u018d\u0192\u0195\u0199\u019a\u019b\u019e\u01a1\u01a3\u01a5\u01a8\u01aa\u01ab\u01ad\u01b0\u01b4\u01b6\u01b9\u01ba\u01bd\u01be\u01bf\u01c6\u01c9\u01cc\u01ce\u01d0\u01d2\u01d4\u01d6\u01d8\u01da\u01dc\u01dd\u01df\u01e1\u01e3\u01e5\u01e7\u01e9\u01eb\u01ed\u01ef\u01f0\u01f3\u01f5\u01f9\u01fb\u01fd\u01ff\u0201\u0203\u0205\u0207\u0209\u020b\u020d\u020f\u0211\u0213\u0215\u0217\u0219\u021b\u021d\u021f\u0221\u0223\u0225\u0227\u0229\u022b\u022d\u022f\u0231\u0233\u0234\u0235\u0236\u0237\u0238\u0239\u023c\u023f\u0240\u0250\u0251\u0252\u0253\u0254\u0255\u0256\u0257\u0258\u0259\u025a\u025b\u025c\u025d\u025e\u025f\u0260\u0261\u0262\u0263\u0264\u0265\u0266\u0267\u0268\u0269\u026a\u026b\u026c\u026d\u026e\u026f\u0270\u0271\u0272\u0273\u0274\u0275\u0276\u0277\u0278\u0279\u027a\u027b\u027c\u027d\u027e\u027f\u0280\u0281\u0282\u0283\u0284\u0285\u0286\u0287\u0288\u0289\u028a\u028b\u028c\u028d\u028e\u028f\u0290\u0291\u0292\u0293\u0294\u0295\u0296\u0297\u0298\u0299\u029a\u029b\u029c\u029d\u029e\u029f\u02a0\u02a1\u02a2\u02a3\u02a4\u02a5\u02a6\u02a7\u02a8\u02a9\u02aa\u02ab\u02ac\u02ad\u02ae\u02af\u0390\u03ac\u03ad\u03ae\u03af\u03b0\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c2\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03ca\u03cb\u03cc\u03cd\u03ce\u03d0\u03d1\u03d5\u03d6\u03d7\u03d9\u03db\u03dd\u03df\u03e1\u03e3\u03e5\u03e7\u03e9\u03eb\u03ed\u03ef\u03f0\u03f1\u03f2\u03f3\u03f5\u03f8\u03fb\u03fc\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044a\u044b\u044c\u044d\u044e\u044f\u0450\u0451\u0452\u0453\u0454\u0455\u0456\u0457\u0458\u0459\u045a\u045b\u045c\u045d\u045e\u045f\u0461\u0463\u0465\u0467\u0469\u046b\u046d\u046f\u0471\u0473\u0475\u0477\u0479\u047b\u047d\u047f\u0481\u048b\u048d\u048f\u0491\u0493\u0495\u0497\u0499\u049b\u049d\u049f\u04a1\u04a3\u04a5\u04a7\u04a9\u04ab\u04ad\u04af\u04b1\u04b3\u04b5\u04b7\u04b9\u04bb\u04bd\u04bf\u04c2\u04c4\u04c6\u04c8\u04ca\u04cc\u04ce\u04d1\u04d3\u04d5\u04d7\u04d9\u04db\u04dd\u04df\u04e1\u04e3\u04e5\u04e7\u04e9\u04eb\u04ed\u04ef\u04f1\u04f3\u04f5\u04f7\u04f9\u0501\u0503\u0505\u0507\u0509\u050b\u050d\u050f\u0561\u0562\u0563\u0564\u0565\u0566\u0567\u0568\u0569\u056a\u056b\u056c\u056d\u056e\u056f\u0570\u0571\u0572\u0573\u0574\u0575\u0576\u0577\u0578\u0579\u057a\u057b\u057c\u057d\u057e\u057f\u0580\u0581\u0582\u0583\u0584\u0585\u0586\u0587\u1d00\u1d01\u1d02\u1d03\u1d04\u1d05\u1d06\u1d07\u1d08\u1d09\u1d0a\u1d0b\u1d0c\u1d0d\u1d0e\u1d0f\u1d10\u1d11\u1d12\u1d13\u1d14\u1d15\u1d16\u1d17\u1d18\u1d19\u1d1a\u1d1b\u1d1c\u1d1d\u1d1e\u1d1f\u1d20\u1d21\u1d22\u1d23\u1d24\u1d25\u1d26\u1d27\u1d28\u1d29\u1d2a\u1d2b\u1d62\u1d63\u1d64\u1d65\u1d66\u1d67\u1d68\u1d69\u1d6a\u1d6b\u1d6c\u1d6d\u1d6e\u1d6f\u1d70\u1d71\u1d72\u1d73\u1d74\u1d75\u1d76\u1d77\u1d79\u1d7a\u1d7b\u1d7c\u1d7d\u1d7e\u1d7f\u1d80\u1d81\u1d82\u1d83\u1d84\u1d85\u1d86\u1d87\u1d88\u1d89\u1d8a\u1d8b\u1d8c\u1d8d\u1d8e\u1d8f\u1d90\u1d91\u1d92\u1d93\u1d94\u1d95\u1d96\u1d97\u1d98\u1d99\u1d9a\u1e01\u1e03\u1e05\u1e07\u1e09\u1e0b\u1e0d\u1e0f\u1e11\u1e13\u1e15\u1e17\u1e19\u1e1b\u1e1d\u1e1f\u1e21\u1e23\u1e25\u1e27\u1e29\u1e2b\u1e2d\u1e2f\u1e31\u1e33\u1e35\u1e37\u1e39\u1e3b\u1e3d\u1e3f\u1e41\u1e43\u1e45\u1e47\u1e49\u1e4b\u1e4d\u1e4f\u1e51\u1e53\u1e55\u1e57\u1e59\u1e5b\u1e5d\u1e5f\u1e61\u1e63\u1e65\u1e67\u1e69\u1e6b\u1e6d\u1e6f\u1e71\u1e73\u1e75\u1e77\u1e79\u1e7b\u1e7d\u1e7f\u1e81\u1e83\u1e85\u1e87\u1e89\u1e8b\u1e8d\u1e8f\u1e91\u1e93\u1e95\u1e96\u1e97\u1e98\u1e99\u1e9a\u1e9b\u1ea1\u1ea3\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u1eb9\u1ebb\u1ebd\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u1ec9\u1ecb\u1ecd\u1ecf\u1ed1\u1ed3\u1ed5\u1ed7\u1ed9\u1edb\u1edd\u1edf\u1ee1\u1ee3\u1ee5\u1ee7\u1ee9\u1eeb\u1eed\u1eef\u1ef1\u1ef3\u1ef5\u1ef7\u1ef9\u1f00\u1f01\u1f02\u1f03\u1f04\u1f05\u1f06\u1f07\u1f10\u1f11\u1f12\u1f13\u1f14\u1f15\u1f20\u1f21\u1f22\u1f23\u1f24\u1f25\u1f26\u1f27\u1f30\u1f31\u1f32\u1f33\u1f34\u1f35\u1f36\u1f37\u1f40\u1f41\u1f42\u1f43\u1f44\u1f45\u1f50\u1f51\u1f52\u1f53\u1f54\u1f55\u1f56\u1f57\u1f60\u1f61\u1f62\u1f63\u1f64\u1f65\u1f66\u1f67\u1f70\u1f71\u1f72\u1f73\u1f74\u1f75\u1f76\u1f77\u1f78\u1f79\u1f7a\u1f7b\u1f7c\u1f7d\u1f80\u1f81\u1f82\u1f83\u1f84\u1f85\u1f86\u1f87\u1f90\u1f91\u1f92\u1f93\u1f94\u1f95\u1f96\u1f97\u1fa0\u1fa1\u1fa2\u1fa3\u1fa4\u1fa5\u1fa6\u1fa7\u1fb0\u1fb1\u1fb2\u1fb3\u1fb4\u1fb6\u1fb7\u1fbe\u1fc2\uLine truncated
|
|
||||||
|
|
||||||
Lm = u'\u02b0\u02b1\u02b2\u02b3\u02b4\u02b5\u02b6\u02b7\u02b8\u02b9\u02ba\u02bb\u02bc\u02bd\u02be\u02bf\u02c0\u02c1\u02c6\u02c7\u02c8\u02c9\u02ca\u02cb\u02cc\u02cd\u02ce\u02cf\u02d0\u02d1\u02e0\u02e1\u02e2\u02e3\u02e4\u02ee\u037a\u0559\u0640\u06e5\u06e6\u0e46\u0ec6\u10fc\u17d7\u1843\u1d2c\u1d2d\u1d2e\u1d2f\u1d30\u1d31\u1d32\u1d33\u1d34\u1d35\u1d36\u1d37\u1d38\u1d39\u1d3a\u1d3b\u1d3c\u1d3d\u1d3e\u1d3f\u1d40\u1d41\u1d42\u1d43\u1d44\u1d45\u1d46\u1d47\u1d48\u1d49\u1d4a\u1d4b\u1d4c\u1d4d\u1d4e\u1d4f\u1d50\u1d51\u1d52\u1d53\u1d54\u1d55\u1d56\u1d57\u1d58\u1d59\u1d5a\u1d5b\u1d5c\u1d5d\u1d5e\u1d5f\u1d60\u1d61\u1d78\u1d9b\u1d9c\u1d9d\u1d9e\u1d9f\u1da0\u1da1\u1da2\u1da3\u1da4\u1da5\u1da6\u1da7\u1da8\u1da9\u1daa\u1dab\u1dac\u1dad\u1dae\u1daf\u1db0\u1db1\u1db2\u1db3\u1db4\u1db5\u1db6\u1db7\u1db8\u1db9\u1dba\u1dbb\u1dbc\u1dbd\u1dbe\u1dbf\u2090\u2091\u2092\u2093\u2094\u2d6f\u3005\u3031\u3032\u3033\u3034\u3035\u303b\u309d\u309e\u30fc\u30fd\u30fe\ua015\uff70\uff9e\uff9f'
|
|
||||||
|
|
||||||
Lo = u'\u01bb\u01c0\u01c1\u01c2\u01c3\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05da\u05db\u05dc\u05dd\u05de\u05df\u05e0\u05e1\u05e2\u05e3\u05e4\u05e5\u05e6\u05e7\u05e8\u05e9\u05ea\u05f0\u05f1\u05f2\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u066e\u066f\u0671\u0672\u0673\u0674\u0675\u0676\u0677\u0678\u0679\u067a\u067b\u067c\u067d\u067e\u067f\u0680\u0681\u0682\u0683\u0684\u0685\u0686\u0687\u0688\u0689\u068a\u068b\u068c\u068d\u068e\u068f\u0690\u0691\u0692\u0693\u0694\u0695\u0696\u0697\u0698\u0699\u069a\u069b\u069c\u069d\u069e\u069f\u06a0\u06a1\u06a2\u06a3\u06a4\u06a5\u06a6\u06a7\u06a8\u06a9\u06aa\u06ab\u06ac\u06ad\u06ae\u06af\u06b0\u06b1\u06b2\u06b3\u06b4\u06b5\u06b6\u06b7\u06b8\u06b9\u06ba\u06bb\u06bc\u06bd\u06be\u06bf\u06c0\u06c1\u06c2\u06c3\u06c4\u06c5\u06c6\u06c7\u06c8\u06c9\u06ca\u06cb\u06cc\u06cd\u06ce\u06cf\u06d0\u06d1\u06d2\u06d3\u06d5\u06ee\u06ef\u06fa\u06fb\u06fc\u06ff\u0710\u0712\u0713\u0714\u0715\u0716\u0717\u0718\u0719\u071a\u071b\u071c\u071d\u071e\u071f\u0720\u0721\u0722\u0723\u0724\u0725\u0726\u0727\u0728\u0729\u072a\u072b\u072c\u072d\u072e\u072f\u074d\u074e\u074f\u0750\u0751\u0752\u0753\u0754\u0755\u0756\u0757\u0758\u0759\u075a\u075b\u075c\u075d\u075e\u075f\u0760\u0761\u0762\u0763\u0764\u0765\u0766\u0767\u0768\u0769\u076a\u076b\u076c\u076d\u0780\u0781\u0782\u0783\u0784\u0785\u0786\u0787\u0788\u0789\u078a\u078b\u078c\u078d\u078e\u078f\u0790\u0791\u0792\u0793\u0794\u0795\u0796\u0797\u0798\u0799\u079a\u079b\u079c\u079d\u079e\u079f\u07a0\u07a1\u07a2\u07a3\u07a4\u07a5\u07b1\u0904\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u090c\u090d\u090e\u090f\u0910\u0911\u0912\u0913\u0914\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u0929\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0931\u0932\u0933\u0934\u0935\u0936\u0937\u0938\u0939\u093d\u0950\u0958\u0959\u095a\u095b\u095c\u095d\u095e\u095f\u0960\u0961\u097d\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098c\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9\u09bd\u09ce\u09dc\u09dd\u09df\u09e0\u09e1\u09f0\u09f1\u0a05\u0a06\u0a07\u0a08\u0a09\u0a0a\u0a0f\u0a10\u0a13\u0a14\u0a15\u0a16\u0a17\u0a18\u0a19\u0a1a\u0a1b\u0a1c\u0a1d\u0a1e\u0a1f\u0a20\u0a21\u0a22\u0a23\u0a24\u0a25\u0a26\u0a27\u0a28\u0a2a\u0a2b\u0a2c\u0a2d\u0a2e\u0a2f\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59\u0a5a\u0a5b\u0a5c\u0a5e\u0a72\u0a73\u0a74\u0a85\u0a86\u0a87\u0a88\u0a89\u0a8a\u0a8b\u0a8c\u0a8d\u0a8f\u0a90\u0a91\u0a93\u0a94\u0a95\u0a96\u0a97\u0a98\u0a99\u0a9a\u0a9b\u0a9c\u0a9d\u0a9e\u0a9f\u0aa0\u0aa1\u0aa2\u0aa3\u0aa4\u0aa5\u0aa6\u0aa7\u0aa8\u0aaa\u0aab\u0aac\u0aad\u0aae\u0aaf\u0ab0\u0ab2\u0ab3\u0ab5\u0ab6\u0ab7\u0ab8\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0b05\u0b06\u0b07\u0b08\u0b09\u0b0a\u0b0b\u0b0c\u0b0f\u0b10\u0b13\u0b14\u0b15\u0b16\u0b17\u0b18\u0b19\u0b1a\u0b1b\u0b1c\u0b1d\u0b1e\u0b1f\u0b20\u0b21\u0b22\u0b23\u0b24\u0b25\u0b26\u0b27\u0b28\u0b2a\u0b2b\u0b2c\u0b2d\u0b2e\u0b2f\u0b30\u0b32\u0b33\u0b35\u0b36\u0b37\u0b38\u0b39\u0b3d\u0b5c\u0b5d\u0b5f\u0b60\u0b61\u0b71\u0b83\u0b85\u0b86\u0b87\u0b88\u0b89\u0b8a\u0b8e\u0b8f\u0b90\u0b92\u0b93\u0b94\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8\u0ba9\u0baa\u0bae\u0baf\u0bb0\u0bb1\u0bb2\u0bb3\u0bb4\u0bb5\u0bb6\u0bb7\u0bb8\u0bb9\u0c05\u0c06\u0c07\u0c08\u0c09\u0c0a\u0c0b\u0c0c\u0c0e\u0c0f\u0c10\u0c12\u0c13\u0c14\u0c15\u0c16\u0c17\u0c18\u0c19\u0c1a\u0c1b\u0c1c\u0c1d\u0c1e\u0c1f\u0c20\u0c21\u0c22\u0c23\u0c24\u0c25\u0c26\u0c27\u0c28\u0c2a\u0c2b\u0c2c\u0c2d\u0c2e\u0c2f\u0c30\u0c31\u0c32\u0c33\u0c35\u0c36\u0c37\u0c38\u0c39\u0c60\u0c61\u0c85\u0c86\u0c87\u0c88\u0c89\u0c8a\u0c8b\u0c8c\u0c8e\u0c8f\u0c90\u0c92\u0c93\u0c94\u0c95\u0c96\u0c97\u0c98\u0c99\u0c9a\u0c9b\u0c9c\u0c9d\u0c9e\u0c9f\u0ca0\u0ca1\u0ca2\u0ca3\u0ca4\u0ca5\u0ca6\u0ca7\u0ca8\u0caa\u0cab\u0cac\u0cad\u0cae\u0caf\u0cb0\u0cb1\u0cb2\u0cb3\u0cb5\u0cb6\u0cb7\u0cb8\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0d05\u0d06\u0d07\u0d08\u0d09\u0d0a\u0d0b\u0d0c\u0d0e\u0d0f\u0d10\u0d12\u0d13\u0d14\u0d15\u0d16\u0d17\u0d18\u0d19\u0d1a\u0d1b\u0d1c\u0d1d\u0d1e\u0d1f\u0d20\u0d21\u0d22\u0d23\u0d24\u0d25\u0d26\u0d27\u0d28\u0d2a\u0d2b\u0d2c\u0d2d\u0d2e\u0d2f\u0d30\u0d31\u0d32\u0d33\u0d34\u0d35\u0d36\u0d37\u0d38\u0d39\u0d60\u0d61\u0d85\u0d86\u0d87\u0d88\u0d89\u0d8a\u0d8b\u0d8c\u0d8d\u0d8e\u0d8f\u0d90\u0d91\u0d92\u0d93\u0d94\u0d95\u0d96\u0d9a\u0d9b\u0d9c\u0d9d\u0d9e\u0d9f\u0da0\u0da1\u0da2\u0da3\u0da4\u0da5\u0da6\u0da7\u0da8\u0da9\u0daa\u0dab\u0dac\u0dad\u0dae\u0daf\u0db0\u0db1\u0db3\u0db4\u0db5\u0db6\u0db7\u0db8\u0db9\u0dba\u0dbb\u0dbd\u0dc0\u0dc1\u0dc2\u0dc3\u0dc4\u0dc5\u0dc6\u0e01\u0e02\u0e03\u0e04\u0e05\u0e06\u0e07\u0e08\u0e09\u0e0a\u0e0b\u0e0c\u0e0d\u0e0e\u0e0f\u0e10\u0e11\u0e12\u0e13\u0e14\u0e15\u0e16\u0e17Line truncated
|
|
||||||
|
|
||||||
Lt = u'\u01c5\u01c8\u01cb\u01f2\u1f88\u1f89\u1f8a\u1f8b\u1f8c\u1f8d\u1f8e\u1f8f\u1f98\u1f99\u1f9a\u1f9b\u1f9c\u1f9d\u1f9e\u1f9f\u1fa8\u1fa9\u1faa\u1fab\u1fac\u1fad\u1fae\u1faf\u1fbc\u1fcc\u1ffc'
|
|
||||||
|
|
||||||
Lu = u'ABCDEFGHIJKLMNOPQRSTUVWXYZ\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd8\xd9\xda\xdb\xdc\xdd\xde\u0100\u0102\u0104\u0106\u0108\u010a\u010c\u010e\u0110\u0112\u0114\u0116\u0118\u011a\u011c\u011e\u0120\u0122\u0124\u0126\u0128\u012a\u012c\u012e\u0130\u0132\u0134\u0136\u0139\u013b\u013d\u013f\u0141\u0143\u0145\u0147\u014a\u014c\u014e\u0150\u0152\u0154\u0156\u0158\u015a\u015c\u015e\u0160\u0162\u0164\u0166\u0168\u016a\u016c\u016e\u0170\u0172\u0174\u0176\u0178\u0179\u017b\u017d\u0181\u0182\u0184\u0186\u0187\u0189\u018a\u018b\u018e\u018f\u0190\u0191\u0193\u0194\u0196\u0197\u0198\u019c\u019d\u019f\u01a0\u01a2\u01a4\u01a6\u01a7\u01a9\u01ac\u01ae\u01af\u01b1\u01b2\u01b3\u01b5\u01b7\u01b8\u01bc\u01c4\u01c7\u01ca\u01cd\u01cf\u01d1\u01d3\u01d5\u01d7\u01d9\u01db\u01de\u01e0\u01e2\u01e4\u01e6\u01e8\u01ea\u01ec\u01ee\u01f1\u01f4\u01f6\u01f7\u01f8\u01fa\u01fc\u01fe\u0200\u0202\u0204\u0206\u0208\u020a\u020c\u020e\u0210\u0212\u0214\u0216\u0218\u021a\u021c\u021e\u0220\u0222\u0224\u0226\u0228\u022a\u022c\u022e\u0230\u0232\u023a\u023b\u023d\u023e\u0241\u0386\u0388\u0389\u038a\u038c\u038e\u038f\u0391\u0392\u0393\u0394\u0395\u0396\u0397\u0398\u0399\u039a\u039b\u039c\u039d\u039e\u039f\u03a0\u03a1\u03a3\u03a4\u03a5\u03a6\u03a7\u03a8\u03a9\u03aa\u03ab\u03d2\u03d3\u03d4\u03d8\u03da\u03dc\u03de\u03e0\u03e2\u03e4\u03e6\u03e8\u03ea\u03ec\u03ee\u03f4\u03f7\u03f9\u03fa\u03fd\u03fe\u03ff\u0400\u0401\u0402\u0403\u0404\u0405\u0406\u0407\u0408\u0409\u040a\u040b\u040c\u040d\u040e\u040f\u0410\u0411\u0412\u0413\u0414\u0415\u0416\u0417\u0418\u0419\u041a\u041b\u041c\u041d\u041e\u041f\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042a\u042b\u042c\u042d\u042e\u042f\u0460\u0462\u0464\u0466\u0468\u046a\u046c\u046e\u0470\u0472\u0474\u0476\u0478\u047a\u047c\u047e\u0480\u048a\u048c\u048e\u0490\u0492\u0494\u0496\u0498\u049a\u049c\u049e\u04a0\u04a2\u04a4\u04a6\u04a8\u04aa\u04ac\u04ae\u04b0\u04b2\u04b4\u04b6\u04b8\u04ba\u04bc\u04be\u04c0\u04c1\u04c3\u04c5\u04c7\u04c9\u04cb\u04cd\u04d0\u04d2\u04d4\u04d6\u04d8\u04da\u04dc\u04de\u04e0\u04e2\u04e4\u04e6\u04e8\u04ea\u04ec\u04ee\u04f0\u04f2\u04f4\u04f6\u04f8\u0500\u0502\u0504\u0506\u0508\u050a\u050c\u050e\u0531\u0532\u0533\u0534\u0535\u0536\u0537\u0538\u0539\u053a\u053b\u053c\u053d\u053e\u053f\u0540\u0541\u0542\u0543\u0544\u0545\u0546\u0547\u0548\u0549\u054a\u054b\u054c\u054d\u054e\u054f\u0550\u0551\u0552\u0553\u0554\u0555\u0556\u10a0\u10a1\u10a2\u10a3\u10a4\u10a5\u10a6\u10a7\u10a8\u10a9\u10aa\u10ab\u10ac\u10ad\u10ae\u10af\u10b0\u10b1\u10b2\u10b3\u10b4\u10b5\u10b6\u10b7\u10b8\u10b9\u10ba\u10bb\u10bc\u10bd\u10be\u10bf\u10c0\u10c1\u10c2\u10c3\u10c4\u10c5\u1e00\u1e02\u1e04\u1e06\u1e08\u1e0a\u1e0c\u1e0e\u1e10\u1e12\u1e14\u1e16\u1e18\u1e1a\u1e1c\u1e1e\u1e20\u1e22\u1e24\u1e26\u1e28\u1e2a\u1e2c\u1e2e\u1e30\u1e32\u1e34\u1e36\u1e38\u1e3a\u1e3c\u1e3e\u1e40\u1e42\u1e44\u1e46\u1e48\u1e4a\u1e4c\u1e4e\u1e50\u1e52\u1e54\u1e56\u1e58\u1e5a\u1e5c\u1e5e\u1e60\u1e62\u1e64\u1e66\u1e68\u1e6a\u1e6c\u1e6e\u1e70\u1e72\u1e74\u1e76\u1e78\u1e7a\u1e7c\u1e7e\u1e80\u1e82\u1e84\u1e86\u1e88\u1e8a\u1e8c\u1e8e\u1e90\u1e92\u1e94\u1ea0\u1ea2\u1ea4\u1ea6\u1ea8\u1eaa\u1eac\u1eae\u1eb0\u1eb2\u1eb4\u1eb6\u1eb8\u1eba\u1ebc\u1ebe\u1ec0\u1ec2\u1ec4\u1ec6\u1ec8\u1eca\u1ecc\u1ece\u1ed0\u1ed2\u1ed4\u1ed6\u1ed8\u1eda\u1edc\u1ede\u1ee0\u1ee2\u1ee4\u1ee6\u1ee8\u1eea\u1eec\u1eee\u1ef0\u1ef2\u1ef4\u1ef6\u1ef8\u1f08\u1f09\u1f0a\u1f0b\u1f0c\u1f0d\u1f0e\u1f0f\u1f18\u1f19\u1f1a\u1f1b\u1f1c\u1f1d\u1f28\u1f29\u1f2a\u1f2b\u1f2c\u1f2d\u1f2e\u1f2f\u1f38\u1f39\u1f3a\u1f3b\u1f3c\u1f3d\u1f3e\u1f3f\u1f48\u1f49\u1f4a\u1f4b\u1f4c\u1f4d\u1f59\u1f5b\u1f5d\u1f5f\u1f68\u1f69\u1f6a\u1f6b\u1f6c\u1f6d\u1f6e\u1f6f\u1fb8\u1fb9\u1fba\u1fbb\u1fc8\u1fc9\u1fca\u1fcb\u1fd8\u1fd9\u1fda\u1fdb\u1fe8\u1fe9\u1fea\u1feb\u1fec\u1ff8\u1ff9\u1ffa\u1ffb\u2102\u2107\u210b\u210c\u210d\u2110\u2111\u2112\u2115\u2119\u211a\u211b\u211c\u211d\u2124\u2126\u2128\u212a\u212b\u212c\u212d\u2130\u2131\u2133\u213e\u213f\u2145\u2c00\u2c01\u2c02\u2c03\u2c04\u2c05\u2c06\u2c07\u2c08\u2c09\u2c0a\u2c0b\u2c0c\u2c0d\u2c0e\u2c0f\u2c10\u2c11\u2c12\u2c13\u2c14\u2c15\u2c16\u2c17\u2c18\u2c19\u2c1a\u2c1b\u2c1c\u2c1d\u2c1e\u2c1f\u2c20\u2c21\u2c22\u2c23\u2c24\u2c25\u2c26\u2c27\u2c28\u2c29\u2c2a\u2c2b\u2c2c\u2c2d\u2c2e\u2c80\u2c82\u2c84\u2c86\u2c88\u2c8a\u2c8c\u2c8e\u2c90\u2c92\u2c94\u2c96\u2c98\u2c9a\u2c9c\u2c9e\u2ca0\u2ca2\u2ca4\u2ca6\u2ca8\u2caa\u2cac\u2cae\u2cb0\u2cb2\u2cb4\u2cb6\u2cb8\u2cba\u2cbc\u2cbe\u2cc0\u2cc2\u2cc4\u2cc6\u2cc8\u2cca\u2ccc\u2cce\u2cd0\u2cd2\u2cd4\u2cd6\u2cd8\u2cda\u2cdc\u2cde\u2ce0\u2ce2\uff21\uff22\uff23\uff24\uff25\uff26\uff27\uff28\uff29\uff2a\uff2b\uff2c\uff2d\uff2e\uff2f\uff30\uff31\uff32\uff33\uff34\uff35\uff36\uff37\uff38\uff39\uff3a'
|
|
||||||
|
|
||||||
Mc = u'\u0903\u093e\u093f\u0940\u0949\u094a\u094b\u094c\u0982\u0983\u09be\u09bf\u09c0\u09c7\u09c8\u09cb\u09cc\u09d7\u0a03\u0a3e\u0a3f\u0a40\u0a83\u0abe\u0abf\u0ac0\u0ac9\u0acb\u0acc\u0b02\u0b03\u0b3e\u0b40\u0b47\u0b48\u0b4b\u0b4c\u0b57\u0bbe\u0bbf\u0bc1\u0bc2\u0bc6\u0bc7\u0bc8\u0bca\u0bcb\u0bcc\u0bd7\u0c01\u0c02\u0c03\u0c41\u0c42\u0c43\u0c44\u0c82\u0c83\u0cbe\u0cc0\u0cc1\u0cc2\u0cc3\u0cc4\u0cc7\u0cc8\u0cca\u0ccb\u0cd5\u0cd6\u0d02\u0d03\u0d3e\u0d3f\u0d40\u0d46\u0d47\u0d48\u0d4a\u0d4b\u0d4c\u0d57\u0d82\u0d83\u0dcf\u0dd0\u0dd1\u0dd8\u0dd9\u0dda\u0ddb\u0ddc\u0ddd\u0dde\u0ddf\u0df2\u0df3\u0f3e\u0f3f\u0f7f\u102c\u1031\u1038\u1056\u1057\u17b6\u17be\u17bf\u17c0\u17c1\u17c2\u17c3\u17c4\u17c5\u17c7\u17c8\u1923\u1924\u1925\u1926\u1929\u192a\u192b\u1930\u1931\u1933\u1934\u1935\u1936\u1937\u1938\u19b0\u19b1\u19b2\u19b3\u19b4\u19b5\u19b6\u19b7\u19b8\u19b9\u19ba\u19bb\u19bc\u19bd\u19be\u19bf\u19c0\u19c8\u19c9\u1a19\u1a1a\u1a1b\ua802\ua823\ua824\ua827'
|
|
||||||
|
|
||||||
Me = u'\u0488\u0489\u06de\u20dd\u20de\u20df\u20e0\u20e2\u20e3\u20e4'
|
|
||||||
|
|
||||||
Mn = u'\u0300\u0301\u0302\u0303\u0304\u0305\u0306\u0307\u0308\u0309\u030a\u030b\u030c\u030d\u030e\u030f\u0310\u0311\u0312\u0313\u0314\u0315\u0316\u0317\u0318\u0319\u031a\u031b\u031c\u031d\u031e\u031f\u0320\u0321\u0322\u0323\u0324\u0325\u0326\u0327\u0328\u0329\u032a\u032b\u032c\u032d\u032e\u032f\u0330\u0331\u0332\u0333\u0334\u0335\u0336\u0337\u0338\u0339\u033a\u033b\u033c\u033d\u033e\u033f\u0340\u0341\u0342\u0343\u0344\u0345\u0346\u0347\u0348\u0349\u034a\u034b\u034c\u034d\u034e\u034f\u0350\u0351\u0352\u0353\u0354\u0355\u0356\u0357\u0358\u0359\u035a\u035b\u035c\u035d\u035e\u035f\u0360\u0361\u0362\u0363\u0364\u0365\u0366\u0367\u0368\u0369\u036a\u036b\u036c\u036d\u036e\u036f\u0483\u0484\u0485\u0486\u0591\u0592\u0593\u0594\u0595\u0596\u0597\u0598\u0599\u059a\u059b\u059c\u059d\u059e\u059f\u05a0\u05a1\u05a2\u05a3\u05a4\u05a5\u05a6\u05a7\u05a8\u05a9\u05aa\u05ab\u05ac\u05ad\u05ae\u05af\u05b0\u05b1\u05b2\u05b3\u05b4\u05b5\u05b6\u05b7\u05b8\u05b9\u05bb\u05bc\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610\u0611\u0612\u0613\u0614\u0615\u064b\u064c\u064d\u064e\u064f\u0650\u0651\u0652\u0653\u0654\u0655\u0656\u0657\u0658\u0659\u065a\u065b\u065c\u065d\u065e\u0670\u06d6\u06d7\u06d8\u06d9\u06da\u06db\u06dc\u06df\u06e0\u06e1\u06e2\u06e3\u06e4\u06e7\u06e8\u06ea\u06eb\u06ec\u06ed\u0711\u0730\u0731\u0732\u0733\u0734\u0735\u0736\u0737\u0738\u0739\u073a\u073b\u073c\u073d\u073e\u073f\u0740\u0741\u0742\u0743\u0744\u0745\u0746\u0747\u0748\u0749\u074a\u07a6\u07a7\u07a8\u07a9\u07aa\u07ab\u07ac\u07ad\u07ae\u07af\u07b0\u0901\u0902\u093c\u0941\u0942\u0943\u0944\u0945\u0946\u0947\u0948\u094d\u0951\u0952\u0953\u0954\u0962\u0963\u0981\u09bc\u09c1\u09c2\u09c3\u09c4\u09cd\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b\u0a4c\u0a4d\u0a70\u0a71\u0a81\u0a82\u0abc\u0ac1\u0ac2\u0ac3\u0ac4\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3f\u0b41\u0b42\u0b43\u0b4d\u0b56\u0b82\u0bc0\u0bcd\u0c3e\u0c3f\u0c40\u0c46\u0c47\u0c48\u0c4a\u0c4b\u0c4c\u0c4d\u0c55\u0c56\u0cbc\u0cbf\u0cc6\u0ccc\u0ccd\u0d41\u0d42\u0d43\u0d4d\u0dca\u0dd2\u0dd3\u0dd4\u0dd6\u0e31\u0e34\u0e35\u0e36\u0e37\u0e38\u0e39\u0e3a\u0e47\u0e48\u0e49\u0e4a\u0e4b\u0e4c\u0e4d\u0e4e\u0eb1\u0eb4\u0eb5\u0eb6\u0eb7\u0eb8\u0eb9\u0ebb\u0ebc\u0ec8\u0ec9\u0eca\u0ecb\u0ecc\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71\u0f72\u0f73\u0f74\u0f75\u0f76\u0f77\u0f78\u0f79\u0f7a\u0f7b\u0f7c\u0f7d\u0f7e\u0f80\u0f81\u0f82\u0f83\u0f84\u0f86\u0f87\u0f90\u0f91\u0f92\u0f93\u0f94\u0f95\u0f96\u0f97\u0f99\u0f9a\u0f9b\u0f9c\u0f9d\u0f9e\u0f9f\u0fa0\u0fa1\u0fa2\u0fa3\u0fa4\u0fa5\u0fa6\u0fa7\u0fa8\u0fa9\u0faa\u0fab\u0fac\u0fad\u0fae\u0faf\u0fb0\u0fb1\u0fb2\u0fb3\u0fb4\u0fb5\u0fb6\u0fb7\u0fb8\u0fb9\u0fba\u0fbb\u0fbc\u0fc6\u102d\u102e\u102f\u1030\u1032\u1036\u1037\u1039\u1058\u1059\u135f\u1712\u1713\u1714\u1732\u1733\u1734\u1752\u1753\u1772\u1773\u17b7\u17b8\u17b9\u17ba\u17bb\u17bc\u17bd\u17c6\u17c9\u17ca\u17cb\u17cc\u17cd\u17ce\u17cf\u17d0\u17d1\u17d2\u17d3\u17dd\u180b\u180c\u180d\u18a9\u1920\u1921\u1922\u1927\u1928\u1932\u1939\u193a\u193b\u1a17\u1a18\u1dc0\u1dc1\u1dc2\u1dc3\u20d0\u20d1\u20d2\u20d3\u20d4\u20d5\u20d6\u20d7\u20d8\u20d9\u20da\u20db\u20dc\u20e1\u20e5\u20e6\u20e7\u20e8\u20e9\u20ea\u20eb\u302a\u302b\u302c\u302d\u302e\u302f\u3099\u309a\ua806\ua80b\ua825\ua826\ufb1e\ufe00\ufe01\ufe02\ufe03\ufe04\ufe05\ufe06\ufe07\ufe08\ufe09\ufe0a\ufe0b\ufe0c\ufe0d\ufe0e\ufe0f\ufe20\ufe21\ufe22\ufe23'
|
|
||||||
|
|
||||||
Nd = u'0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef\u0a66\u0a67\u0a68\u0a69\u0a6a\u0a6b\u0a6c\u0a6d\u0a6e\u0a6f\u0ae6\u0ae7\u0ae8\u0ae9\u0aea\u0aeb\u0aec\u0aed\u0aee\u0aef\u0b66\u0b67\u0b68\u0b69\u0b6a\u0b6b\u0b6c\u0b6d\u0b6e\u0b6f\u0be6\u0be7\u0be8\u0be9\u0bea\u0beb\u0bec\u0bed\u0bee\u0bef\u0c66\u0c67\u0c68\u0c69\u0c6a\u0c6b\u0c6c\u0c6d\u0c6e\u0c6f\u0ce6\u0ce7\u0ce8\u0ce9\u0cea\u0ceb\u0cec\u0ced\u0cee\u0cef\u0d66\u0d67\u0d68\u0d69\u0d6a\u0d6b\u0d6c\u0d6d\u0d6e\u0d6f\u0e50\u0e51\u0e52\u0e53\u0e54\u0e55\u0e56\u0e57\u0e58\u0e59\u0ed0\u0ed1\u0ed2\u0ed3\u0ed4\u0ed5\u0ed6\u0ed7\u0ed8\u0ed9\u0f20\u0f21\u0f22\u0f23\u0f24\u0f25\u0f26\u0f27\u0f28\u0f29\u1040\u1041\u1042\u1043\u1044\u1045\u1046\u1047\u1048\u1049\u17e0\u17e1\u17e2\u17e3\u17e4\u17e5\u17e6\u17e7\u17e8\u17e9\u1810\u1811\u1812\u1813\u1814\u1815\u1816\u1817\u1818\u1819\u1946\u1947\u1948\u1949\u194a\u194b\u194c\u194d\u194e\u194f\u19d0\u19d1\u19d2\u19d3\u19d4\u19d5\u19d6\u19d7\u19d8\u19d9\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19'
|
|
||||||
|
|
||||||
Nl = u'\u16ee\u16ef\u16f0\u2160\u2161\u2162\u2163\u2164\u2165\u2166\u2167\u2168\u2169\u216a\u216b\u216c\u216d\u216e\u216f\u2170\u2171\u2172\u2173\u2174\u2175\u2176\u2177\u2178\u2179\u217a\u217b\u217c\u217d\u217e\u217f\u2180\u2181\u2182\u2183\u3007\u3021\u3022\u3023\u3024\u3025\u3026\u3027\u3028\u3029\u3038\u3039\u303a'
|
|
||||||
|
|
||||||
No = u'\xb2\xb3\xb9\xbc\xbd\xbe\u09f4\u09f5\u09f6\u09f7\u09f8\u09f9\u0bf0\u0bf1\u0bf2\u0f2a\u0f2b\u0f2c\u0f2d\u0f2e\u0f2f\u0f30\u0f31\u0f32\u0f33\u1369\u136a\u136b\u136c\u136d\u136e\u136f\u1370\u1371\u1372\u1373\u1374\u1375\u1376\u1377\u1378\u1379\u137a\u137b\u137c\u17f0\u17f1\u17f2\u17f3\u17f4\u17f5\u17f6\u17f7\u17f8\u17f9\u2070\u2074\u2075\u2076\u2077\u2078\u2079\u2080\u2081\u2082\u2083\u2084\u2085\u2086\u2087\u2088\u2089\u2153\u2154\u2155\u2156\u2157\u2158\u2159\u215a\u215b\u215c\u215d\u215e\u215f\u2460\u2461\u2462\u2463\u2464\u2465\u2466\u2467\u2468\u2469\u246a\u246b\u246c\u246d\u246e\u246f\u2470\u2471\u2472\u2473\u2474\u2475\u2476\u2477\u2478\u2479\u247a\u247b\u247c\u247d\u247e\u247f\u2480\u2481\u2482\u2483\u2484\u2485\u2486\u2487\u2488\u2489\u248a\u248b\u248c\u248d\u248e\u248f\u2490\u2491\u2492\u2493\u2494\u2495\u2496\u2497\u2498\u2499\u249a\u249b\u24ea\u24eb\u24ec\u24ed\u24ee\u24ef\u24f0\u24f1\u24f2\u24f3\u24f4\u24f5\u24f6\u24f7\u24f8\u24f9\u24fa\u24fb\u24fc\u24fd\u24fe\u24ff\u2776\u2777\u2778\u2779\u277a\u277b\u277c\u277d\u277e\u277f\u2780\u2781\u2782\u2783\u2784\u2785\u2786\u2787\u2788\u2789\u278a\u278b\u278c\u278d\u278e\u278f\u2790\u2791\u2792\u2793\u2cfd\u3192\u3193\u3194\u3195\u3220\u3221\u3222\u3223\u3224\u3225\u3226\u3227\u3228\u3229\u3251\u3252\u3253\u3254\u3255\u3256\u3257\u3258\u3259\u325a\u325b\u325c\u325d\u325e\u325f\u3280\u3281\u3282\u3283\u3284\u3285\u3286\u3287\u3288\u3289\u32b1\u32b2\u32b3\u32b4\u32b5\u32b6\u32b7\u32b8\u32b9\u32ba\u32bb\u32bc\u32bd\u32be\u32bf'
|
|
||||||
|
|
||||||
Pc = u'_\u203f\u2040\u2054\ufe33\ufe34\ufe4d\ufe4e\ufe4f\uff3f'
|
|
||||||
|
|
||||||
Pd = u'-\u058a\u1806\u2010\u2011\u2012\u2013\u2014\u2015\u2e17\u301c\u3030\u30a0\ufe31\ufe32\ufe58\ufe63\uff0d'
|
|
||||||
|
|
||||||
Pe = u')]}\u0f3b\u0f3d\u169c\u2046\u207e\u208e\u232a\u23b5\u2769\u276b\u276d\u276f\u2771\u2773\u2775\u27c6\u27e7\u27e9\u27eb\u2984\u2986\u2988\u298a\u298c\u298e\u2990\u2992\u2994\u2996\u2998\u29d9\u29db\u29fd\u3009\u300b\u300d\u300f\u3011\u3015\u3017\u3019\u301b\u301e\u301f\ufd3f\ufe18\ufe36\ufe38\ufe3a\ufe3c\ufe3e\ufe40\ufe42\ufe44\ufe48\ufe5a\ufe5c\ufe5e\uff09\uff3d\uff5d\uff60\uff63'
|
|
||||||
|
|
||||||
Pf = u'\xbb\u2019\u201d\u203a\u2e03\u2e05\u2e0a\u2e0d\u2e1d'
|
|
||||||
|
|
||||||
Pi = u'\xab\u2018\u201b\u201c\u201f\u2039\u2e02\u2e04\u2e09\u2e0c\u2e1c'
|
|
||||||
|
|
||||||
Po = u'!"#%&\'*,./:;?@\\\xa1\xb7\xbf\u037e\u0387\u055a\u055b\u055c\u055d\u055e\u055f\u0589\u05be\u05c0\u05c3\u05c6\u05f3\u05f4\u060c\u060d\u061b\u061e\u061f\u066a\u066b\u066c\u066d\u06d4\u0700\u0701\u0702\u0703\u0704\u0705\u0706\u0707\u0708\u0709\u070a\u070b\u070c\u070d\u0964\u0965\u0970\u0df4\u0e4f\u0e5a\u0e5b\u0f04\u0f05\u0f06\u0f07\u0f08\u0f09\u0f0a\u0f0b\u0f0c\u0f0d\u0f0e\u0f0f\u0f10\u0f11\u0f12\u0f85\u0fd0\u0fd1\u104a\u104b\u104c\u104d\u104e\u104f\u10fb\u1361\u1362\u1363\u1364\u1365\u1366\u1367\u1368\u166d\u166e\u16eb\u16ec\u16ed\u1735\u1736\u17d4\u17d5\u17d6\u17d8\u17d9\u17da\u1800\u1801\u1802\u1803\u1804\u1805\u1807\u1808\u1809\u180a\u1944\u1945\u19de\u19df\u1a1e\u1a1f\u2016\u2017\u2020\u2021\u2022\u2023\u2024\u2025\u2026\u2027\u2030\u2031\u2032\u2033\u2034\u2035\u2036\u2037\u2038\u203b\u203c\u203d\u203e\u2041\u2042\u2043\u2047\u2048\u2049\u204a\u204b\u204c\u204d\u204e\u204f\u2050\u2051\u2053\u2055\u2056\u2057\u2058\u2059\u205a\u205b\u205c\u205d\u205e\u23b6\u2cf9\u2cfa\u2cfb\u2cfc\u2cfe\u2cff\u2e00\u2e01\u2e06\u2e07\u2e08\u2e0b\u2e0e\u2e0f\u2e10\u2e11\u2e12\u2e13\u2e14\u2e15\u2e16\u3001\u3002\u3003\u303d\u30fb\ufe10\ufe11\ufe12\ufe13\ufe14\ufe15\ufe16\ufe19\ufe30\ufe45\ufe46\ufe49\ufe4a\ufe4b\ufe4c\ufe50\ufe51\ufe52\ufe54\ufe55\ufe56\ufe57\ufe5f\ufe60\ufe61\ufe68\ufe6a\ufe6b\uff01\uff02\uff03\uff05\uff06\uff07\uff0a\uff0c\uff0e\uff0f\uff1a\uff1b\uff1f\uff20\uff3c\uff61\uff64\uff65'
|
|
||||||
|
|
||||||
Ps = u'([{\u0f3a\u0f3c\u169b\u201a\u201e\u2045\u207d\u208d\u2329\u23b4\u2768\u276a\u276c\u276e\u2770\u2772\u2774\u27c5\u27e6\u27e8\u27ea\u2983\u2985\u2987\u2989\u298b\u298d\u298f\u2991\u2993\u2995\u2997\u29d8\u29da\u29fc\u3008\u300a\u300c\u300e\u3010\u3014\u3016\u3018\u301a\u301d\ufd3e\ufe17\ufe35\ufe37\ufe39\ufe3b\ufe3d\ufe3f\ufe41\ufe43\ufe47\ufe59\ufe5b\ufe5d\uff08\uff3b\uff5b\uff5f\uff62'
|
|
||||||
|
|
||||||
Sc = u'$\xa2\xa3\xa4\xa5\u060b\u09f2\u09f3\u0af1\u0bf9\u0e3f\u17db\u20a0\u20a1\u20a2\u20a3\u20a4\u20a5\u20a6\u20a7\u20a8\u20a9\u20aa\u20ab\u20ac\u20ad\u20ae\u20af\u20b0\u20b1\u20b2\u20b3\u20b4\u20b5\ufdfc\ufe69\uff04\uffe0\uffe1\uffe5\uffe6'
|
|
||||||
|
|
||||||
Sk = u'^`\xa8\xaf\xb4\xb8\u02c2\u02c3\u02c4\u02c5\u02d2\u02d3\u02d4\u02d5\u02d6\u02d7\u02d8\u02d9\u02da\u02db\u02dc\u02dd\u02de\u02df\u02e5\u02e6\u02e7\u02e8\u02e9\u02ea\u02eb\u02ec\u02ed\u02ef\u02f0\u02f1\u02f2\u02f3\u02f4\u02f5\u02f6\u02f7\u02f8\u02f9\u02fa\u02fb\u02fc\u02fd\u02fe\u02ff\u0374\u0375\u0384\u0385\u1fbd\u1fbf\u1fc0\u1fc1\u1fcd\u1fce\u1fcf\u1fdd\u1fde\u1fdf\u1fed\u1fee\u1fef\u1ffd\u1ffe\u309b\u309c\ua700\ua701\ua702\ua703\ua704\ua705\ua706\ua707\ua708\ua709\ua70a\ua70b\ua70c\ua70d\ua70e\ua70f\ua710\ua711\ua712\ua713\ua714\ua715\ua716\uff3e\uff40\uffe3'
|
|
||||||
|
|
||||||
Sm = u'+<=>|~\xac\xb1\xd7\xf7\u03f6\u2044\u2052\u207a\u207b\u207c\u208a\u208b\u208c\u2140\u2141\u2142\u2143\u2144\u214b\u2190\u2191\u2192\u2193\u2194\u219a\u219b\u21a0\u21a3\u21a6\u21ae\u21ce\u21cf\u21d2\u21d4\u21f4\u21f5\u21f6\u21f7\u21f8\u21f9\u21fa\u21fb\u21fc\u21fd\u21fe\u21ff\u2200\u2201\u2202\u2203\u2204\u2205\u2206\u2207\u2208\u2209\u220a\u220b\u220c\u220d\u220e\u220f\u2210\u2211\u2212\u2213\u2214\u2215\u2216\u2217\u2218\u2219\u221a\u221b\u221c\u221d\u221e\u221f\u2220\u2221\u2222\u2223\u2224\u2225\u2226\u2227\u2228\u2229\u222a\u222b\u222c\u222d\u222e\u222f\u2230\u2231\u2232\u2233\u2234\u2235\u2236\u2237\u2238\u2239\u223a\u223b\u223c\u223d\u223e\u223f\u2240\u2241\u2242\u2243\u2244\u2245\u2246\u2247\u2248\u2249\u224a\u224b\u224c\u224d\u224e\u224f\u2250\u2251\u2252\u2253\u2254\u2255\u2256\u2257\u2258\u2259\u225a\u225b\u225c\u225d\u225e\u225f\u2260\u2261\u2262\u2263\u2264\u2265\u2266\u2267\u2268\u2269\u226a\u226b\u226c\u226d\u226e\u226f\u2270\u2271\u2272\u2273\u2274\u2275\u2276\u2277\u2278\u2279\u227a\u227b\u227c\u227d\u227e\u227f\u2280\u2281\u2282\u2283\u2284\u2285\u2286\u2287\u2288\u2289\u228a\u228b\u228c\u228d\u228e\u228f\u2290\u2291\u2292\u2293\u2294\u2295\u2296\u2297\u2298\u2299\u229a\u229b\u229c\u229d\u229e\u229f\u22a0\u22a1\u22a2\u22a3\u22a4\u22a5\u22a6\u22a7\u22a8\u22a9\u22aa\u22ab\u22ac\u22ad\u22ae\u22af\u22b0\u22b1\u22b2\u22b3\u22b4\u22b5\u22b6\u22b7\u22b8\u22b9\u22ba\u22bb\u22bc\u22bd\u22be\u22bf\u22c0\u22c1\u22c2\u22c3\u22c4\u22c5\u22c6\u22c7\u22c8\u22c9\u22ca\u22cb\u22cc\u22cd\u22ce\u22cf\u22d0\u22d1\u22d2\u22d3\u22d4\u22d5\u22d6\u22d7\u22d8\u22d9\u22da\u22db\u22dc\u22dd\u22de\u22df\u22e0\u22e1\u22e2\u22e3\u22e4\u22e5\u22e6\u22e7\u22e8\u22e9\u22ea\u22eb\u22ec\u22ed\u22ee\u22ef\u22f0\u22f1\u22f2\u22f3\u22f4\u22f5\u22f6\u22f7\u22f8\u22f9\u22fa\u22fb\u22fc\u22fd\u22fe\u22ff\u2308\u2309\u230a\u230b\u2320\u2321\u237c\u239b\u239c\u239d\u239e\u239f\u23a0\u23a1\u23a2\u23a3\u23a4\u23a5\u23a6\u23a7\u23a8\u23a9\u23aa\u23ab\u23ac\u23ad\u23ae\u23af\u23b0\u23b1\u23b2\u23b3\u25b7\u25c1\u25f8\u25f9\u25fa\u25fb\u25fc\u25fd\u25fe\u25ff\u266f\u27c0\u27c1\u27c2\u27c3\u27c4\u27d0\u27d1\u27d2\u27d3\u27d4\u27d5\u27d6\u27d7\u27d8\u27d9\u27da\u27db\u27dc\u27dd\u27de\u27df\u27e0\u27e1\u27e2\u27e3\u27e4\u27e5\u27f0\u27f1\u27f2\u27f3\u27f4\u27f5\u27f6\u27f7\u27f8\u27f9\u27fa\u27fb\u27fc\u27fd\u27fe\u27ff\u2900\u2901\u2902\u2903\u2904\u2905\u2906\u2907\u2908\u2909\u290a\u290b\u290c\u290d\u290e\u290f\u2910\u2911\u2912\u2913\u2914\u2915\u2916\u2917\u2918\u2919\u291a\u291b\u291c\u291d\u291e\u291f\u2920\u2921\u2922\u2923\u2924\u2925\u2926\u2927\u2928\u2929\u292a\u292b\u292c\u292d\u292e\u292f\u2930\u2931\u2932\u2933\u2934\u2935\u2936\u2937\u2938\u2939\u293a\u293b\u293c\u293d\u293e\u293f\u2940\u2941\u2942\u2943\u2944\u2945\u2946\u2947\u2948\u2949\u294a\u294b\u294c\u294d\u294e\u294f\u2950\u2951\u2952\u2953\u2954\u2955\u2956\u2957\u2958\u2959\u295a\u295b\u295c\u295d\u295e\u295f\u2960\u2961\u2962\u2963\u2964\u2965\u2966\u2967\u2968\u2969\u296a\u296b\u296c\u296d\u296e\u296f\u2970\u2971\u2972\u2973\u2974\u2975\u2976\u2977\u2978\u2979\u297a\u297b\u297c\u297d\u297e\u297f\u2980\u2981\u2982\u2999\u299a\u299b\u299c\u299d\u299e\u299f\u29a0\u29a1\u29a2\u29a3\u29a4\u29a5\u29a6\u29a7\u29a8\u29a9\u29aa\u29ab\u29ac\u29ad\u29ae\u29af\u29b0\u29b1\u29b2\u29b3\u29b4\u29b5\u29b6\u29b7\u29b8\u29b9\u29ba\u29bb\u29bc\u29bd\u29be\u29bf\u29c0\u29c1\u29c2\u29c3\u29c4\u29c5\u29c6\u29c7\u29c8\u29c9\u29ca\u29cb\u29cc\u29cd\u29ce\u29cf\u29d0\u29d1\u29d2\u29d3\u29d4\u29d5\u29d6\u29d7\u29dc\u29dd\u29de\u29df\u29e0\u29e1\u29e2\u29e3\u29e4\u29e5\u29e6\u29e7\u29e8\u29e9\u29ea\u29eb\u29ec\u29ed\u29ee\u29ef\u29f0\u29f1\u29f2\u29f3\u29f4\u29f5\u29f6\u29f7\u29f8\u29f9\u29fa\u29fb\u29fe\u29ff\u2a00\u2a01\u2a02\u2a03\u2a04\u2a05\u2a06\u2a07\u2a08\u2a09\u2a0a\u2a0b\u2a0c\u2a0d\u2a0e\u2a0f\u2a10\u2a11\u2a12\u2a13\u2a14\u2a15\u2a16\u2a17\u2a18\u2a19\u2a1a\u2a1b\u2a1c\u2a1d\u2a1e\u2a1f\u2a20\u2a21\u2a22\u2a23\u2a24\u2a25\u2a26\u2a27\u2a28\u2a29\u2a2a\u2a2b\u2a2c\u2a2d\u2a2e\u2a2f\u2a30\u2a31\u2a32\u2a33\u2a34\u2a35\u2a36\u2a37\u2a38\u2a39\u2a3a\u2a3b\u2a3c\u2a3d\u2a3e\u2a3f\u2a40\u2a41\u2a42\u2a43\u2a44\u2a45\u2a46\u2a47\u2a48\u2a49\u2a4a\u2a4b\u2a4c\u2a4d\u2a4e\u2a4f\u2a50\u2a51\u2a52\u2a53\u2a54\u2a55\u2a56\u2a57\u2a58\u2a59\u2a5a\u2a5b\u2a5c\u2a5d\u2a5e\u2a5f\u2a60\u2a61\u2a62\u2a63\u2a64\u2a65\u2a66\u2a67\u2a68\u2a69\u2a6a\u2a6b\u2a6c\u2a6d\u2a6e\u2a6f\u2a70\u2a71\u2a72\u2a73\u2a74\u2a75\u2a76\u2a77\u2a78\u2a79\u2a7a\u2a7b\u2a7c\u2a7d\u2a7e\u2a7f\u2a80\u2a81\u2a82\u2a83\u2a84\u2a85\u2a86\u2a87\u2a88\u2a89\u2a8a\u2a8b\u2a8c\u2a8d\u2a8e\u2a8f\u2a90\u2a91\u2a92\u2a93\u2a94\u2a95\u2a96\u2a97\u2a98\u2a99\u2a9a\u2a9b\u2a9c\u2a9d\u2a9e\u2a9f\u2aa0\u2aa1\u2aa2\u2aa3\u2aa4\u2aa5\u2aa6\u2aa7\u2aa8\u2aa9\u2aaa\u2aab\u2aac\u2aad\u2aae\u2aaf\u2ab0\u2ab1\u2ab2\u2ab3\u2ab4\u2ab5\u2ab6\u2ab7\u2ab8\u2ab9\u2aba\u2abb\u2abc\u2abd\u2abe\u2abf\u2ac0\u2ac1\u2ac2\u2ac3\u2ac4\u2ac5\u2ac6\u2ac7\u2ac8\u2ac9\u2aca\u2acb\u2acc\u2acd\u2ace\u2acf\u2ad0\u2ad1\u2ad2\u2ad3\u2ad4\u2ad5\u2ad6\u2ad7\uLine truncated
|
|
||||||
|
|
||||||
So = u'\xa6\xa7\xa9\xae\xb0\xb6\u0482\u060e\u060f\u06e9\u06fd\u06fe\u09fa\u0b70\u0bf3\u0bf4\u0bf5\u0bf6\u0bf7\u0bf8\u0bfa\u0f01\u0f02\u0f03\u0f13\u0f14\u0f15\u0f16\u0f17\u0f1a\u0f1b\u0f1c\u0f1d\u0f1e\u0f1f\u0f34\u0f36\u0f38\u0fbe\u0fbf\u0fc0\u0fc1\u0fc2\u0fc3\u0fc4\u0fc5\u0fc7\u0fc8\u0fc9\u0fca\u0fcb\u0fcc\u0fcf\u1360\u1390\u1391\u1392\u1393\u1394\u1395\u1396\u1397\u1398\u1399\u1940\u19e0\u19e1\u19e2\u19e3\u19e4\u19e5\u19e6\u19e7\u19e8\u19e9\u19ea\u19eb\u19ec\u19ed\u19ee\u19ef\u19f0\u19f1\u19f2\u19f3\u19f4\u19f5\u19f6\u19f7\u19f8\u19f9\u19fa\u19fb\u19fc\u19fd\u19fe\u19ff\u2100\u2101\u2103\u2104\u2105\u2106\u2108\u2109\u2114\u2116\u2117\u2118\u211e\u211f\u2120\u2121\u2122\u2123\u2125\u2127\u2129\u212e\u2132\u213a\u213b\u214a\u214c\u2195\u2196\u2197\u2198\u2199\u219c\u219d\u219e\u219f\u21a1\u21a2\u21a4\u21a5\u21a7\u21a8\u21a9\u21aa\u21ab\u21ac\u21ad\u21af\u21b0\u21b1\u21b2\u21b3\u21b4\u21b5\u21b6\u21b7\u21b8\u21b9\u21ba\u21bb\u21bc\u21bd\u21be\u21bf\u21c0\u21c1\u21c2\u21c3\u21c4\u21c5\u21c6\u21c7\u21c8\u21c9\u21ca\u21cb\u21cc\u21cd\u21d0\u21d1\u21d3\u21d5\u21d6\u21d7\u21d8\u21d9\u21da\u21db\u21dc\u21dd\u21de\u21df\u21e0\u21e1\u21e2\u21e3\u21e4\u21e5\u21e6\u21e7\u21e8\u21e9\u21ea\u21eb\u21ec\u21ed\u21ee\u21ef\u21f0\u21f1\u21f2\u21f3\u2300\u2301\u2302\u2303\u2304\u2305\u2306\u2307\u230c\u230d\u230e\u230f\u2310\u2311\u2312\u2313\u2314\u2315\u2316\u2317\u2318\u2319\u231a\u231b\u231c\u231d\u231e\u231f\u2322\u2323\u2324\u2325\u2326\u2327\u2328\u232b\u232c\u232d\u232e\u232f\u2330\u2331\u2332\u2333\u2334\u2335\u2336\u2337\u2338\u2339\u233a\u233b\u233c\u233d\u233e\u233f\u2340\u2341\u2342\u2343\u2344\u2345\u2346\u2347\u2348\u2349\u234a\u234b\u234c\u234d\u234e\u234f\u2350\u2351\u2352\u2353\u2354\u2355\u2356\u2357\u2358\u2359\u235a\u235b\u235c\u235d\u235e\u235f\u2360\u2361\u2362\u2363\u2364\u2365\u2366\u2367\u2368\u2369\u236a\u236b\u236c\u236d\u236e\u236f\u2370\u2371\u2372\u2373\u2374\u2375\u2376\u2377\u2378\u2379\u237a\u237b\u237d\u237e\u237f\u2380\u2381\u2382\u2383\u2384\u2385\u2386\u2387\u2388\u2389\u238a\u238b\u238c\u238d\u238e\u238f\u2390\u2391\u2392\u2393\u2394\u2395\u2396\u2397\u2398\u2399\u239a\u23b7\u23b8\u23b9\u23ba\u23bb\u23bc\u23bd\u23be\u23bf\u23c0\u23c1\u23c2\u23c3\u23c4\u23c5\u23c6\u23c7\u23c8\u23c9\u23ca\u23cb\u23cc\u23cd\u23ce\u23cf\u23d0\u23d1\u23d2\u23d3\u23d4\u23d5\u23d6\u23d7\u23d8\u23d9\u23da\u23db\u2400\u2401\u2402\u2403\u2404\u2405\u2406\u2407\u2408\u2409\u240a\u240b\u240c\u240d\u240e\u240f\u2410\u2411\u2412\u2413\u2414\u2415\u2416\u2417\u2418\u2419\u241a\u241b\u241c\u241d\u241e\u241f\u2420\u2421\u2422\u2423\u2424\u2425\u2426\u2440\u2441\u2442\u2443\u2444\u2445\u2446\u2447\u2448\u2449\u244a\u249c\u249d\u249e\u249f\u24a0\u24a1\u24a2\u24a3\u24a4\u24a5\u24a6\u24a7\u24a8\u24a9\u24aa\u24ab\u24ac\u24ad\u24ae\u24af\u24b0\u24b1\u24b2\u24b3\u24b4\u24b5\u24b6\u24b7\u24b8\u24b9\u24ba\u24bb\u24bc\u24bd\u24be\u24bf\u24c0\u24c1\u24c2\u24c3\u24c4\u24c5\u24c6\u24c7\u24c8\u24c9\u24ca\u24cb\u24cc\u24cd\u24ce\u24cf\u24d0\u24d1\u24d2\u24d3\u24d4\u24d5\u24d6\u24d7\u24d8\u24d9\u24da\u24db\u24dc\u24dd\u24de\u24df\u24e0\u24e1\u24e2\u24e3\u24e4\u24e5\u24e6\u24e7\u24e8\u24e9\u2500\u2501\u2502\u2503\u2504\u2505\u2506\u2507\u2508\u2509\u250a\u250b\u250c\u250d\u250e\u250f\u2510\u2511\u2512\u2513\u2514\u2515\u2516\u2517\u2518\u2519\u251a\u251b\u251c\u251d\u251e\u251f\u2520\u2521\u2522\u2523\u2524\u2525\u2526\u2527\u2528\u2529\u252a\u252b\u252c\u252d\u252e\u252f\u2530\u2531\u2532\u2533\u2534\u2535\u2536\u2537\u2538\u2539\u253a\u253b\u253c\u253d\u253e\u253f\u2540\u2541\u2542\u2543\u2544\u2545\u2546\u2547\u2548\u2549\u254a\u254b\u254c\u254d\u254e\u254f\u2550\u2551\u2552\u2553\u2554\u2555\u2556\u2557\u2558\u2559\u255a\u255b\u255c\u255d\u255e\u255f\u2560\u2561\u2562\u2563\u2564\u2565\u2566\u2567\u2568\u2569\u256a\u256b\u256c\u256d\u256e\u256f\u2570\u2571\u2572\u2573\u2574\u2575\u2576\u2577\u2578\u2579\u257a\u257b\u257c\u257d\u257e\u257f\u2580\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588\u2589\u258a\u258b\u258c\u258d\u258e\u258f\u2590\u2591\u2592\u2593\u2594\u2595\u2596\u2597\u2598\u2599\u259a\u259b\u259c\u259d\u259e\u259f\u25a0\u25a1\u25a2\u25a3\u25a4\u25a5\u25a6\u25a7\u25a8\u25a9\u25aa\u25ab\u25ac\u25ad\u25ae\u25af\u25b0\u25b1\u25b2\u25b3\u25b4\u25b5\u25b6\u25b8\u25b9\u25ba\u25bb\u25bc\u25bd\u25be\u25bf\u25c0\u25c2\u25c3\u25c4\u25c5\u25c6\u25c7\u25c8\u25c9\u25ca\u25cb\u25cc\u25cd\u25ce\u25cf\u25d0\u25d1\u25d2\u25d3\u25d4\u25d5\u25d6\u25d7\u25d8\u25d9\u25da\u25db\u25dc\u25dd\u25de\u25df\u25e0\u25e1\u25e2\u25e3\u25e4\u25e5\u25e6\u25e7\u25e8\u25e9\u25ea\u25eb\u25ec\u25ed\u25ee\u25ef\u25f0\u25f1\u25f2\u25f3\u25f4\u25f5\u25f6\u25f7\u2600\u2601\u2602\u2603\u2604\u2605\u2606\u2607\u2608\u2609\u260a\u260b\u260c\u260d\u260e\u260f\u2610\u2611\u2612\u2613\u2614\u2615\u2616\u2617\u2618\u2619\u261a\u261b\u261c\u261d\u261e\u261f\u2620\u2621\u2622\u2623\u2624\u2625\u2626\u2627\u2628\u2629\u262a\u262b\u262c\u262d\u262e\u262f\u2630\u2631\u2632\u2633\u2634\u2635\u2636\u2637\u2638\u2639\u263a\u263b\u263c\u263d\u263e\u263f\u2640\u2641\u2642\u2643Line truncated
|
|
||||||
|
|
||||||
Zl = u'\u2028'
|
|
||||||
|
|
||||||
Zp = u'\u2029'
|
|
||||||
|
|
||||||
Zs = u' \xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000'
|
|
||||||
|
|
||||||
cats = ['Cc', 'Cf', 'Cn', 'Co', 'Cs', 'Ll', 'Lm', 'Lo', 'Lt', 'Lu', 'Mc', 'Me', 'Mn', 'Nd', 'Nl', 'No', 'Pc', 'Pd', 'Pe', 'Pf', 'Pi', 'Po', 'Ps', 'Sc', 'Sk', 'Sm', 'So', 'Zl', 'Zp', 'Zs']
|
|
||||||
|
|
||||||
def combine(*args):
|
|
||||||
return u''.join([globals()[cat] for cat in args])
|
|
||||||
|
|
||||||
xid_start = u'\u0041-\u005A\u005F\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u01BA\u01BB\u01BC-\u01BF\u01C0-\u01C3\u01C4-\u0241\u0250-\u02AF\u02B0-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EE\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03CE\u03D0-\u03F5\u03F7-\u0481\u048A-\u04CE\u04D0-\u04F9\u0500-\u050F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0621-\u063A\u0640\u0641-\u064A\u066E-\u066F\u0671-\u06D3\u06D5\u06E5-\u06E6\u06EE-\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u076D\u0780-\u07A5\u07B1\u0904-\u0939\u093D\u0950\u0958-\u0961\u097D\u0985-\u098C\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC-\u09DD\u09DF-\u09E1\u09F0-\u09F1\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0-\u0AE1\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B35-\u0B39\u0B3D\u0B5C-\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C60-\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0-\u0CE1\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D60-\u0D61\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E40-\u0E45\u0E46\u0E81-\u0E82\u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EB0\u0EB2\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDD\u0F00\u0F40-\u0F47\u0F49-\u0F6A\u0F88-\u0F8B\u1000-\u1021\u1023-\u1027\u1029-\u102A\u1050-\u1055\u10A0-\u10C5\u10D0-\u10FA\u10FC\u1100-\u1159\u115F-\u11A2\u11A8-\u11F9\u1200-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u1676\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F0\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1842\u1843\u1844-\u1877\u1880-\u18A8\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19A9\u19C1-\u19C7\u1A00-\u1A16\u1D00-\u1D2B\u1D2C-\u1D61\u1D62-\u1D77\u1D78\u1D79-\u1D9A\u1D9B-\u1DBF\u1E00-\u1E9B\u1EA0-\u1EF9\u1F00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u2094\u2102\u2107\u210A-\u2113\u2115\u2118\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212E\u212F-\u2131\u2133-\u2134\u2135-\u2138\u2139\u213C-\u213F\u2145-\u2149\u2160-\u2183\u2C00-\u2C2E\u2C30-\u2C5E\u2C80-\u2CE4\u2D00-\u2D25\u2D30-\u2D65\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3005\u3006\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303A\u303B\u303C\u3041-\u3096\u309D-\u309E\u309F\u30A1-\u30FA\u30FC-\u30FE\u30FF\u3105-\u312C\u3131-\u318E\u31A0-\u31B7\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FBB\uA000-\uA014\uA015\uA016-\uA48C\uA800-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uAC00-\uD7A3\uF900-\uFA2D\uFA30-\uFA6A\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40-\uFB41\uFB43-\uFB44\uFB46-\uFBB1\uFBD3-\uFC5D\uFC64-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDF9\uFE71\uFE73\uFE77\uFE79\uFE7B\uFE7D\uFE7F-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFF6F\uFF70\uFF71-\uFF9D\uFFA0-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC'
|
|
||||||
|
|
||||||
xid_continue = u'\u0030-\u0039\u0041-\u005A\u005F\u0061-\u007A\u00AA\u00B5\u00B7\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u01BA\u01BB\u01BC-\u01BF\u01C0-\u01C3\u01C4-\u0241\u0250-\u02AF\u02B0-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EE\u0300-\u036F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03CE\u03D0-\u03F5\u03F7-\u0481\u0483-\u0486\u048A-\u04CE\u04D0-\u04F9\u0500-\u050F\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05B9\u05BB-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u05D0-\u05EA\u05F0-\u05F2\u0610-\u0615\u0621-\u063A\u0640\u0641-\u064A\u064B-\u065E\u0660-\u0669\u066E-\u066F\u0670\u0671-\u06D3\u06D5\u06D6-\u06DC\u06DF-\u06E4\u06E5-\u06E6\u06E7-\u06E8\u06EA-\u06ED\u06EE-\u06EF\u06F0-\u06F9\u06FA-\u06FC\u06FF\u0710\u0711\u0712-\u072F\u0730-\u074A\u074D-\u076D\u0780-\u07A5\u07A6-\u07B0\u07B1\u0901-\u0902\u0903\u0904-\u0939\u093C\u093D\u093E-\u0940\u0941-\u0948\u0949-\u094C\u094D\u0950\u0951-\u0954\u0958-\u0961\u0962-\u0963\u0966-\u096F\u097D\u0981\u0982-\u0983\u0985-\u098C\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC\u09BD\u09BE-\u09C0\u09C1-\u09C4\u09C7-\u09C8\u09CB-\u09CC\u09CD\u09CE\u09D7\u09DC-\u09DD\u09DF-\u09E1\u09E2-\u09E3\u09E6-\u09EF\u09F0-\u09F1\u0A01-\u0A02\u0A03\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39\u0A3C\u0A3E-\u0A40\u0A41-\u0A42\u0A47-\u0A48\u0A4B-\u0A4D\u0A59-\u0A5C\u0A5E\u0A66-\u0A6F\u0A70-\u0A71\u0A72-\u0A74\u0A81-\u0A82\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABC\u0ABD\u0ABE-\u0AC0\u0AC1-\u0AC5\u0AC7-\u0AC8\u0AC9\u0ACB-\u0ACC\u0ACD\u0AD0\u0AE0-\u0AE1\u0AE2-\u0AE3\u0AE6-\u0AEF\u0B01\u0B02-\u0B03\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B35-\u0B39\u0B3C\u0B3D\u0B3E\u0B3F\u0B40\u0B41-\u0B43\u0B47-\u0B48\u0B4B-\u0B4C\u0B4D\u0B56\u0B57\u0B5C-\u0B5D\u0B5F-\u0B61\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BBF\u0BC0\u0BC1-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCC\u0BCD\u0BD7\u0BE6-\u0BEF\u0C01-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3E-\u0C40\u0C41-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55-\u0C56\u0C60-\u0C61\u0C66-\u0C6F\u0C82-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC\u0CBD\u0CBE\u0CBF\u0CC0-\u0CC4\u0CC6\u0CC7-\u0CC8\u0CCA-\u0CCB\u0CCC-\u0CCD\u0CD5-\u0CD6\u0CDE\u0CE0-\u0CE1\u0CE6-\u0CEF\u0D02-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D3E-\u0D40\u0D41-\u0D43\u0D46-\u0D48\u0D4A-\u0D4C\u0D4D\u0D57\u0D60-\u0D61\u0D66-\u0D6F\u0D82-\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD1\u0DD2-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2-\u0DF3\u0E01-\u0E30\u0E31\u0E32-\u0E33\u0E34-\u0E3A\u0E40-\u0E45\u0E46\u0E47-\u0E4E\u0E50-\u0E59\u0E81-\u0E82\u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EB0\u0EB1\u0EB2-\u0EB3\u0EB4-\u0EB9\u0EBB-\u0EBC\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDD\u0F00\u0F18-\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F3F\u0F40-\u0F47\u0F49-\u0F6A\u0F71-\u0F7E\u0F7F\u0F80-\u0F84\u0F86-\u0F87\u0F88-\u0F8B\u0F90-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1021\u1023-\u1027\u1029-\u102A\u102C\u102D-\u1030\u1031\u1032\u1036-\u1037\u1038\u1039\u1040-\u1049\u1050-\u1055\u1056-\u1057\u1058-\u1059\u10A0-\u10C5\u10D0-\u10FA\u10FC\u1100-\u1159\u115F-\u11A2\u11A8-\u11F9\u1200-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135F\u1369-\u1371\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u1676\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F0\u1700-\u170C\u170E-\u1711\u1712-\u1714\u1720-\u1731\u1732-\u1734\u1740-\u1751\u1752-\u1753\u1760-\u176C\u176E-\u1770\u1772-\u1773\u1780-\u17B3\u17B6\u17B7-\u17BD\u17BE-\u17C5\u17C6\u17C7-\u17C8\u17C9-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1842\u1843\u1844-\u1877\u1880-\u18A8\u18A9\u1900-\u191C\u1920-\u1922\u1923-\u1926\u1927-\u1928\u1929-\u192B\u1930-\u1931\u1932\u1933-\u1938\u1939-\u193B\u1946-\u194F\u1950-\u196D\u1970-\u1974\u1980-\u19A9\u19B0-\u19C0\u19C1-\u19C7\u19C8-\u19C9\u19D0-\u19D9\u1A00-\u1A16\u1A17-\u1A18\u1A19-\u1A1B\u1D00-\u1D2B\u1D2C-\u1D61\u1D62-\u1D77\u1D78\u1D79-\u1D9A\u1D9B-\u1DBF\u1DC0-\u1DC3\u1E00-\u1E9B\u1EA0-\u1EF9\u1F00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u203F-\u2040\u2054\u2071\u207F\u2090-\u2094\u20D0-\u20DC\u20E1\u20E5-\u20EB\u2102\u2107\u210A-\u2113\u2115\u2118\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212E\u212F-\u2131\u2133-\u2134\u2135-\u2138\u2139\u213C-\u213F\u2145-\u2149\u2160-\u2183\u2C00-\u2C2E\u2C30-\u2C5E\u2C80-\u2CE4\u2D00-\u2D25\u2D30-\u2D65\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DLine truncated
|
|
||||||
|
|
||||||
def allexcept(*args):
|
|
||||||
newcats = cats[:]
|
|
||||||
for arg in args:
|
|
||||||
newcats.remove(arg)
|
|
||||||
return u''.join([globals()[cat] for cat in newcats])
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
import unicodedata
|
|
||||||
|
|
||||||
categories = {}
|
|
||||||
|
|
||||||
f = open(__file__.rstrip('co'))
|
|
||||||
try:
|
|
||||||
content = f.read()
|
|
||||||
finally:
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
header = content[:content.find('Cc =')]
|
|
||||||
footer = content[content.find("def combine("):]
|
|
||||||
|
|
||||||
for code in range(65535):
|
|
||||||
c = unichr(code)
|
|
||||||
cat = unicodedata.category(c)
|
|
||||||
categories.setdefault(cat, []).append(c)
|
|
||||||
|
|
||||||
f = open(__file__, 'w')
|
|
||||||
f.write(header)
|
|
||||||
|
|
||||||
for cat in sorted(categories):
|
|
||||||
val = u''.join(categories[cat])
|
|
||||||
if cat == 'Cs':
|
|
||||||
# Jython can't handle isolated surrogates
|
|
||||||
f.write("""\
|
|
||||||
try:
|
|
||||||
Cs = eval(r"%r")
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
Cs = '' # Jython can't handle isolated surrogates\n\n""" % val)
|
|
||||||
else:
|
|
||||||
f.write('%s = %r\n\n' % (cat, val))
|
|
||||||
f.write('cats = %r\n\n' % sorted(categories.keys()))
|
|
||||||
|
|
||||||
f.write(footer)
|
|
||||||
f.close()
|
|
||||||
@@ -1,301 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
jinja2.bccache
|
|
||||||
~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
This module implements the bytecode cache system Jinja is optionally
|
|
||||||
using. This is useful if you have very complex template situations and
|
|
||||||
the compiliation of all those templates slow down your application too
|
|
||||||
much.
|
|
||||||
|
|
||||||
Situations where this is useful are often forking web applications that
|
|
||||||
are initialized on the first request.
|
|
||||||
|
|
||||||
:copyright: (c) 2010 by the Jinja Team.
|
|
||||||
:license: BSD.
|
|
||||||
"""
|
|
||||||
from os import path, listdir
|
|
||||||
import sys
|
|
||||||
import marshal
|
|
||||||
import tempfile
|
|
||||||
import cPickle as pickle
|
|
||||||
import fnmatch
|
|
||||||
try:
|
|
||||||
from hashlib import sha1
|
|
||||||
except ImportError:
|
|
||||||
from sha import new as sha1
|
|
||||||
from jinja2.utils import open_if_exists
|
|
||||||
|
|
||||||
|
|
||||||
# marshal works better on 3.x, one hack less required
|
|
||||||
if sys.version_info > (3, 0):
|
|
||||||
from io import BytesIO
|
|
||||||
marshal_dump = marshal.dump
|
|
||||||
marshal_load = marshal.load
|
|
||||||
else:
|
|
||||||
from cStringIO import StringIO as BytesIO
|
|
||||||
|
|
||||||
def marshal_dump(code, f):
|
|
||||||
if isinstance(f, file):
|
|
||||||
marshal.dump(code, f)
|
|
||||||
else:
|
|
||||||
f.write(marshal.dumps(code))
|
|
||||||
|
|
||||||
def marshal_load(f):
|
|
||||||
if isinstance(f, file):
|
|
||||||
return marshal.load(f)
|
|
||||||
return marshal.loads(f.read())
|
|
||||||
|
|
||||||
|
|
||||||
bc_version = 2
|
|
||||||
|
|
||||||
# magic version used to only change with new jinja versions. With 2.6
|
|
||||||
# we change this to also take Python version changes into account. The
|
|
||||||
# reason for this is that Python tends to segfault if fed earlier bytecode
|
|
||||||
# versions because someone thought it would be a good idea to reuse opcodes
|
|
||||||
# or make Python incompatible with earlier versions.
|
|
||||||
bc_magic = 'j2'.encode('ascii') + \
|
|
||||||
pickle.dumps(bc_version, 2) + \
|
|
||||||
pickle.dumps((sys.version_info[0] << 24) | sys.version_info[1])
|
|
||||||
|
|
||||||
|
|
||||||
class Bucket(object):
|
|
||||||
"""Buckets are used to store the bytecode for one template. It's created
|
|
||||||
and initialized by the bytecode cache and passed to the loading functions.
|
|
||||||
|
|
||||||
The buckets get an internal checksum from the cache assigned and use this
|
|
||||||
to automatically reject outdated cache material. Individual bytecode
|
|
||||||
cache subclasses don't have to care about cache invalidation.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, environment, key, checksum):
|
|
||||||
self.environment = environment
|
|
||||||
self.key = key
|
|
||||||
self.checksum = checksum
|
|
||||||
self.reset()
|
|
||||||
|
|
||||||
def reset(self):
|
|
||||||
"""Resets the bucket (unloads the bytecode)."""
|
|
||||||
self.code = None
|
|
||||||
|
|
||||||
def load_bytecode(self, f):
|
|
||||||
"""Loads bytecode from a file or file like object."""
|
|
||||||
# make sure the magic header is correct
|
|
||||||
magic = f.read(len(bc_magic))
|
|
||||||
if magic != bc_magic:
|
|
||||||
self.reset()
|
|
||||||
return
|
|
||||||
# the source code of the file changed, we need to reload
|
|
||||||
checksum = pickle.load(f)
|
|
||||||
if self.checksum != checksum:
|
|
||||||
self.reset()
|
|
||||||
return
|
|
||||||
self.code = marshal_load(f)
|
|
||||||
|
|
||||||
def write_bytecode(self, f):
|
|
||||||
"""Dump the bytecode into the file or file like object passed."""
|
|
||||||
if self.code is None:
|
|
||||||
raise TypeError('can\'t write empty bucket')
|
|
||||||
f.write(bc_magic)
|
|
||||||
pickle.dump(self.checksum, f, 2)
|
|
||||||
marshal_dump(self.code, f)
|
|
||||||
|
|
||||||
def bytecode_from_string(self, string):
|
|
||||||
"""Load bytecode from a string."""
|
|
||||||
self.load_bytecode(BytesIO(string))
|
|
||||||
|
|
||||||
def bytecode_to_string(self):
|
|
||||||
"""Return the bytecode as string."""
|
|
||||||
out = BytesIO()
|
|
||||||
self.write_bytecode(out)
|
|
||||||
return out.getvalue()
|
|
||||||
|
|
||||||
|
|
||||||
class BytecodeCache(object):
|
|
||||||
"""To implement your own bytecode cache you have to subclass this class
|
|
||||||
and override :meth:`load_bytecode` and :meth:`dump_bytecode`. Both of
|
|
||||||
these methods are passed a :class:`~jinja2.bccache.Bucket`.
|
|
||||||
|
|
||||||
A very basic bytecode cache that saves the bytecode on the file system::
|
|
||||||
|
|
||||||
from os import path
|
|
||||||
|
|
||||||
class MyCache(BytecodeCache):
|
|
||||||
|
|
||||||
def __init__(self, directory):
|
|
||||||
self.directory = directory
|
|
||||||
|
|
||||||
def load_bytecode(self, bucket):
|
|
||||||
filename = path.join(self.directory, bucket.key)
|
|
||||||
if path.exists(filename):
|
|
||||||
with open(filename, 'rb') as f:
|
|
||||||
bucket.load_bytecode(f)
|
|
||||||
|
|
||||||
def dump_bytecode(self, bucket):
|
|
||||||
filename = path.join(self.directory, bucket.key)
|
|
||||||
with open(filename, 'wb') as f:
|
|
||||||
bucket.write_bytecode(f)
|
|
||||||
|
|
||||||
A more advanced version of a filesystem based bytecode cache is part of
|
|
||||||
Jinja2.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def load_bytecode(self, bucket):
|
|
||||||
"""Subclasses have to override this method to load bytecode into a
|
|
||||||
bucket. If they are not able to find code in the cache for the
|
|
||||||
bucket, it must not do anything.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def dump_bytecode(self, bucket):
|
|
||||||
"""Subclasses have to override this method to write the bytecode
|
|
||||||
from a bucket back to the cache. If it unable to do so it must not
|
|
||||||
fail silently but raise an exception.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
"""Clears the cache. This method is not used by Jinja2 but should be
|
|
||||||
implemented to allow applications to clear the bytecode cache used
|
|
||||||
by a particular environment.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get_cache_key(self, name, filename=None):
|
|
||||||
"""Returns the unique hash key for this template name."""
|
|
||||||
hash = sha1(name.encode('utf-8'))
|
|
||||||
if filename is not None:
|
|
||||||
filename = '|' + filename
|
|
||||||
if isinstance(filename, unicode):
|
|
||||||
filename = filename.encode('utf-8')
|
|
||||||
hash.update(filename)
|
|
||||||
return hash.hexdigest()
|
|
||||||
|
|
||||||
def get_source_checksum(self, source):
|
|
||||||
"""Returns a checksum for the source."""
|
|
||||||
return sha1(source.encode('utf-8')).hexdigest()
|
|
||||||
|
|
||||||
def get_bucket(self, environment, name, filename, source):
|
|
||||||
"""Return a cache bucket for the given template. All arguments are
|
|
||||||
mandatory but filename may be `None`.
|
|
||||||
"""
|
|
||||||
key = self.get_cache_key(name, filename)
|
|
||||||
checksum = self.get_source_checksum(source)
|
|
||||||
bucket = Bucket(environment, key, checksum)
|
|
||||||
self.load_bytecode(bucket)
|
|
||||||
return bucket
|
|
||||||
|
|
||||||
def set_bucket(self, bucket):
|
|
||||||
"""Put the bucket into the cache."""
|
|
||||||
self.dump_bytecode(bucket)
|
|
||||||
|
|
||||||
|
|
||||||
class FileSystemBytecodeCache(BytecodeCache):
|
|
||||||
"""A bytecode cache that stores bytecode on the filesystem. It accepts
|
|
||||||
two arguments: The directory where the cache items are stored and a
|
|
||||||
pattern string that is used to build the filename.
|
|
||||||
|
|
||||||
If no directory is specified the system temporary items folder is used.
|
|
||||||
|
|
||||||
The pattern can be used to have multiple separate caches operate on the
|
|
||||||
same directory. The default pattern is ``'__jinja2_%s.cache'``. ``%s``
|
|
||||||
is replaced with the cache key.
|
|
||||||
|
|
||||||
>>> bcc = FileSystemBytecodeCache('/tmp/jinja_cache', '%s.cache')
|
|
||||||
|
|
||||||
This bytecode cache supports clearing of the cache using the clear method.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, directory=None, pattern='__jinja2_%s.cache'):
|
|
||||||
if directory is None:
|
|
||||||
directory = tempfile.gettempdir()
|
|
||||||
self.directory = directory
|
|
||||||
self.pattern = pattern
|
|
||||||
|
|
||||||
def _get_cache_filename(self, bucket):
|
|
||||||
return path.join(self.directory, self.pattern % bucket.key)
|
|
||||||
|
|
||||||
def load_bytecode(self, bucket):
|
|
||||||
f = open_if_exists(self._get_cache_filename(bucket), 'rb')
|
|
||||||
if f is not None:
|
|
||||||
try:
|
|
||||||
bucket.load_bytecode(f)
|
|
||||||
finally:
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
def dump_bytecode(self, bucket):
|
|
||||||
f = open(self._get_cache_filename(bucket), 'wb')
|
|
||||||
try:
|
|
||||||
bucket.write_bytecode(f)
|
|
||||||
finally:
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
# imported lazily here because google app-engine doesn't support
|
|
||||||
# write access on the file system and the function does not exist
|
|
||||||
# normally.
|
|
||||||
from os import remove
|
|
||||||
files = fnmatch.filter(listdir(self.directory), self.pattern % '*')
|
|
||||||
for filename in files:
|
|
||||||
try:
|
|
||||||
remove(path.join(self.directory, filename))
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class MemcachedBytecodeCache(BytecodeCache):
|
|
||||||
"""This class implements a bytecode cache that uses a memcache cache for
|
|
||||||
storing the information. It does not enforce a specific memcache library
|
|
||||||
(tummy's memcache or cmemcache) but will accept any class that provides
|
|
||||||
the minimal interface required.
|
|
||||||
|
|
||||||
Libraries compatible with this class:
|
|
||||||
|
|
||||||
- `werkzeug <http://werkzeug.pocoo.org/>`_.contrib.cache
|
|
||||||
- `python-memcached <http://www.tummy.com/Community/software/python-memcached/>`_
|
|
||||||
- `cmemcache <http://gijsbert.org/cmemcache/>`_
|
|
||||||
|
|
||||||
(Unfortunately the django cache interface is not compatible because it
|
|
||||||
does not support storing binary data, only unicode. You can however pass
|
|
||||||
the underlying cache client to the bytecode cache which is available
|
|
||||||
as `django.core.cache.cache._client`.)
|
|
||||||
|
|
||||||
The minimal interface for the client passed to the constructor is this:
|
|
||||||
|
|
||||||
.. class:: MinimalClientInterface
|
|
||||||
|
|
||||||
.. method:: set(key, value[, timeout])
|
|
||||||
|
|
||||||
Stores the bytecode in the cache. `value` is a string and
|
|
||||||
`timeout` the timeout of the key. If timeout is not provided
|
|
||||||
a default timeout or no timeout should be assumed, if it's
|
|
||||||
provided it's an integer with the number of seconds the cache
|
|
||||||
item should exist.
|
|
||||||
|
|
||||||
.. method:: get(key)
|
|
||||||
|
|
||||||
Returns the value for the cache key. If the item does not
|
|
||||||
exist in the cache the return value must be `None`.
|
|
||||||
|
|
||||||
The other arguments to the constructor are the prefix for all keys that
|
|
||||||
is added before the actual cache key and the timeout for the bytecode in
|
|
||||||
the cache system. We recommend a high (or no) timeout.
|
|
||||||
|
|
||||||
This bytecode cache does not support clearing of used items in the cache.
|
|
||||||
The clear method is a no-operation function.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, client, prefix='jinja2/bytecode/', timeout=None):
|
|
||||||
self.client = client
|
|
||||||
self.prefix = prefix
|
|
||||||
self.timeout = timeout
|
|
||||||
|
|
||||||
def load_bytecode(self, bucket):
|
|
||||||
code = self.client.get(self.prefix + bucket.key)
|
|
||||||
if code is not None:
|
|
||||||
bucket.bytecode_from_string(code)
|
|
||||||
|
|
||||||
def dump_bytecode(self, bucket):
|
|
||||||
args = (self.prefix + bucket.key, bucket.bytecode_to_string())
|
|
||||||
if self.timeout is not None:
|
|
||||||
args += (self.timeout,)
|
|
||||||
self.client.set(*args)
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -1,32 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
jinja.constants
|
|
||||||
~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Various constants.
|
|
||||||
|
|
||||||
:copyright: (c) 2010 by the Jinja Team.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
#: list of lorem ipsum words used by the lipsum() helper function
|
|
||||||
LOREM_IPSUM_WORDS = u'''\
|
|
||||||
a ac accumsan ad adipiscing aenean aliquam aliquet amet ante aptent arcu at
|
|
||||||
auctor augue bibendum blandit class commodo condimentum congue consectetuer
|
|
||||||
consequat conubia convallis cras cubilia cum curabitur curae cursus dapibus
|
|
||||||
diam dictum dictumst dignissim dis dolor donec dui duis egestas eget eleifend
|
|
||||||
elementum elit enim erat eros est et etiam eu euismod facilisi facilisis fames
|
|
||||||
faucibus felis fermentum feugiat fringilla fusce gravida habitant habitasse hac
|
|
||||||
hendrerit hymenaeos iaculis id imperdiet in inceptos integer interdum ipsum
|
|
||||||
justo lacinia lacus laoreet lectus leo libero ligula litora lobortis lorem
|
|
||||||
luctus maecenas magna magnis malesuada massa mattis mauris metus mi molestie
|
|
||||||
mollis montes morbi mus nam nascetur natoque nec neque netus nibh nisi nisl non
|
|
||||||
nonummy nostra nulla nullam nunc odio orci ornare parturient pede pellentesque
|
|
||||||
penatibus per pharetra phasellus placerat platea porta porttitor posuere
|
|
||||||
potenti praesent pretium primis proin pulvinar purus quam quis quisque rhoncus
|
|
||||||
ridiculus risus rutrum sagittis sapien scelerisque sed sem semper senectus sit
|
|
||||||
sociis sociosqu sodales sollicitudin suscipit suspendisse taciti tellus tempor
|
|
||||||
tempus tincidunt torquent tortor tristique turpis ullamcorper ultrices
|
|
||||||
ultricies urna ut varius vehicula vel velit venenatis vestibulum vitae vivamus
|
|
||||||
viverra volutpat vulputate'''
|
|
||||||
@@ -1,339 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
jinja2.debug
|
|
||||||
~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Implements the debug interface for Jinja. This module does some pretty
|
|
||||||
ugly stuff with the Python traceback system in order to achieve tracebacks
|
|
||||||
with correct line numbers, locals and contents.
|
|
||||||
|
|
||||||
:copyright: (c) 2010 by the Jinja Team.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
import traceback
|
|
||||||
from types import TracebackType
|
|
||||||
from jinja2.utils import CodeType, missing, internal_code
|
|
||||||
from jinja2.exceptions import TemplateSyntaxError
|
|
||||||
|
|
||||||
# on pypy we can take advantage of transparent proxies
|
|
||||||
try:
|
|
||||||
from __pypy__ import tproxy
|
|
||||||
except ImportError:
|
|
||||||
tproxy = None
|
|
||||||
|
|
||||||
|
|
||||||
# how does the raise helper look like?
|
|
||||||
try:
|
|
||||||
exec "raise TypeError, 'foo'"
|
|
||||||
except SyntaxError:
|
|
||||||
raise_helper = 'raise __jinja_exception__[1]'
|
|
||||||
except TypeError:
|
|
||||||
raise_helper = 'raise __jinja_exception__[0], __jinja_exception__[1]'
|
|
||||||
|
|
||||||
|
|
||||||
class TracebackFrameProxy(object):
|
|
||||||
"""Proxies a traceback frame."""
|
|
||||||
|
|
||||||
def __init__(self, tb):
|
|
||||||
self.tb = tb
|
|
||||||
self._tb_next = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def tb_next(self):
|
|
||||||
return self._tb_next
|
|
||||||
|
|
||||||
def set_next(self, next):
|
|
||||||
if tb_set_next is not None:
|
|
||||||
try:
|
|
||||||
tb_set_next(self.tb, next and next.tb or None)
|
|
||||||
except Exception:
|
|
||||||
# this function can fail due to all the hackery it does
|
|
||||||
# on various python implementations. We just catch errors
|
|
||||||
# down and ignore them if necessary.
|
|
||||||
pass
|
|
||||||
self._tb_next = next
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_jinja_frame(self):
|
|
||||||
return '__jinja_template__' in self.tb.tb_frame.f_globals
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
return getattr(self.tb, name)
|
|
||||||
|
|
||||||
|
|
||||||
def make_frame_proxy(frame):
|
|
||||||
proxy = TracebackFrameProxy(frame)
|
|
||||||
if tproxy is None:
|
|
||||||
return proxy
|
|
||||||
def operation_handler(operation, *args, **kwargs):
|
|
||||||
if operation in ('__getattribute__', '__getattr__'):
|
|
||||||
return getattr(proxy, args[0])
|
|
||||||
elif operation == '__setattr__':
|
|
||||||
proxy.__setattr__(*args, **kwargs)
|
|
||||||
else:
|
|
||||||
return getattr(proxy, operation)(*args, **kwargs)
|
|
||||||
return tproxy(TracebackType, operation_handler)
|
|
||||||
|
|
||||||
|
|
||||||
class ProcessedTraceback(object):
|
|
||||||
"""Holds a Jinja preprocessed traceback for priting or reraising."""
|
|
||||||
|
|
||||||
def __init__(self, exc_type, exc_value, frames):
|
|
||||||
assert frames, 'no frames for this traceback?'
|
|
||||||
self.exc_type = exc_type
|
|
||||||
self.exc_value = exc_value
|
|
||||||
self.frames = frames
|
|
||||||
|
|
||||||
# newly concatenate the frames (which are proxies)
|
|
||||||
prev_tb = None
|
|
||||||
for tb in self.frames:
|
|
||||||
if prev_tb is not None:
|
|
||||||
prev_tb.set_next(tb)
|
|
||||||
prev_tb = tb
|
|
||||||
prev_tb.set_next(None)
|
|
||||||
|
|
||||||
def render_as_text(self, limit=None):
|
|
||||||
"""Return a string with the traceback."""
|
|
||||||
lines = traceback.format_exception(self.exc_type, self.exc_value,
|
|
||||||
self.frames[0], limit=limit)
|
|
||||||
return ''.join(lines).rstrip()
|
|
||||||
|
|
||||||
def render_as_html(self, full=False):
|
|
||||||
"""Return a unicode string with the traceback as rendered HTML."""
|
|
||||||
from jinja2.debugrenderer import render_traceback
|
|
||||||
return u'%s\n\n<!--\n%s\n-->' % (
|
|
||||||
render_traceback(self, full=full),
|
|
||||||
self.render_as_text().decode('utf-8', 'replace')
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_template_syntax_error(self):
|
|
||||||
"""`True` if this is a template syntax error."""
|
|
||||||
return isinstance(self.exc_value, TemplateSyntaxError)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def exc_info(self):
|
|
||||||
"""Exception info tuple with a proxy around the frame objects."""
|
|
||||||
return self.exc_type, self.exc_value, self.frames[0]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def standard_exc_info(self):
|
|
||||||
"""Standard python exc_info for re-raising"""
|
|
||||||
tb = self.frames[0]
|
|
||||||
# the frame will be an actual traceback (or transparent proxy) if
|
|
||||||
# we are on pypy or a python implementation with support for tproxy
|
|
||||||
if type(tb) is not TracebackType:
|
|
||||||
tb = tb.tb
|
|
||||||
return self.exc_type, self.exc_value, tb
|
|
||||||
|
|
||||||
|
|
||||||
def make_traceback(exc_info, source_hint=None):
|
|
||||||
"""Creates a processed traceback object from the exc_info."""
|
|
||||||
exc_type, exc_value, tb = exc_info
|
|
||||||
if isinstance(exc_value, TemplateSyntaxError):
|
|
||||||
exc_info = translate_syntax_error(exc_value, source_hint)
|
|
||||||
initial_skip = 0
|
|
||||||
else:
|
|
||||||
initial_skip = 1
|
|
||||||
return translate_exception(exc_info, initial_skip)
|
|
||||||
|
|
||||||
|
|
||||||
def translate_syntax_error(error, source=None):
|
|
||||||
"""Rewrites a syntax error to please traceback systems."""
|
|
||||||
error.source = source
|
|
||||||
error.translated = True
|
|
||||||
exc_info = (error.__class__, error, None)
|
|
||||||
filename = error.filename
|
|
||||||
if filename is None:
|
|
||||||
filename = '<unknown>'
|
|
||||||
return fake_exc_info(exc_info, filename, error.lineno)
|
|
||||||
|
|
||||||
|
|
||||||
def translate_exception(exc_info, initial_skip=0):
|
|
||||||
"""If passed an exc_info it will automatically rewrite the exceptions
|
|
||||||
all the way down to the correct line numbers and frames.
|
|
||||||
"""
|
|
||||||
tb = exc_info[2]
|
|
||||||
frames = []
|
|
||||||
|
|
||||||
# skip some internal frames if wanted
|
|
||||||
for x in xrange(initial_skip):
|
|
||||||
if tb is not None:
|
|
||||||
tb = tb.tb_next
|
|
||||||
initial_tb = tb
|
|
||||||
|
|
||||||
while tb is not None:
|
|
||||||
# skip frames decorated with @internalcode. These are internal
|
|
||||||
# calls we can't avoid and that are useless in template debugging
|
|
||||||
# output.
|
|
||||||
if tb.tb_frame.f_code in internal_code:
|
|
||||||
tb = tb.tb_next
|
|
||||||
continue
|
|
||||||
|
|
||||||
# save a reference to the next frame if we override the current
|
|
||||||
# one with a faked one.
|
|
||||||
next = tb.tb_next
|
|
||||||
|
|
||||||
# fake template exceptions
|
|
||||||
template = tb.tb_frame.f_globals.get('__jinja_template__')
|
|
||||||
if template is not None:
|
|
||||||
lineno = template.get_corresponding_lineno(tb.tb_lineno)
|
|
||||||
tb = fake_exc_info(exc_info[:2] + (tb,), template.filename,
|
|
||||||
lineno)[2]
|
|
||||||
|
|
||||||
frames.append(make_frame_proxy(tb))
|
|
||||||
tb = next
|
|
||||||
|
|
||||||
# if we don't have any exceptions in the frames left, we have to
|
|
||||||
# reraise it unchanged.
|
|
||||||
# XXX: can we backup here? when could this happen?
|
|
||||||
if not frames:
|
|
||||||
raise exc_info[0], exc_info[1], exc_info[2]
|
|
||||||
|
|
||||||
return ProcessedTraceback(exc_info[0], exc_info[1], frames)
|
|
||||||
|
|
||||||
|
|
||||||
def fake_exc_info(exc_info, filename, lineno):
|
|
||||||
"""Helper for `translate_exception`."""
|
|
||||||
exc_type, exc_value, tb = exc_info
|
|
||||||
|
|
||||||
# figure the real context out
|
|
||||||
if tb is not None:
|
|
||||||
real_locals = tb.tb_frame.f_locals.copy()
|
|
||||||
ctx = real_locals.get('context')
|
|
||||||
if ctx:
|
|
||||||
locals = ctx.get_all()
|
|
||||||
else:
|
|
||||||
locals = {}
|
|
||||||
for name, value in real_locals.iteritems():
|
|
||||||
if name.startswith('l_') and value is not missing:
|
|
||||||
locals[name[2:]] = value
|
|
||||||
|
|
||||||
# if there is a local called __jinja_exception__, we get
|
|
||||||
# rid of it to not break the debug functionality.
|
|
||||||
locals.pop('__jinja_exception__', None)
|
|
||||||
else:
|
|
||||||
locals = {}
|
|
||||||
|
|
||||||
# assamble fake globals we need
|
|
||||||
globals = {
|
|
||||||
'__name__': filename,
|
|
||||||
'__file__': filename,
|
|
||||||
'__jinja_exception__': exc_info[:2],
|
|
||||||
|
|
||||||
# we don't want to keep the reference to the template around
|
|
||||||
# to not cause circular dependencies, but we mark it as Jinja
|
|
||||||
# frame for the ProcessedTraceback
|
|
||||||
'__jinja_template__': None
|
|
||||||
}
|
|
||||||
|
|
||||||
# and fake the exception
|
|
||||||
code = compile('\n' * (lineno - 1) + raise_helper, filename, 'exec')
|
|
||||||
|
|
||||||
# if it's possible, change the name of the code. This won't work
|
|
||||||
# on some python environments such as google appengine
|
|
||||||
try:
|
|
||||||
if tb is None:
|
|
||||||
location = 'template'
|
|
||||||
else:
|
|
||||||
function = tb.tb_frame.f_code.co_name
|
|
||||||
if function == 'root':
|
|
||||||
location = 'top-level template code'
|
|
||||||
elif function.startswith('block_'):
|
|
||||||
location = 'block "%s"' % function[6:]
|
|
||||||
else:
|
|
||||||
location = 'template'
|
|
||||||
code = CodeType(0, code.co_nlocals, code.co_stacksize,
|
|
||||||
code.co_flags, code.co_code, code.co_consts,
|
|
||||||
code.co_names, code.co_varnames, filename,
|
|
||||||
location, code.co_firstlineno,
|
|
||||||
code.co_lnotab, (), ())
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# execute the code and catch the new traceback
|
|
||||||
try:
|
|
||||||
exec code in globals, locals
|
|
||||||
except:
|
|
||||||
exc_info = sys.exc_info()
|
|
||||||
new_tb = exc_info[2].tb_next
|
|
||||||
|
|
||||||
# return without this frame
|
|
||||||
return exc_info[:2] + (new_tb,)
|
|
||||||
|
|
||||||
|
|
||||||
def _init_ugly_crap():
|
|
||||||
"""This function implements a few ugly things so that we can patch the
|
|
||||||
traceback objects. The function returned allows resetting `tb_next` on
|
|
||||||
any python traceback object. Do not attempt to use this on non cpython
|
|
||||||
interpreters
|
|
||||||
"""
|
|
||||||
import ctypes
|
|
||||||
from types import TracebackType
|
|
||||||
|
|
||||||
# figure out side of _Py_ssize_t
|
|
||||||
if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'):
|
|
||||||
_Py_ssize_t = ctypes.c_int64
|
|
||||||
else:
|
|
||||||
_Py_ssize_t = ctypes.c_int
|
|
||||||
|
|
||||||
# regular python
|
|
||||||
class _PyObject(ctypes.Structure):
|
|
||||||
pass
|
|
||||||
_PyObject._fields_ = [
|
|
||||||
('ob_refcnt', _Py_ssize_t),
|
|
||||||
('ob_type', ctypes.POINTER(_PyObject))
|
|
||||||
]
|
|
||||||
|
|
||||||
# python with trace
|
|
||||||
if hasattr(sys, 'getobjects'):
|
|
||||||
class _PyObject(ctypes.Structure):
|
|
||||||
pass
|
|
||||||
_PyObject._fields_ = [
|
|
||||||
('_ob_next', ctypes.POINTER(_PyObject)),
|
|
||||||
('_ob_prev', ctypes.POINTER(_PyObject)),
|
|
||||||
('ob_refcnt', _Py_ssize_t),
|
|
||||||
('ob_type', ctypes.POINTER(_PyObject))
|
|
||||||
]
|
|
||||||
|
|
||||||
class _Traceback(_PyObject):
|
|
||||||
pass
|
|
||||||
_Traceback._fields_ = [
|
|
||||||
('tb_next', ctypes.POINTER(_Traceback)),
|
|
||||||
('tb_frame', ctypes.POINTER(_PyObject)),
|
|
||||||
('tb_lasti', ctypes.c_int),
|
|
||||||
('tb_lineno', ctypes.c_int)
|
|
||||||
]
|
|
||||||
|
|
||||||
def tb_set_next(tb, next):
|
|
||||||
"""Set the tb_next attribute of a traceback object."""
|
|
||||||
if not (isinstance(tb, TracebackType) and
|
|
||||||
(next is None or isinstance(next, TracebackType))):
|
|
||||||
raise TypeError('tb_set_next arguments must be traceback objects')
|
|
||||||
obj = _Traceback.from_address(id(tb))
|
|
||||||
if tb.tb_next is not None:
|
|
||||||
old = _Traceback.from_address(id(tb.tb_next))
|
|
||||||
old.ob_refcnt -= 1
|
|
||||||
if next is None:
|
|
||||||
obj.tb_next = ctypes.POINTER(_Traceback)()
|
|
||||||
else:
|
|
||||||
next = _Traceback.from_address(id(next))
|
|
||||||
next.ob_refcnt += 1
|
|
||||||
obj.tb_next = ctypes.pointer(next)
|
|
||||||
|
|
||||||
return tb_set_next
|
|
||||||
|
|
||||||
|
|
||||||
# try to get a tb_set_next implementation if we don't have transparent
|
|
||||||
# proxies.
|
|
||||||
tb_set_next = None
|
|
||||||
if tproxy is None:
|
|
||||||
try:
|
|
||||||
from jinja2._debugsupport import tb_set_next
|
|
||||||
except ImportError:
|
|
||||||
try:
|
|
||||||
tb_set_next = _init_ugly_crap()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
del _init_ugly_crap
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
jinja2.defaults
|
|
||||||
~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Jinja default filters and tags.
|
|
||||||
|
|
||||||
:copyright: (c) 2010 by the Jinja Team.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
from jinja2.utils import generate_lorem_ipsum, Cycler, Joiner
|
|
||||||
|
|
||||||
|
|
||||||
# defaults for the parser / lexer
|
|
||||||
BLOCK_START_STRING = '{%'
|
|
||||||
BLOCK_END_STRING = '%}'
|
|
||||||
VARIABLE_START_STRING = '{{'
|
|
||||||
VARIABLE_END_STRING = '}}'
|
|
||||||
COMMENT_START_STRING = '{#'
|
|
||||||
COMMENT_END_STRING = '#}'
|
|
||||||
LINE_STATEMENT_PREFIX = None
|
|
||||||
LINE_COMMENT_PREFIX = None
|
|
||||||
TRIM_BLOCKS = False
|
|
||||||
NEWLINE_SEQUENCE = '\n'
|
|
||||||
|
|
||||||
|
|
||||||
# default filters, tests and namespace
|
|
||||||
from jinja2.filters import FILTERS as DEFAULT_FILTERS
|
|
||||||
from jinja2.tests import TESTS as DEFAULT_TESTS
|
|
||||||
DEFAULT_NAMESPACE = {
|
|
||||||
'range': xrange,
|
|
||||||
'dict': lambda **kw: kw,
|
|
||||||
'lipsum': generate_lorem_ipsum,
|
|
||||||
'cycler': Cycler,
|
|
||||||
'joiner': Joiner
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# export all constants
|
|
||||||
__all__ = tuple(x for x in locals().keys() if x.isupper())
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -1,143 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
jinja2.exceptions
|
|
||||||
~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Jinja exceptions.
|
|
||||||
|
|
||||||
:copyright: (c) 2010 by the Jinja Team.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class TemplateError(Exception):
|
|
||||||
"""Baseclass for all template errors."""
|
|
||||||
|
|
||||||
def __init__(self, message=None):
|
|
||||||
if message is not None:
|
|
||||||
message = unicode(message).encode('utf-8')
|
|
||||||
Exception.__init__(self, message)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def message(self):
|
|
||||||
if self.args:
|
|
||||||
message = self.args[0]
|
|
||||||
if message is not None:
|
|
||||||
return message.decode('utf-8', 'replace')
|
|
||||||
|
|
||||||
|
|
||||||
class TemplateNotFound(IOError, LookupError, TemplateError):
|
|
||||||
"""Raised if a template does not exist."""
|
|
||||||
|
|
||||||
# looks weird, but removes the warning descriptor that just
|
|
||||||
# bogusly warns us about message being deprecated
|
|
||||||
message = None
|
|
||||||
|
|
||||||
def __init__(self, name, message=None):
|
|
||||||
IOError.__init__(self)
|
|
||||||
if message is None:
|
|
||||||
message = name
|
|
||||||
self.message = message
|
|
||||||
self.name = name
|
|
||||||
self.templates = [name]
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.message.encode('utf-8')
|
|
||||||
|
|
||||||
# unicode goes after __str__ because we configured 2to3 to rename
|
|
||||||
# __unicode__ to __str__. because the 2to3 tree is not designed to
|
|
||||||
# remove nodes from it, we leave the above __str__ around and let
|
|
||||||
# it override at runtime.
|
|
||||||
def __unicode__(self):
|
|
||||||
return self.message
|
|
||||||
|
|
||||||
|
|
||||||
class TemplatesNotFound(TemplateNotFound):
|
|
||||||
"""Like :class:`TemplateNotFound` but raised if multiple templates
|
|
||||||
are selected. This is a subclass of :class:`TemplateNotFound`
|
|
||||||
exception, so just catching the base exception will catch both.
|
|
||||||
|
|
||||||
.. versionadded:: 2.2
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, names=(), message=None):
|
|
||||||
if message is None:
|
|
||||||
message = u'non of the templates given were found: ' + \
|
|
||||||
u', '.join(map(unicode, names))
|
|
||||||
TemplateNotFound.__init__(self, names and names[-1] or None, message)
|
|
||||||
self.templates = list(names)
|
|
||||||
|
|
||||||
|
|
||||||
class TemplateSyntaxError(TemplateError):
|
|
||||||
"""Raised to tell the user that there is a problem with the template."""
|
|
||||||
|
|
||||||
def __init__(self, message, lineno, name=None, filename=None):
|
|
||||||
TemplateError.__init__(self, message)
|
|
||||||
self.lineno = lineno
|
|
||||||
self.name = name
|
|
||||||
self.filename = filename
|
|
||||||
self.source = None
|
|
||||||
|
|
||||||
# this is set to True if the debug.translate_syntax_error
|
|
||||||
# function translated the syntax error into a new traceback
|
|
||||||
self.translated = False
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return unicode(self).encode('utf-8')
|
|
||||||
|
|
||||||
# unicode goes after __str__ because we configured 2to3 to rename
|
|
||||||
# __unicode__ to __str__. because the 2to3 tree is not designed to
|
|
||||||
# remove nodes from it, we leave the above __str__ around and let
|
|
||||||
# it override at runtime.
|
|
||||||
def __unicode__(self):
|
|
||||||
# for translated errors we only return the message
|
|
||||||
if self.translated:
|
|
||||||
return self.message
|
|
||||||
|
|
||||||
# otherwise attach some stuff
|
|
||||||
location = 'line %d' % self.lineno
|
|
||||||
name = self.filename or self.name
|
|
||||||
if name:
|
|
||||||
location = 'File "%s", %s' % (name, location)
|
|
||||||
lines = [self.message, ' ' + location]
|
|
||||||
|
|
||||||
# if the source is set, add the line to the output
|
|
||||||
if self.source is not None:
|
|
||||||
try:
|
|
||||||
line = self.source.splitlines()[self.lineno - 1]
|
|
||||||
except IndexError:
|
|
||||||
line = None
|
|
||||||
if line:
|
|
||||||
lines.append(' ' + line.strip())
|
|
||||||
|
|
||||||
return u'\n'.join(lines)
|
|
||||||
|
|
||||||
|
|
||||||
class TemplateAssertionError(TemplateSyntaxError):
|
|
||||||
"""Like a template syntax error, but covers cases where something in the
|
|
||||||
template caused an error at compile time that wasn't necessarily caused
|
|
||||||
by a syntax error. However it's a direct subclass of
|
|
||||||
:exc:`TemplateSyntaxError` and has the same attributes.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class TemplateRuntimeError(TemplateError):
|
|
||||||
"""A generic runtime error in the template engine. Under some situations
|
|
||||||
Jinja may raise this exception.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class UndefinedError(TemplateRuntimeError):
|
|
||||||
"""Raised if a template tries to operate on :class:`Undefined`."""
|
|
||||||
|
|
||||||
|
|
||||||
class SecurityError(TemplateRuntimeError):
|
|
||||||
"""Raised if a template tries to do something insecure if the
|
|
||||||
sandbox is enabled.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class FilterArgumentError(TemplateRuntimeError):
|
|
||||||
"""This error is raised if a filter was called with inappropriate
|
|
||||||
arguments
|
|
||||||
"""
|
|
||||||
@@ -1,612 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
jinja2.ext
|
|
||||||
~~~~~~~~~~
|
|
||||||
|
|
||||||
Jinja extensions allow to add custom tags similar to the way django custom
|
|
||||||
tags work. By default two example extensions exist: an i18n and a cache
|
|
||||||
extension.
|
|
||||||
|
|
||||||
:copyright: (c) 2010 by the Jinja Team.
|
|
||||||
:license: BSD.
|
|
||||||
"""
|
|
||||||
from collections import deque
|
|
||||||
from jinja2 import nodes
|
|
||||||
from jinja2.defaults import *
|
|
||||||
from jinja2.environment import Environment
|
|
||||||
from jinja2.runtime import Undefined, concat
|
|
||||||
from jinja2.exceptions import TemplateAssertionError, TemplateSyntaxError
|
|
||||||
from jinja2.utils import contextfunction, import_string, Markup, next
|
|
||||||
|
|
||||||
|
|
||||||
# the only real useful gettext functions for a Jinja template. Note
|
|
||||||
# that ugettext must be assigned to gettext as Jinja doesn't support
|
|
||||||
# non unicode strings.
|
|
||||||
GETTEXT_FUNCTIONS = ('_', 'gettext', 'ngettext')
|
|
||||||
|
|
||||||
|
|
||||||
class ExtensionRegistry(type):
|
|
||||||
"""Gives the extension an unique identifier."""
|
|
||||||
|
|
||||||
def __new__(cls, name, bases, d):
|
|
||||||
rv = type.__new__(cls, name, bases, d)
|
|
||||||
rv.identifier = rv.__module__ + '.' + rv.__name__
|
|
||||||
return rv
|
|
||||||
|
|
||||||
|
|
||||||
class Extension(object):
|
|
||||||
"""Extensions can be used to add extra functionality to the Jinja template
|
|
||||||
system at the parser level. Custom extensions are bound to an environment
|
|
||||||
but may not store environment specific data on `self`. The reason for
|
|
||||||
this is that an extension can be bound to another environment (for
|
|
||||||
overlays) by creating a copy and reassigning the `environment` attribute.
|
|
||||||
|
|
||||||
As extensions are created by the environment they cannot accept any
|
|
||||||
arguments for configuration. One may want to work around that by using
|
|
||||||
a factory function, but that is not possible as extensions are identified
|
|
||||||
by their import name. The correct way to configure the extension is
|
|
||||||
storing the configuration values on the environment. Because this way the
|
|
||||||
environment ends up acting as central configuration storage the
|
|
||||||
attributes may clash which is why extensions have to ensure that the names
|
|
||||||
they choose for configuration are not too generic. ``prefix`` for example
|
|
||||||
is a terrible name, ``fragment_cache_prefix`` on the other hand is a good
|
|
||||||
name as includes the name of the extension (fragment cache).
|
|
||||||
"""
|
|
||||||
__metaclass__ = ExtensionRegistry
|
|
||||||
|
|
||||||
#: if this extension parses this is the list of tags it's listening to.
|
|
||||||
tags = set()
|
|
||||||
|
|
||||||
#: the priority of that extension. This is especially useful for
|
|
||||||
#: extensions that preprocess values. A lower value means higher
|
|
||||||
#: priority.
|
|
||||||
#:
|
|
||||||
#: .. versionadded:: 2.4
|
|
||||||
priority = 100
|
|
||||||
|
|
||||||
def __init__(self, environment):
|
|
||||||
self.environment = environment
|
|
||||||
|
|
||||||
def bind(self, environment):
|
|
||||||
"""Create a copy of this extension bound to another environment."""
|
|
||||||
rv = object.__new__(self.__class__)
|
|
||||||
rv.__dict__.update(self.__dict__)
|
|
||||||
rv.environment = environment
|
|
||||||
return rv
|
|
||||||
|
|
||||||
def preprocess(self, source, name, filename=None):
|
|
||||||
"""This method is called before the actual lexing and can be used to
|
|
||||||
preprocess the source. The `filename` is optional. The return value
|
|
||||||
must be the preprocessed source.
|
|
||||||
"""
|
|
||||||
return source
|
|
||||||
|
|
||||||
def filter_stream(self, stream):
|
|
||||||
"""It's passed a :class:`~jinja2.lexer.TokenStream` that can be used
|
|
||||||
to filter tokens returned. This method has to return an iterable of
|
|
||||||
:class:`~jinja2.lexer.Token`\s, but it doesn't have to return a
|
|
||||||
:class:`~jinja2.lexer.TokenStream`.
|
|
||||||
|
|
||||||
In the `ext` folder of the Jinja2 source distribution there is a file
|
|
||||||
called `inlinegettext.py` which implements a filter that utilizes this
|
|
||||||
method.
|
|
||||||
"""
|
|
||||||
return stream
|
|
||||||
|
|
||||||
def parse(self, parser):
|
|
||||||
"""If any of the :attr:`tags` matched this method is called with the
|
|
||||||
parser as first argument. The token the parser stream is pointing at
|
|
||||||
is the name token that matched. This method has to return one or a
|
|
||||||
list of multiple nodes.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def attr(self, name, lineno=None):
|
|
||||||
"""Return an attribute node for the current extension. This is useful
|
|
||||||
to pass constants on extensions to generated template code.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
self.attr('_my_attribute', lineno=lineno)
|
|
||||||
"""
|
|
||||||
return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno)
|
|
||||||
|
|
||||||
def call_method(self, name, args=None, kwargs=None, dyn_args=None,
|
|
||||||
dyn_kwargs=None, lineno=None):
|
|
||||||
"""Call a method of the extension. This is a shortcut for
|
|
||||||
:meth:`attr` + :class:`jinja2.nodes.Call`.
|
|
||||||
"""
|
|
||||||
if args is None:
|
|
||||||
args = []
|
|
||||||
if kwargs is None:
|
|
||||||
kwargs = []
|
|
||||||
return nodes.Call(self.attr(name, lineno=lineno), args, kwargs,
|
|
||||||
dyn_args, dyn_kwargs, lineno=lineno)
|
|
||||||
|
|
||||||
|
|
||||||
@contextfunction
|
|
||||||
def _gettext_alias(__context, *args, **kwargs):
|
|
||||||
return __context.call(__context.resolve('gettext'), *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def _make_new_gettext(func):
|
|
||||||
@contextfunction
|
|
||||||
def gettext(__context, __string, **variables):
|
|
||||||
rv = __context.call(func, __string)
|
|
||||||
if __context.eval_ctx.autoescape:
|
|
||||||
rv = Markup(rv)
|
|
||||||
return rv % variables
|
|
||||||
return gettext
|
|
||||||
|
|
||||||
|
|
||||||
def _make_new_ngettext(func):
|
|
||||||
@contextfunction
|
|
||||||
def ngettext(__context, __singular, __plural, __num, **variables):
|
|
||||||
variables.setdefault('num', __num)
|
|
||||||
rv = __context.call(func, __singular, __plural, __num)
|
|
||||||
if __context.eval_ctx.autoescape:
|
|
||||||
rv = Markup(rv)
|
|
||||||
return rv % variables
|
|
||||||
return ngettext
|
|
||||||
|
|
||||||
|
|
||||||
class InternationalizationExtension(Extension):
|
|
||||||
"""This extension adds gettext support to Jinja2."""
|
|
||||||
tags = set(['trans'])
|
|
||||||
|
|
||||||
# TODO: the i18n extension is currently reevaluating values in a few
|
|
||||||
# situations. Take this example:
|
|
||||||
# {% trans count=something() %}{{ count }} foo{% pluralize
|
|
||||||
# %}{{ count }} fooss{% endtrans %}
|
|
||||||
# something is called twice here. One time for the gettext value and
|
|
||||||
# the other time for the n-parameter of the ngettext function.
|
|
||||||
|
|
||||||
def __init__(self, environment):
|
|
||||||
Extension.__init__(self, environment)
|
|
||||||
environment.globals['_'] = _gettext_alias
|
|
||||||
environment.extend(
|
|
||||||
install_gettext_translations=self._install,
|
|
||||||
install_null_translations=self._install_null,
|
|
||||||
install_gettext_callables=self._install_callables,
|
|
||||||
uninstall_gettext_translations=self._uninstall,
|
|
||||||
extract_translations=self._extract,
|
|
||||||
newstyle_gettext=False
|
|
||||||
)
|
|
||||||
|
|
||||||
def _install(self, translations, newstyle=None):
|
|
||||||
gettext = getattr(translations, 'ugettext', None)
|
|
||||||
if gettext is None:
|
|
||||||
gettext = translations.gettext
|
|
||||||
ngettext = getattr(translations, 'ungettext', None)
|
|
||||||
if ngettext is None:
|
|
||||||
ngettext = translations.ngettext
|
|
||||||
self._install_callables(gettext, ngettext, newstyle)
|
|
||||||
|
|
||||||
def _install_null(self, newstyle=None):
|
|
||||||
self._install_callables(
|
|
||||||
lambda x: x,
|
|
||||||
lambda s, p, n: (n != 1 and (p,) or (s,))[0],
|
|
||||||
newstyle
|
|
||||||
)
|
|
||||||
|
|
||||||
def _install_callables(self, gettext, ngettext, newstyle=None):
|
|
||||||
if newstyle is not None:
|
|
||||||
self.environment.newstyle_gettext = newstyle
|
|
||||||
if self.environment.newstyle_gettext:
|
|
||||||
gettext = _make_new_gettext(gettext)
|
|
||||||
ngettext = _make_new_ngettext(ngettext)
|
|
||||||
self.environment.globals.update(
|
|
||||||
gettext=gettext,
|
|
||||||
ngettext=ngettext
|
|
||||||
)
|
|
||||||
|
|
||||||
def _uninstall(self, translations):
|
|
||||||
for key in 'gettext', 'ngettext':
|
|
||||||
self.environment.globals.pop(key, None)
|
|
||||||
|
|
||||||
def _extract(self, source, gettext_functions=GETTEXT_FUNCTIONS):
|
|
||||||
if isinstance(source, basestring):
|
|
||||||
source = self.environment.parse(source)
|
|
||||||
return extract_from_ast(source, gettext_functions)
|
|
||||||
|
|
||||||
def parse(self, parser):
|
|
||||||
"""Parse a translatable tag."""
|
|
||||||
lineno = next(parser.stream).lineno
|
|
||||||
num_called_num = False
|
|
||||||
|
|
||||||
# find all the variables referenced. Additionally a variable can be
|
|
||||||
# defined in the body of the trans block too, but this is checked at
|
|
||||||
# a later state.
|
|
||||||
plural_expr = None
|
|
||||||
variables = {}
|
|
||||||
while parser.stream.current.type != 'block_end':
|
|
||||||
if variables:
|
|
||||||
parser.stream.expect('comma')
|
|
||||||
|
|
||||||
# skip colon for python compatibility
|
|
||||||
if parser.stream.skip_if('colon'):
|
|
||||||
break
|
|
||||||
|
|
||||||
name = parser.stream.expect('name')
|
|
||||||
if name.value in variables:
|
|
||||||
parser.fail('translatable variable %r defined twice.' %
|
|
||||||
name.value, name.lineno,
|
|
||||||
exc=TemplateAssertionError)
|
|
||||||
|
|
||||||
# expressions
|
|
||||||
if parser.stream.current.type == 'assign':
|
|
||||||
next(parser.stream)
|
|
||||||
variables[name.value] = var = parser.parse_expression()
|
|
||||||
else:
|
|
||||||
variables[name.value] = var = nodes.Name(name.value, 'load')
|
|
||||||
|
|
||||||
if plural_expr is None:
|
|
||||||
plural_expr = var
|
|
||||||
num_called_num = name.value == 'num'
|
|
||||||
|
|
||||||
parser.stream.expect('block_end')
|
|
||||||
|
|
||||||
plural = plural_names = None
|
|
||||||
have_plural = False
|
|
||||||
referenced = set()
|
|
||||||
|
|
||||||
# now parse until endtrans or pluralize
|
|
||||||
singular_names, singular = self._parse_block(parser, True)
|
|
||||||
if singular_names:
|
|
||||||
referenced.update(singular_names)
|
|
||||||
if plural_expr is None:
|
|
||||||
plural_expr = nodes.Name(singular_names[0], 'load')
|
|
||||||
num_called_num = singular_names[0] == 'num'
|
|
||||||
|
|
||||||
# if we have a pluralize block, we parse that too
|
|
||||||
if parser.stream.current.test('name:pluralize'):
|
|
||||||
have_plural = True
|
|
||||||
next(parser.stream)
|
|
||||||
if parser.stream.current.type != 'block_end':
|
|
||||||
name = parser.stream.expect('name')
|
|
||||||
if name.value not in variables:
|
|
||||||
parser.fail('unknown variable %r for pluralization' %
|
|
||||||
name.value, name.lineno,
|
|
||||||
exc=TemplateAssertionError)
|
|
||||||
plural_expr = variables[name.value]
|
|
||||||
num_called_num = name.value == 'num'
|
|
||||||
parser.stream.expect('block_end')
|
|
||||||
plural_names, plural = self._parse_block(parser, False)
|
|
||||||
next(parser.stream)
|
|
||||||
referenced.update(plural_names)
|
|
||||||
else:
|
|
||||||
next(parser.stream)
|
|
||||||
|
|
||||||
# register free names as simple name expressions
|
|
||||||
for var in referenced:
|
|
||||||
if var not in variables:
|
|
||||||
variables[var] = nodes.Name(var, 'load')
|
|
||||||
|
|
||||||
if not have_plural:
|
|
||||||
plural_expr = None
|
|
||||||
elif plural_expr is None:
|
|
||||||
parser.fail('pluralize without variables', lineno)
|
|
||||||
|
|
||||||
node = self._make_node(singular, plural, variables, plural_expr,
|
|
||||||
bool(referenced),
|
|
||||||
num_called_num and have_plural)
|
|
||||||
node.set_lineno(lineno)
|
|
||||||
return node
|
|
||||||
|
|
||||||
def _parse_block(self, parser, allow_pluralize):
|
|
||||||
"""Parse until the next block tag with a given name."""
|
|
||||||
referenced = []
|
|
||||||
buf = []
|
|
||||||
while 1:
|
|
||||||
if parser.stream.current.type == 'data':
|
|
||||||
buf.append(parser.stream.current.value.replace('%', '%%'))
|
|
||||||
next(parser.stream)
|
|
||||||
elif parser.stream.current.type == 'variable_begin':
|
|
||||||
next(parser.stream)
|
|
||||||
name = parser.stream.expect('name').value
|
|
||||||
referenced.append(name)
|
|
||||||
buf.append('%%(%s)s' % name)
|
|
||||||
parser.stream.expect('variable_end')
|
|
||||||
elif parser.stream.current.type == 'block_begin':
|
|
||||||
next(parser.stream)
|
|
||||||
if parser.stream.current.test('name:endtrans'):
|
|
||||||
break
|
|
||||||
elif parser.stream.current.test('name:pluralize'):
|
|
||||||
if allow_pluralize:
|
|
||||||
break
|
|
||||||
parser.fail('a translatable section can have only one '
|
|
||||||
'pluralize section')
|
|
||||||
parser.fail('control structures in translatable sections are '
|
|
||||||
'not allowed')
|
|
||||||
elif parser.stream.eos:
|
|
||||||
parser.fail('unclosed translation block')
|
|
||||||
else:
|
|
||||||
assert False, 'internal parser error'
|
|
||||||
|
|
||||||
return referenced, concat(buf)
|
|
||||||
|
|
||||||
def _make_node(self, singular, plural, variables, plural_expr,
|
|
||||||
vars_referenced, num_called_num):
|
|
||||||
"""Generates a useful node from the data provided."""
|
|
||||||
# no variables referenced? no need to escape for old style
|
|
||||||
# gettext invocations only if there are vars.
|
|
||||||
if not vars_referenced and not self.environment.newstyle_gettext:
|
|
||||||
singular = singular.replace('%%', '%')
|
|
||||||
if plural:
|
|
||||||
plural = plural.replace('%%', '%')
|
|
||||||
|
|
||||||
# singular only:
|
|
||||||
if plural_expr is None:
|
|
||||||
gettext = nodes.Name('gettext', 'load')
|
|
||||||
node = nodes.Call(gettext, [nodes.Const(singular)],
|
|
||||||
[], None, None)
|
|
||||||
|
|
||||||
# singular and plural
|
|
||||||
else:
|
|
||||||
ngettext = nodes.Name('ngettext', 'load')
|
|
||||||
node = nodes.Call(ngettext, [
|
|
||||||
nodes.Const(singular),
|
|
||||||
nodes.Const(plural),
|
|
||||||
plural_expr
|
|
||||||
], [], None, None)
|
|
||||||
|
|
||||||
# in case newstyle gettext is used, the method is powerful
|
|
||||||
# enough to handle the variable expansion and autoescape
|
|
||||||
# handling itself
|
|
||||||
if self.environment.newstyle_gettext:
|
|
||||||
for key, value in variables.iteritems():
|
|
||||||
# the function adds that later anyways in case num was
|
|
||||||
# called num, so just skip it.
|
|
||||||
if num_called_num and key == 'num':
|
|
||||||
continue
|
|
||||||
node.kwargs.append(nodes.Keyword(key, value))
|
|
||||||
|
|
||||||
# otherwise do that here
|
|
||||||
else:
|
|
||||||
# mark the return value as safe if we are in an
|
|
||||||
# environment with autoescaping turned on
|
|
||||||
node = nodes.MarkSafeIfAutoescape(node)
|
|
||||||
if variables:
|
|
||||||
node = nodes.Mod(node, nodes.Dict([
|
|
||||||
nodes.Pair(nodes.Const(key), value)
|
|
||||||
for key, value in variables.items()
|
|
||||||
]))
|
|
||||||
return nodes.Output([node])
|
|
||||||
|
|
||||||
|
|
||||||
class ExprStmtExtension(Extension):
|
|
||||||
"""Adds a `do` tag to Jinja2 that works like the print statement just
|
|
||||||
that it doesn't print the return value.
|
|
||||||
"""
|
|
||||||
tags = set(['do'])
|
|
||||||
|
|
||||||
def parse(self, parser):
|
|
||||||
node = nodes.ExprStmt(lineno=next(parser.stream).lineno)
|
|
||||||
node.node = parser.parse_tuple()
|
|
||||||
return node
|
|
||||||
|
|
||||||
|
|
||||||
class LoopControlExtension(Extension):
|
|
||||||
"""Adds break and continue to the template engine."""
|
|
||||||
tags = set(['break', 'continue'])
|
|
||||||
|
|
||||||
def parse(self, parser):
|
|
||||||
token = next(parser.stream)
|
|
||||||
if token.value == 'break':
|
|
||||||
return nodes.Break(lineno=token.lineno)
|
|
||||||
return nodes.Continue(lineno=token.lineno)
|
|
||||||
|
|
||||||
|
|
||||||
class WithExtension(Extension):
|
|
||||||
"""Adds support for a django-like with block."""
|
|
||||||
tags = set(['with'])
|
|
||||||
|
|
||||||
def parse(self, parser):
|
|
||||||
node = nodes.Scope(lineno=next(parser.stream).lineno)
|
|
||||||
assignments = []
|
|
||||||
while parser.stream.current.type != 'block_end':
|
|
||||||
lineno = parser.stream.current.lineno
|
|
||||||
if assignments:
|
|
||||||
parser.stream.expect('comma')
|
|
||||||
target = parser.parse_assign_target()
|
|
||||||
parser.stream.expect('assign')
|
|
||||||
expr = parser.parse_expression()
|
|
||||||
assignments.append(nodes.Assign(target, expr, lineno=lineno))
|
|
||||||
node.body = assignments + \
|
|
||||||
list(parser.parse_statements(('name:endwith',),
|
|
||||||
drop_needle=True))
|
|
||||||
return node
|
|
||||||
|
|
||||||
|
|
||||||
class AutoEscapeExtension(Extension):
|
|
||||||
"""Changes auto escape rules for a scope."""
|
|
||||||
tags = set(['autoescape'])
|
|
||||||
|
|
||||||
def parse(self, parser):
|
|
||||||
node = nodes.ScopedEvalContextModifier(lineno=next(parser.stream).lineno)
|
|
||||||
node.options = [
|
|
||||||
nodes.Keyword('autoescape', parser.parse_expression())
|
|
||||||
]
|
|
||||||
node.body = parser.parse_statements(('name:endautoescape',),
|
|
||||||
drop_needle=True)
|
|
||||||
return nodes.Scope([node])
|
|
||||||
|
|
||||||
|
|
||||||
def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS,
|
|
||||||
babel_style=True):
|
|
||||||
"""Extract localizable strings from the given template node. Per
|
|
||||||
default this function returns matches in babel style that means non string
|
|
||||||
parameters as well as keyword arguments are returned as `None`. This
|
|
||||||
allows Babel to figure out what you really meant if you are using
|
|
||||||
gettext functions that allow keyword arguments for placeholder expansion.
|
|
||||||
If you don't want that behavior set the `babel_style` parameter to `False`
|
|
||||||
which causes only strings to be returned and parameters are always stored
|
|
||||||
in tuples. As a consequence invalid gettext calls (calls without a single
|
|
||||||
string parameter or string parameters after non-string parameters) are
|
|
||||||
skipped.
|
|
||||||
|
|
||||||
This example explains the behavior:
|
|
||||||
|
|
||||||
>>> from jinja2 import Environment
|
|
||||||
>>> env = Environment()
|
|
||||||
>>> node = env.parse('{{ (_("foo"), _(), ngettext("foo", "bar", 42)) }}')
|
|
||||||
>>> list(extract_from_ast(node))
|
|
||||||
[(1, '_', 'foo'), (1, '_', ()), (1, 'ngettext', ('foo', 'bar', None))]
|
|
||||||
>>> list(extract_from_ast(node, babel_style=False))
|
|
||||||
[(1, '_', ('foo',)), (1, 'ngettext', ('foo', 'bar'))]
|
|
||||||
|
|
||||||
For every string found this function yields a ``(lineno, function,
|
|
||||||
message)`` tuple, where:
|
|
||||||
|
|
||||||
* ``lineno`` is the number of the line on which the string was found,
|
|
||||||
* ``function`` is the name of the ``gettext`` function used (if the
|
|
||||||
string was extracted from embedded Python code), and
|
|
||||||
* ``message`` is the string itself (a ``unicode`` object, or a tuple
|
|
||||||
of ``unicode`` objects for functions with multiple string arguments).
|
|
||||||
|
|
||||||
This extraction function operates on the AST and is because of that unable
|
|
||||||
to extract any comments. For comment support you have to use the babel
|
|
||||||
extraction interface or extract comments yourself.
|
|
||||||
"""
|
|
||||||
for node in node.find_all(nodes.Call):
|
|
||||||
if not isinstance(node.node, nodes.Name) or \
|
|
||||||
node.node.name not in gettext_functions:
|
|
||||||
continue
|
|
||||||
|
|
||||||
strings = []
|
|
||||||
for arg in node.args:
|
|
||||||
if isinstance(arg, nodes.Const) and \
|
|
||||||
isinstance(arg.value, basestring):
|
|
||||||
strings.append(arg.value)
|
|
||||||
else:
|
|
||||||
strings.append(None)
|
|
||||||
|
|
||||||
for arg in node.kwargs:
|
|
||||||
strings.append(None)
|
|
||||||
if node.dyn_args is not None:
|
|
||||||
strings.append(None)
|
|
||||||
if node.dyn_kwargs is not None:
|
|
||||||
strings.append(None)
|
|
||||||
|
|
||||||
if not babel_style:
|
|
||||||
strings = tuple(x for x in strings if x is not None)
|
|
||||||
if not strings:
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
if len(strings) == 1:
|
|
||||||
strings = strings[0]
|
|
||||||
else:
|
|
||||||
strings = tuple(strings)
|
|
||||||
yield node.lineno, node.node.name, strings
|
|
||||||
|
|
||||||
|
|
||||||
class _CommentFinder(object):
|
|
||||||
"""Helper class to find comments in a token stream. Can only
|
|
||||||
find comments for gettext calls forwards. Once the comment
|
|
||||||
from line 4 is found, a comment for line 1 will not return a
|
|
||||||
usable value.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, tokens, comment_tags):
|
|
||||||
self.tokens = tokens
|
|
||||||
self.comment_tags = comment_tags
|
|
||||||
self.offset = 0
|
|
||||||
self.last_lineno = 0
|
|
||||||
|
|
||||||
def find_backwards(self, offset):
|
|
||||||
try:
|
|
||||||
for _, token_type, token_value in \
|
|
||||||
reversed(self.tokens[self.offset:offset]):
|
|
||||||
if token_type in ('comment', 'linecomment'):
|
|
||||||
try:
|
|
||||||
prefix, comment = token_value.split(None, 1)
|
|
||||||
except ValueError:
|
|
||||||
continue
|
|
||||||
if prefix in self.comment_tags:
|
|
||||||
return [comment.rstrip()]
|
|
||||||
return []
|
|
||||||
finally:
|
|
||||||
self.offset = offset
|
|
||||||
|
|
||||||
def find_comments(self, lineno):
|
|
||||||
if not self.comment_tags or self.last_lineno > lineno:
|
|
||||||
return []
|
|
||||||
for idx, (token_lineno, _, _) in enumerate(self.tokens[self.offset:]):
|
|
||||||
if token_lineno > lineno:
|
|
||||||
return self.find_backwards(self.offset + idx)
|
|
||||||
return self.find_backwards(len(self.tokens))
|
|
||||||
|
|
||||||
|
|
||||||
def babel_extract(fileobj, keywords, comment_tags, options):
|
|
||||||
"""Babel extraction method for Jinja templates.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.3
|
|
||||||
Basic support for translation comments was added. If `comment_tags`
|
|
||||||
is now set to a list of keywords for extraction, the extractor will
|
|
||||||
try to find the best preceeding comment that begins with one of the
|
|
||||||
keywords. For best results, make sure to not have more than one
|
|
||||||
gettext call in one line of code and the matching comment in the
|
|
||||||
same line or the line before.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.5.1
|
|
||||||
The `newstyle_gettext` flag can be set to `True` to enable newstyle
|
|
||||||
gettext calls.
|
|
||||||
|
|
||||||
:param fileobj: the file-like object the messages should be extracted from
|
|
||||||
:param keywords: a list of keywords (i.e. function names) that should be
|
|
||||||
recognized as translation functions
|
|
||||||
:param comment_tags: a list of translator tags to search for and include
|
|
||||||
in the results.
|
|
||||||
:param options: a dictionary of additional options (optional)
|
|
||||||
:return: an iterator over ``(lineno, funcname, message, comments)`` tuples.
|
|
||||||
(comments will be empty currently)
|
|
||||||
"""
|
|
||||||
extensions = set()
|
|
||||||
for extension in options.get('extensions', '').split(','):
|
|
||||||
extension = extension.strip()
|
|
||||||
if not extension:
|
|
||||||
continue
|
|
||||||
extensions.add(import_string(extension))
|
|
||||||
if InternationalizationExtension not in extensions:
|
|
||||||
extensions.add(InternationalizationExtension)
|
|
||||||
|
|
||||||
def getbool(options, key, default=False):
|
|
||||||
options.get(key, str(default)).lower() in ('1', 'on', 'yes', 'true')
|
|
||||||
|
|
||||||
environment = Environment(
|
|
||||||
options.get('block_start_string', BLOCK_START_STRING),
|
|
||||||
options.get('block_end_string', BLOCK_END_STRING),
|
|
||||||
options.get('variable_start_string', VARIABLE_START_STRING),
|
|
||||||
options.get('variable_end_string', VARIABLE_END_STRING),
|
|
||||||
options.get('comment_start_string', COMMENT_START_STRING),
|
|
||||||
options.get('comment_end_string', COMMENT_END_STRING),
|
|
||||||
options.get('line_statement_prefix') or LINE_STATEMENT_PREFIX,
|
|
||||||
options.get('line_comment_prefix') or LINE_COMMENT_PREFIX,
|
|
||||||
getbool(options, 'trim_blocks', TRIM_BLOCKS),
|
|
||||||
NEWLINE_SEQUENCE, frozenset(extensions),
|
|
||||||
cache_size=0,
|
|
||||||
auto_reload=False
|
|
||||||
)
|
|
||||||
|
|
||||||
if getbool(options, 'newstyle_gettext'):
|
|
||||||
environment.newstyle_gettext = True
|
|
||||||
|
|
||||||
source = fileobj.read().decode(options.get('encoding', 'utf-8'))
|
|
||||||
try:
|
|
||||||
node = environment.parse(source)
|
|
||||||
tokens = list(environment.lex(environment.preprocess(source)))
|
|
||||||
except TemplateSyntaxError, e:
|
|
||||||
# skip templates with syntax errors
|
|
||||||
return
|
|
||||||
|
|
||||||
finder = _CommentFinder(tokens, comment_tags)
|
|
||||||
for lineno, func, message in extract_from_ast(node, keywords):
|
|
||||||
yield lineno, func, message, finder.find_comments(lineno)
|
|
||||||
|
|
||||||
|
|
||||||
#: nicer import names
|
|
||||||
i18n = InternationalizationExtension
|
|
||||||
do = ExprStmtExtension
|
|
||||||
loopcontrols = LoopControlExtension
|
|
||||||
with_ = WithExtension
|
|
||||||
autoescape = AutoEscapeExtension
|
|
||||||
@@ -1,801 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
jinja2.filters
|
|
||||||
~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Bundled jinja filters.
|
|
||||||
|
|
||||||
:copyright: (c) 2010 by the Jinja Team.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
import re
|
|
||||||
import math
|
|
||||||
from random import choice
|
|
||||||
from operator import itemgetter
|
|
||||||
from itertools import imap, groupby
|
|
||||||
from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode
|
|
||||||
from jinja2.runtime import Undefined
|
|
||||||
from jinja2.exceptions import FilterArgumentError, SecurityError
|
|
||||||
|
|
||||||
|
|
||||||
_word_re = re.compile(r'\w+(?u)')
|
|
||||||
|
|
||||||
|
|
||||||
def contextfilter(f):
|
|
||||||
"""Decorator for marking context dependent filters. The current
|
|
||||||
:class:`Context` will be passed as first argument.
|
|
||||||
"""
|
|
||||||
f.contextfilter = True
|
|
||||||
return f
|
|
||||||
|
|
||||||
|
|
||||||
def evalcontextfilter(f):
|
|
||||||
"""Decorator for marking eval-context dependent filters. An eval
|
|
||||||
context object is passed as first argument. For more information
|
|
||||||
about the eval context, see :ref:`eval-context`.
|
|
||||||
|
|
||||||
.. versionadded:: 2.4
|
|
||||||
"""
|
|
||||||
f.evalcontextfilter = True
|
|
||||||
return f
|
|
||||||
|
|
||||||
|
|
||||||
def environmentfilter(f):
|
|
||||||
"""Decorator for marking evironment dependent filters. The current
|
|
||||||
:class:`Environment` is passed to the filter as first argument.
|
|
||||||
"""
|
|
||||||
f.environmentfilter = True
|
|
||||||
return f
|
|
||||||
|
|
||||||
|
|
||||||
def make_attrgetter(environment, attribute):
|
|
||||||
"""Returns a callable that looks up the given attribute from a
|
|
||||||
passed object with the rules of the environment. Dots are allowed
|
|
||||||
to access attributes of attributes.
|
|
||||||
"""
|
|
||||||
if not isinstance(attribute, basestring) or '.' not in attribute:
|
|
||||||
return lambda x: environment.getitem(x, attribute)
|
|
||||||
attribute = attribute.split('.')
|
|
||||||
def attrgetter(item):
|
|
||||||
for part in attribute:
|
|
||||||
item = environment.getitem(item, part)
|
|
||||||
return item
|
|
||||||
return attrgetter
|
|
||||||
|
|
||||||
|
|
||||||
def do_forceescape(value):
|
|
||||||
"""Enforce HTML escaping. This will probably double escape variables."""
|
|
||||||
if hasattr(value, '__html__'):
|
|
||||||
value = value.__html__()
|
|
||||||
return escape(unicode(value))
|
|
||||||
|
|
||||||
|
|
||||||
@evalcontextfilter
|
|
||||||
def do_replace(eval_ctx, s, old, new, count=None):
|
|
||||||
"""Return a copy of the value with all occurrences of a substring
|
|
||||||
replaced with a new one. The first argument is the substring
|
|
||||||
that should be replaced, the second is the replacement string.
|
|
||||||
If the optional third argument ``count`` is given, only the first
|
|
||||||
``count`` occurrences are replaced:
|
|
||||||
|
|
||||||
.. sourcecode:: jinja
|
|
||||||
|
|
||||||
{{ "Hello World"|replace("Hello", "Goodbye") }}
|
|
||||||
-> Goodbye World
|
|
||||||
|
|
||||||
{{ "aaaaargh"|replace("a", "d'oh, ", 2) }}
|
|
||||||
-> d'oh, d'oh, aaargh
|
|
||||||
"""
|
|
||||||
if count is None:
|
|
||||||
count = -1
|
|
||||||
if not eval_ctx.autoescape:
|
|
||||||
return unicode(s).replace(unicode(old), unicode(new), count)
|
|
||||||
if hasattr(old, '__html__') or hasattr(new, '__html__') and \
|
|
||||||
not hasattr(s, '__html__'):
|
|
||||||
s = escape(s)
|
|
||||||
else:
|
|
||||||
s = soft_unicode(s)
|
|
||||||
return s.replace(soft_unicode(old), soft_unicode(new), count)
|
|
||||||
|
|
||||||
|
|
||||||
def do_upper(s):
|
|
||||||
"""Convert a value to uppercase."""
|
|
||||||
return soft_unicode(s).upper()
|
|
||||||
|
|
||||||
|
|
||||||
def do_lower(s):
|
|
||||||
"""Convert a value to lowercase."""
|
|
||||||
return soft_unicode(s).lower()
|
|
||||||
|
|
||||||
|
|
||||||
@evalcontextfilter
|
|
||||||
def do_xmlattr(_eval_ctx, d, autospace=True):
|
|
||||||
"""Create an SGML/XML attribute string based on the items in a dict.
|
|
||||||
All values that are neither `none` nor `undefined` are automatically
|
|
||||||
escaped:
|
|
||||||
|
|
||||||
.. sourcecode:: html+jinja
|
|
||||||
|
|
||||||
<ul{{ {'class': 'my_list', 'missing': none,
|
|
||||||
'id': 'list-%d'|format(variable)}|xmlattr }}>
|
|
||||||
...
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Results in something like this:
|
|
||||||
|
|
||||||
.. sourcecode:: html
|
|
||||||
|
|
||||||
<ul class="my_list" id="list-42">
|
|
||||||
...
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
As you can see it automatically prepends a space in front of the item
|
|
||||||
if the filter returned something unless the second parameter is false.
|
|
||||||
"""
|
|
||||||
rv = u' '.join(
|
|
||||||
u'%s="%s"' % (escape(key), escape(value))
|
|
||||||
for key, value in d.iteritems()
|
|
||||||
if value is not None and not isinstance(value, Undefined)
|
|
||||||
)
|
|
||||||
if autospace and rv:
|
|
||||||
rv = u' ' + rv
|
|
||||||
if _eval_ctx.autoescape:
|
|
||||||
rv = Markup(rv)
|
|
||||||
return rv
|
|
||||||
|
|
||||||
|
|
||||||
def do_capitalize(s):
|
|
||||||
"""Capitalize a value. The first character will be uppercase, all others
|
|
||||||
lowercase.
|
|
||||||
"""
|
|
||||||
return soft_unicode(s).capitalize()
|
|
||||||
|
|
||||||
|
|
||||||
def do_title(s):
|
|
||||||
"""Return a titlecased version of the value. I.e. words will start with
|
|
||||||
uppercase letters, all remaining characters are lowercase.
|
|
||||||
"""
|
|
||||||
return soft_unicode(s).title()
|
|
||||||
|
|
||||||
|
|
||||||
def do_dictsort(value, case_sensitive=False, by='key'):
|
|
||||||
"""Sort a dict and yield (key, value) pairs. Because python dicts are
|
|
||||||
unsorted you may want to use this function to order them by either
|
|
||||||
key or value:
|
|
||||||
|
|
||||||
.. sourcecode:: jinja
|
|
||||||
|
|
||||||
{% for item in mydict|dictsort %}
|
|
||||||
sort the dict by key, case insensitive
|
|
||||||
|
|
||||||
{% for item in mydict|dicsort(true) %}
|
|
||||||
sort the dict by key, case sensitive
|
|
||||||
|
|
||||||
{% for item in mydict|dictsort(false, 'value') %}
|
|
||||||
sort the dict by key, case insensitive, sorted
|
|
||||||
normally and ordered by value.
|
|
||||||
"""
|
|
||||||
if by == 'key':
|
|
||||||
pos = 0
|
|
||||||
elif by == 'value':
|
|
||||||
pos = 1
|
|
||||||
else:
|
|
||||||
raise FilterArgumentError('You can only sort by either '
|
|
||||||
'"key" or "value"')
|
|
||||||
def sort_func(item):
|
|
||||||
value = item[pos]
|
|
||||||
if isinstance(value, basestring) and not case_sensitive:
|
|
||||||
value = value.lower()
|
|
||||||
return value
|
|
||||||
|
|
||||||
return sorted(value.items(), key=sort_func)
|
|
||||||
|
|
||||||
|
|
||||||
@environmentfilter
|
|
||||||
def do_sort(environment, value, reverse=False, case_sensitive=False,
|
|
||||||
attribute=None):
|
|
||||||
"""Sort an iterable. Per default it sorts ascending, if you pass it
|
|
||||||
true as first argument it will reverse the sorting.
|
|
||||||
|
|
||||||
If the iterable is made of strings the third parameter can be used to
|
|
||||||
control the case sensitiveness of the comparison which is disabled by
|
|
||||||
default.
|
|
||||||
|
|
||||||
.. sourcecode:: jinja
|
|
||||||
|
|
||||||
{% for item in iterable|sort %}
|
|
||||||
...
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
It is also possible to sort by an attribute (for example to sort
|
|
||||||
by the date of an object) by specifying the `attribute` parameter:
|
|
||||||
|
|
||||||
.. sourcecode:: jinja
|
|
||||||
|
|
||||||
{% for item in iterable|sort(attribute='date') %}
|
|
||||||
...
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
.. versionchanged:: 2.6
|
|
||||||
The `attribute` parameter was added.
|
|
||||||
"""
|
|
||||||
if not case_sensitive:
|
|
||||||
def sort_func(item):
|
|
||||||
if isinstance(item, basestring):
|
|
||||||
item = item.lower()
|
|
||||||
return item
|
|
||||||
else:
|
|
||||||
sort_func = None
|
|
||||||
if attribute is not None:
|
|
||||||
getter = make_attrgetter(environment, attribute)
|
|
||||||
def sort_func(item, processor=sort_func or (lambda x: x)):
|
|
||||||
return processor(getter(item))
|
|
||||||
return sorted(value, key=sort_func, reverse=reverse)
|
|
||||||
|
|
||||||
|
|
||||||
def do_default(value, default_value=u'', boolean=False):
|
|
||||||
"""If the value is undefined it will return the passed default value,
|
|
||||||
otherwise the value of the variable:
|
|
||||||
|
|
||||||
.. sourcecode:: jinja
|
|
||||||
|
|
||||||
{{ my_variable|default('my_variable is not defined') }}
|
|
||||||
|
|
||||||
This will output the value of ``my_variable`` if the variable was
|
|
||||||
defined, otherwise ``'my_variable is not defined'``. If you want
|
|
||||||
to use default with variables that evaluate to false you have to
|
|
||||||
set the second parameter to `true`:
|
|
||||||
|
|
||||||
.. sourcecode:: jinja
|
|
||||||
|
|
||||||
{{ ''|default('the string was empty', true) }}
|
|
||||||
"""
|
|
||||||
if (boolean and not value) or isinstance(value, Undefined):
|
|
||||||
return default_value
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
@evalcontextfilter
|
|
||||||
def do_join(eval_ctx, value, d=u'', attribute=None):
|
|
||||||
"""Return a string which is the concatenation of the strings in the
|
|
||||||
sequence. The separator between elements is an empty string per
|
|
||||||
default, you can define it with the optional parameter:
|
|
||||||
|
|
||||||
.. sourcecode:: jinja
|
|
||||||
|
|
||||||
{{ [1, 2, 3]|join('|') }}
|
|
||||||
-> 1|2|3
|
|
||||||
|
|
||||||
{{ [1, 2, 3]|join }}
|
|
||||||
-> 123
|
|
||||||
|
|
||||||
It is also possible to join certain attributes of an object:
|
|
||||||
|
|
||||||
.. sourcecode:: jinja
|
|
||||||
|
|
||||||
{{ users|join(', ', attribute='username') }}
|
|
||||||
|
|
||||||
.. versionadded:: 2.6
|
|
||||||
The `attribute` parameter was added.
|
|
||||||
"""
|
|
||||||
if attribute is not None:
|
|
||||||
value = imap(make_attrgetter(eval_ctx.environment, attribute), value)
|
|
||||||
|
|
||||||
# no automatic escaping? joining is a lot eaiser then
|
|
||||||
if not eval_ctx.autoescape:
|
|
||||||
return unicode(d).join(imap(unicode, value))
|
|
||||||
|
|
||||||
# if the delimiter doesn't have an html representation we check
|
|
||||||
# if any of the items has. If yes we do a coercion to Markup
|
|
||||||
if not hasattr(d, '__html__'):
|
|
||||||
value = list(value)
|
|
||||||
do_escape = False
|
|
||||||
for idx, item in enumerate(value):
|
|
||||||
if hasattr(item, '__html__'):
|
|
||||||
do_escape = True
|
|
||||||
else:
|
|
||||||
value[idx] = unicode(item)
|
|
||||||
if do_escape:
|
|
||||||
d = escape(d)
|
|
||||||
else:
|
|
||||||
d = unicode(d)
|
|
||||||
return d.join(value)
|
|
||||||
|
|
||||||
# no html involved, to normal joining
|
|
||||||
return soft_unicode(d).join(imap(soft_unicode, value))
|
|
||||||
|
|
||||||
|
|
||||||
def do_center(value, width=80):
|
|
||||||
"""Centers the value in a field of a given width."""
|
|
||||||
return unicode(value).center(width)
|
|
||||||
|
|
||||||
|
|
||||||
@environmentfilter
|
|
||||||
def do_first(environment, seq):
|
|
||||||
"""Return the first item of a sequence."""
|
|
||||||
try:
|
|
||||||
return iter(seq).next()
|
|
||||||
except StopIteration:
|
|
||||||
return environment.undefined('No first item, sequence was empty.')
|
|
||||||
|
|
||||||
|
|
||||||
@environmentfilter
|
|
||||||
def do_last(environment, seq):
|
|
||||||
"""Return the last item of a sequence."""
|
|
||||||
try:
|
|
||||||
return iter(reversed(seq)).next()
|
|
||||||
except StopIteration:
|
|
||||||
return environment.undefined('No last item, sequence was empty.')
|
|
||||||
|
|
||||||
|
|
||||||
@environmentfilter
|
|
||||||
def do_random(environment, seq):
|
|
||||||
"""Return a random item from the sequence."""
|
|
||||||
try:
|
|
||||||
return choice(seq)
|
|
||||||
except IndexError:
|
|
||||||
return environment.undefined('No random item, sequence was empty.')
|
|
||||||
|
|
||||||
|
|
||||||
def do_filesizeformat(value, binary=False):
|
|
||||||
"""Format the value like a 'human-readable' file size (i.e. 13 kB,
|
|
||||||
4.1 MB, 102 Bytes, etc). Per default decimal prefixes are used (Mega,
|
|
||||||
Giga, etc.), if the second parameter is set to `True` the binary
|
|
||||||
prefixes are used (Mebi, Gibi).
|
|
||||||
"""
|
|
||||||
bytes = float(value)
|
|
||||||
base = binary and 1024 or 1000
|
|
||||||
prefixes = [
|
|
||||||
(binary and "KiB" or "kB"),
|
|
||||||
(binary and "MiB" or "MB"),
|
|
||||||
(binary and "GiB" or "GB"),
|
|
||||||
(binary and "TiB" or "TB"),
|
|
||||||
(binary and "PiB" or "PB"),
|
|
||||||
(binary and "EiB" or "EB"),
|
|
||||||
(binary and "ZiB" or "ZB"),
|
|
||||||
(binary and "YiB" or "YB")
|
|
||||||
]
|
|
||||||
if bytes == 1:
|
|
||||||
return "1 Byte"
|
|
||||||
elif bytes < base:
|
|
||||||
return "%d Bytes" % bytes
|
|
||||||
else:
|
|
||||||
for i, prefix in enumerate(prefixes):
|
|
||||||
unit = base * base ** (i + 1)
|
|
||||||
if bytes < unit:
|
|
||||||
return "%.1f %s" % ((bytes / unit), prefix)
|
|
||||||
return "%.1f %s" % ((bytes / unit), prefix)
|
|
||||||
|
|
||||||
|
|
||||||
def do_pprint(value, verbose=False):
|
|
||||||
"""Pretty print a variable. Useful for debugging.
|
|
||||||
|
|
||||||
With Jinja 1.2 onwards you can pass it a parameter. If this parameter
|
|
||||||
is truthy the output will be more verbose (this requires `pretty`)
|
|
||||||
"""
|
|
||||||
return pformat(value, verbose=verbose)
|
|
||||||
|
|
||||||
|
|
||||||
@evalcontextfilter
|
|
||||||
def do_urlize(eval_ctx, value, trim_url_limit=None, nofollow=False):
|
|
||||||
"""Converts URLs in plain text into clickable links.
|
|
||||||
|
|
||||||
If you pass the filter an additional integer it will shorten the urls
|
|
||||||
to that number. Also a third argument exists that makes the urls
|
|
||||||
"nofollow":
|
|
||||||
|
|
||||||
.. sourcecode:: jinja
|
|
||||||
|
|
||||||
{{ mytext|urlize(40, true) }}
|
|
||||||
links are shortened to 40 chars and defined with rel="nofollow"
|
|
||||||
"""
|
|
||||||
rv = urlize(value, trim_url_limit, nofollow)
|
|
||||||
if eval_ctx.autoescape:
|
|
||||||
rv = Markup(rv)
|
|
||||||
return rv
|
|
||||||
|
|
||||||
|
|
||||||
def do_indent(s, width=4, indentfirst=False):
|
|
||||||
"""Return a copy of the passed string, each line indented by
|
|
||||||
4 spaces. The first line is not indented. If you want to
|
|
||||||
change the number of spaces or indent the first line too
|
|
||||||
you can pass additional parameters to the filter:
|
|
||||||
|
|
||||||
.. sourcecode:: jinja
|
|
||||||
|
|
||||||
{{ mytext|indent(2, true) }}
|
|
||||||
indent by two spaces and indent the first line too.
|
|
||||||
"""
|
|
||||||
indention = u' ' * width
|
|
||||||
rv = (u'\n' + indention).join(s.splitlines())
|
|
||||||
if indentfirst:
|
|
||||||
rv = indention + rv
|
|
||||||
return rv
|
|
||||||
|
|
||||||
|
|
||||||
def do_truncate(s, length=255, killwords=False, end='...'):
|
|
||||||
"""Return a truncated copy of the string. The length is specified
|
|
||||||
with the first parameter which defaults to ``255``. If the second
|
|
||||||
parameter is ``true`` the filter will cut the text at length. Otherwise
|
|
||||||
it will try to save the last word. If the text was in fact
|
|
||||||
truncated it will append an ellipsis sign (``"..."``). If you want a
|
|
||||||
different ellipsis sign than ``"..."`` you can specify it using the
|
|
||||||
third parameter.
|
|
||||||
|
|
||||||
.. sourcecode jinja::
|
|
||||||
|
|
||||||
{{ mytext|truncate(300, false, '»') }}
|
|
||||||
truncate mytext to 300 chars, don't split up words, use a
|
|
||||||
right pointing double arrow as ellipsis sign.
|
|
||||||
"""
|
|
||||||
if len(s) <= length:
|
|
||||||
return s
|
|
||||||
elif killwords:
|
|
||||||
return s[:length] + end
|
|
||||||
words = s.split(' ')
|
|
||||||
result = []
|
|
||||||
m = 0
|
|
||||||
for word in words:
|
|
||||||
m += len(word) + 1
|
|
||||||
if m > length:
|
|
||||||
break
|
|
||||||
result.append(word)
|
|
||||||
result.append(end)
|
|
||||||
return u' '.join(result)
|
|
||||||
|
|
||||||
@environmentfilter
|
|
||||||
def do_wordwrap(environment, s, width=79, break_long_words=True):
|
|
||||||
"""
|
|
||||||
Return a copy of the string passed to the filter wrapped after
|
|
||||||
``79`` characters. You can override this default using the first
|
|
||||||
parameter. If you set the second parameter to `false` Jinja will not
|
|
||||||
split words apart if they are longer than `width`.
|
|
||||||
"""
|
|
||||||
import textwrap
|
|
||||||
return environment.newline_sequence.join(textwrap.wrap(s, width=width, expand_tabs=False,
|
|
||||||
replace_whitespace=False,
|
|
||||||
break_long_words=break_long_words))
|
|
||||||
|
|
||||||
|
|
||||||
def do_wordcount(s):
|
|
||||||
"""Count the words in that string."""
|
|
||||||
return len(_word_re.findall(s))
|
|
||||||
|
|
||||||
|
|
||||||
def do_int(value, default=0):
|
|
||||||
"""Convert the value into an integer. If the
|
|
||||||
conversion doesn't work it will return ``0``. You can
|
|
||||||
override this default using the first parameter.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return int(value)
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
# this quirk is necessary so that "42.23"|int gives 42.
|
|
||||||
try:
|
|
||||||
return int(float(value))
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
return default
|
|
||||||
|
|
||||||
|
|
||||||
def do_float(value, default=0.0):
|
|
||||||
"""Convert the value into a floating point number. If the
|
|
||||||
conversion doesn't work it will return ``0.0``. You can
|
|
||||||
override this default using the first parameter.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return float(value)
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
return default
|
|
||||||
|
|
||||||
|
|
||||||
def do_format(value, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Apply python string formatting on an object:
|
|
||||||
|
|
||||||
.. sourcecode:: jinja
|
|
||||||
|
|
||||||
{{ "%s - %s"|format("Hello?", "Foo!") }}
|
|
||||||
-> Hello? - Foo!
|
|
||||||
"""
|
|
||||||
if args and kwargs:
|
|
||||||
raise FilterArgumentError('can\'t handle positional and keyword '
|
|
||||||
'arguments at the same time')
|
|
||||||
return soft_unicode(value) % (kwargs or args)
|
|
||||||
|
|
||||||
|
|
||||||
def do_trim(value):
|
|
||||||
"""Strip leading and trailing whitespace."""
|
|
||||||
return soft_unicode(value).strip()
|
|
||||||
|
|
||||||
|
|
||||||
def do_striptags(value):
|
|
||||||
"""Strip SGML/XML tags and replace adjacent whitespace by one space.
|
|
||||||
"""
|
|
||||||
if hasattr(value, '__html__'):
|
|
||||||
value = value.__html__()
|
|
||||||
return Markup(unicode(value)).striptags()
|
|
||||||
|
|
||||||
|
|
||||||
def do_slice(value, slices, fill_with=None):
|
|
||||||
"""Slice an iterator and return a list of lists containing
|
|
||||||
those items. Useful if you want to create a div containing
|
|
||||||
three ul tags that represent columns:
|
|
||||||
|
|
||||||
.. sourcecode:: html+jinja
|
|
||||||
|
|
||||||
<div class="columwrapper">
|
|
||||||
{%- for column in items|slice(3) %}
|
|
||||||
<ul class="column-{{ loop.index }}">
|
|
||||||
{%- for item in column %}
|
|
||||||
<li>{{ item }}</li>
|
|
||||||
{%- endfor %}
|
|
||||||
</ul>
|
|
||||||
{%- endfor %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
If you pass it a second argument it's used to fill missing
|
|
||||||
values on the last iteration.
|
|
||||||
"""
|
|
||||||
seq = list(value)
|
|
||||||
length = len(seq)
|
|
||||||
items_per_slice = length // slices
|
|
||||||
slices_with_extra = length % slices
|
|
||||||
offset = 0
|
|
||||||
for slice_number in xrange(slices):
|
|
||||||
start = offset + slice_number * items_per_slice
|
|
||||||
if slice_number < slices_with_extra:
|
|
||||||
offset += 1
|
|
||||||
end = offset + (slice_number + 1) * items_per_slice
|
|
||||||
tmp = seq[start:end]
|
|
||||||
if fill_with is not None and slice_number >= slices_with_extra:
|
|
||||||
tmp.append(fill_with)
|
|
||||||
yield tmp
|
|
||||||
|
|
||||||
|
|
||||||
def do_batch(value, linecount, fill_with=None):
|
|
||||||
"""
|
|
||||||
A filter that batches items. It works pretty much like `slice`
|
|
||||||
just the other way round. It returns a list of lists with the
|
|
||||||
given number of items. If you provide a second parameter this
|
|
||||||
is used to fill missing items. See this example:
|
|
||||||
|
|
||||||
.. sourcecode:: html+jinja
|
|
||||||
|
|
||||||
<table>
|
|
||||||
{%- for row in items|batch(3, ' ') %}
|
|
||||||
<tr>
|
|
||||||
{%- for column in row %}
|
|
||||||
<td>{{ column }}</td>
|
|
||||||
{%- endfor %}
|
|
||||||
</tr>
|
|
||||||
{%- endfor %}
|
|
||||||
</table>
|
|
||||||
"""
|
|
||||||
result = []
|
|
||||||
tmp = []
|
|
||||||
for item in value:
|
|
||||||
if len(tmp) == linecount:
|
|
||||||
yield tmp
|
|
||||||
tmp = []
|
|
||||||
tmp.append(item)
|
|
||||||
if tmp:
|
|
||||||
if fill_with is not None and len(tmp) < linecount:
|
|
||||||
tmp += [fill_with] * (linecount - len(tmp))
|
|
||||||
yield tmp
|
|
||||||
|
|
||||||
|
|
||||||
def do_round(value, precision=0, method='common'):
|
|
||||||
"""Round the number to a given precision. The first
|
|
||||||
parameter specifies the precision (default is ``0``), the
|
|
||||||
second the rounding method:
|
|
||||||
|
|
||||||
- ``'common'`` rounds either up or down
|
|
||||||
- ``'ceil'`` always rounds up
|
|
||||||
- ``'floor'`` always rounds down
|
|
||||||
|
|
||||||
If you don't specify a method ``'common'`` is used.
|
|
||||||
|
|
||||||
.. sourcecode:: jinja
|
|
||||||
|
|
||||||
{{ 42.55|round }}
|
|
||||||
-> 43.0
|
|
||||||
{{ 42.55|round(1, 'floor') }}
|
|
||||||
-> 42.5
|
|
||||||
|
|
||||||
Note that even if rounded to 0 precision, a float is returned. If
|
|
||||||
you need a real integer, pipe it through `int`:
|
|
||||||
|
|
||||||
.. sourcecode:: jinja
|
|
||||||
|
|
||||||
{{ 42.55|round|int }}
|
|
||||||
-> 43
|
|
||||||
"""
|
|
||||||
if not method in ('common', 'ceil', 'floor'):
|
|
||||||
raise FilterArgumentError('method must be common, ceil or floor')
|
|
||||||
if method == 'common':
|
|
||||||
return round(value, precision)
|
|
||||||
func = getattr(math, method)
|
|
||||||
return func(value * (10 ** precision)) / (10 ** precision)
|
|
||||||
|
|
||||||
|
|
||||||
@environmentfilter
|
|
||||||
def do_groupby(environment, value, attribute):
|
|
||||||
"""Group a sequence of objects by a common attribute.
|
|
||||||
|
|
||||||
If you for example have a list of dicts or objects that represent persons
|
|
||||||
with `gender`, `first_name` and `last_name` attributes and you want to
|
|
||||||
group all users by genders you can do something like the following
|
|
||||||
snippet:
|
|
||||||
|
|
||||||
.. sourcecode:: html+jinja
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
{% for group in persons|groupby('gender') %}
|
|
||||||
<li>{{ group.grouper }}<ul>
|
|
||||||
{% for person in group.list %}
|
|
||||||
<li>{{ person.first_name }} {{ person.last_name }}</li>
|
|
||||||
{% endfor %}</ul></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Additionally it's possible to use tuple unpacking for the grouper and
|
|
||||||
list:
|
|
||||||
|
|
||||||
.. sourcecode:: html+jinja
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
{% for grouper, list in persons|groupby('gender') %}
|
|
||||||
...
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
As you can see the item we're grouping by is stored in the `grouper`
|
|
||||||
attribute and the `list` contains all the objects that have this grouper
|
|
||||||
in common.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.6
|
|
||||||
It's now possible to use dotted notation to group by the child
|
|
||||||
attribute of another attribute.
|
|
||||||
"""
|
|
||||||
expr = make_attrgetter(environment, attribute)
|
|
||||||
return sorted(map(_GroupTuple, groupby(sorted(value, key=expr), expr)))
|
|
||||||
|
|
||||||
|
|
||||||
class _GroupTuple(tuple):
|
|
||||||
__slots__ = ()
|
|
||||||
grouper = property(itemgetter(0))
|
|
||||||
list = property(itemgetter(1))
|
|
||||||
|
|
||||||
def __new__(cls, (key, value)):
|
|
||||||
return tuple.__new__(cls, (key, list(value)))
|
|
||||||
|
|
||||||
|
|
||||||
@environmentfilter
|
|
||||||
def do_sum(environment, iterable, attribute=None, start=0):
|
|
||||||
"""Returns the sum of a sequence of numbers plus the value of parameter
|
|
||||||
'start' (which defaults to 0). When the sequence is empty it returns
|
|
||||||
start.
|
|
||||||
|
|
||||||
It is also possible to sum up only certain attributes:
|
|
||||||
|
|
||||||
.. sourcecode:: jinja
|
|
||||||
|
|
||||||
Total: {{ items|sum(attribute='price') }}
|
|
||||||
|
|
||||||
.. versionchanged:: 2.6
|
|
||||||
The `attribute` parameter was added to allow suming up over
|
|
||||||
attributes. Also the `start` parameter was moved on to the right.
|
|
||||||
"""
|
|
||||||
if attribute is not None:
|
|
||||||
iterable = imap(make_attrgetter(environment, attribute), iterable)
|
|
||||||
return sum(iterable, start)
|
|
||||||
|
|
||||||
|
|
||||||
def do_list(value):
|
|
||||||
"""Convert the value into a list. If it was a string the returned list
|
|
||||||
will be a list of characters.
|
|
||||||
"""
|
|
||||||
return list(value)
|
|
||||||
|
|
||||||
|
|
||||||
def do_mark_safe(value):
|
|
||||||
"""Mark the value as safe which means that in an environment with automatic
|
|
||||||
escaping enabled this variable will not be escaped.
|
|
||||||
"""
|
|
||||||
return Markup(value)
|
|
||||||
|
|
||||||
|
|
||||||
def do_mark_unsafe(value):
|
|
||||||
"""Mark a value as unsafe. This is the reverse operation for :func:`safe`."""
|
|
||||||
return unicode(value)
|
|
||||||
|
|
||||||
|
|
||||||
def do_reverse(value):
|
|
||||||
"""Reverse the object or return an iterator the iterates over it the other
|
|
||||||
way round.
|
|
||||||
"""
|
|
||||||
if isinstance(value, basestring):
|
|
||||||
return value[::-1]
|
|
||||||
try:
|
|
||||||
return reversed(value)
|
|
||||||
except TypeError:
|
|
||||||
try:
|
|
||||||
rv = list(value)
|
|
||||||
rv.reverse()
|
|
||||||
return rv
|
|
||||||
except TypeError:
|
|
||||||
raise FilterArgumentError('argument must be iterable')
|
|
||||||
|
|
||||||
|
|
||||||
@environmentfilter
|
|
||||||
def do_attr(environment, obj, name):
|
|
||||||
"""Get an attribute of an object. ``foo|attr("bar")`` works like
|
|
||||||
``foo["bar"]`` just that always an attribute is returned and items are not
|
|
||||||
looked up.
|
|
||||||
|
|
||||||
See :ref:`Notes on subscriptions <notes-on-subscriptions>` for more details.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
name = str(name)
|
|
||||||
except UnicodeError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
value = getattr(obj, name)
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
if environment.sandboxed and not \
|
|
||||||
environment.is_safe_attribute(obj, name, value):
|
|
||||||
return environment.unsafe_undefined(obj, name)
|
|
||||||
return value
|
|
||||||
return environment.undefined(obj=obj, name=name)
|
|
||||||
|
|
||||||
|
|
||||||
FILTERS = {
|
|
||||||
'attr': do_attr,
|
|
||||||
'replace': do_replace,
|
|
||||||
'upper': do_upper,
|
|
||||||
'lower': do_lower,
|
|
||||||
'escape': escape,
|
|
||||||
'e': escape,
|
|
||||||
'forceescape': do_forceescape,
|
|
||||||
'capitalize': do_capitalize,
|
|
||||||
'title': do_title,
|
|
||||||
'default': do_default,
|
|
||||||
'd': do_default,
|
|
||||||
'join': do_join,
|
|
||||||
'count': len,
|
|
||||||
'dictsort': do_dictsort,
|
|
||||||
'sort': do_sort,
|
|
||||||
'length': len,
|
|
||||||
'reverse': do_reverse,
|
|
||||||
'center': do_center,
|
|
||||||
'indent': do_indent,
|
|
||||||
'title': do_title,
|
|
||||||
'capitalize': do_capitalize,
|
|
||||||
'first': do_first,
|
|
||||||
'last': do_last,
|
|
||||||
'random': do_random,
|
|
||||||
'filesizeformat': do_filesizeformat,
|
|
||||||
'pprint': do_pprint,
|
|
||||||
'truncate': do_truncate,
|
|
||||||
'wordwrap': do_wordwrap,
|
|
||||||
'wordcount': do_wordcount,
|
|
||||||
'int': do_int,
|
|
||||||
'float': do_float,
|
|
||||||
'string': soft_unicode,
|
|
||||||
'list': do_list,
|
|
||||||
'urlize': do_urlize,
|
|
||||||
'format': do_format,
|
|
||||||
'trim': do_trim,
|
|
||||||
'striptags': do_striptags,
|
|
||||||
'slice': do_slice,
|
|
||||||
'batch': do_batch,
|
|
||||||
'sum': do_sum,
|
|
||||||
'abs': abs,
|
|
||||||
'round': do_round,
|
|
||||||
'groupby': do_groupby,
|
|
||||||
'safe': do_mark_safe,
|
|
||||||
'xmlattr': do_xmlattr
|
|
||||||
}
|
|
||||||
@@ -1,681 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
jinja2.lexer
|
|
||||||
~~~~~~~~~~~~
|
|
||||||
|
|
||||||
This module implements a Jinja / Python combination lexer. The
|
|
||||||
`Lexer` class provided by this module is used to do some preprocessing
|
|
||||||
for Jinja.
|
|
||||||
|
|
||||||
On the one hand it filters out invalid operators like the bitshift
|
|
||||||
operators we don't allow in templates. On the other hand it separates
|
|
||||||
template code and python code in expressions.
|
|
||||||
|
|
||||||
:copyright: (c) 2010 by the Jinja Team.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
import re
|
|
||||||
from operator import itemgetter
|
|
||||||
from collections import deque
|
|
||||||
from jinja2.exceptions import TemplateSyntaxError
|
|
||||||
from jinja2.utils import LRUCache, next
|
|
||||||
|
|
||||||
|
|
||||||
# cache for the lexers. Exists in order to be able to have multiple
|
|
||||||
# environments with the same lexer
|
|
||||||
_lexer_cache = LRUCache(50)
|
|
||||||
|
|
||||||
# static regular expressions
|
|
||||||
whitespace_re = re.compile(r'\s+', re.U)
|
|
||||||
string_re = re.compile(r"('([^'\\]*(?:\\.[^'\\]*)*)'"
|
|
||||||
r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S)
|
|
||||||
integer_re = re.compile(r'\d+')
|
|
||||||
|
|
||||||
# we use the unicode identifier rule if this python version is able
|
|
||||||
# to handle unicode identifiers, otherwise the standard ASCII one.
|
|
||||||
try:
|
|
||||||
compile('föö', '<unknown>', 'eval')
|
|
||||||
except SyntaxError:
|
|
||||||
name_re = re.compile(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b')
|
|
||||||
else:
|
|
||||||
from jinja2 import _stringdefs
|
|
||||||
name_re = re.compile(r'[%s][%s]*' % (_stringdefs.xid_start,
|
|
||||||
_stringdefs.xid_continue))
|
|
||||||
|
|
||||||
float_re = re.compile(r'(?<!\.)\d+\.\d+')
|
|
||||||
newline_re = re.compile(r'(\r\n|\r|\n)')
|
|
||||||
|
|
||||||
# internal the tokens and keep references to them
|
|
||||||
TOKEN_ADD = intern('add')
|
|
||||||
TOKEN_ASSIGN = intern('assign')
|
|
||||||
TOKEN_COLON = intern('colon')
|
|
||||||
TOKEN_COMMA = intern('comma')
|
|
||||||
TOKEN_DIV = intern('div')
|
|
||||||
TOKEN_DOT = intern('dot')
|
|
||||||
TOKEN_EQ = intern('eq')
|
|
||||||
TOKEN_FLOORDIV = intern('floordiv')
|
|
||||||
TOKEN_GT = intern('gt')
|
|
||||||
TOKEN_GTEQ = intern('gteq')
|
|
||||||
TOKEN_LBRACE = intern('lbrace')
|
|
||||||
TOKEN_LBRACKET = intern('lbracket')
|
|
||||||
TOKEN_LPAREN = intern('lparen')
|
|
||||||
TOKEN_LT = intern('lt')
|
|
||||||
TOKEN_LTEQ = intern('lteq')
|
|
||||||
TOKEN_MOD = intern('mod')
|
|
||||||
TOKEN_MUL = intern('mul')
|
|
||||||
TOKEN_NE = intern('ne')
|
|
||||||
TOKEN_PIPE = intern('pipe')
|
|
||||||
TOKEN_POW = intern('pow')
|
|
||||||
TOKEN_RBRACE = intern('rbrace')
|
|
||||||
TOKEN_RBRACKET = intern('rbracket')
|
|
||||||
TOKEN_RPAREN = intern('rparen')
|
|
||||||
TOKEN_SEMICOLON = intern('semicolon')
|
|
||||||
TOKEN_SUB = intern('sub')
|
|
||||||
TOKEN_TILDE = intern('tilde')
|
|
||||||
TOKEN_WHITESPACE = intern('whitespace')
|
|
||||||
TOKEN_FLOAT = intern('float')
|
|
||||||
TOKEN_INTEGER = intern('integer')
|
|
||||||
TOKEN_NAME = intern('name')
|
|
||||||
TOKEN_STRING = intern('string')
|
|
||||||
TOKEN_OPERATOR = intern('operator')
|
|
||||||
TOKEN_BLOCK_BEGIN = intern('block_begin')
|
|
||||||
TOKEN_BLOCK_END = intern('block_end')
|
|
||||||
TOKEN_VARIABLE_BEGIN = intern('variable_begin')
|
|
||||||
TOKEN_VARIABLE_END = intern('variable_end')
|
|
||||||
TOKEN_RAW_BEGIN = intern('raw_begin')
|
|
||||||
TOKEN_RAW_END = intern('raw_end')
|
|
||||||
TOKEN_COMMENT_BEGIN = intern('comment_begin')
|
|
||||||
TOKEN_COMMENT_END = intern('comment_end')
|
|
||||||
TOKEN_COMMENT = intern('comment')
|
|
||||||
TOKEN_LINESTATEMENT_BEGIN = intern('linestatement_begin')
|
|
||||||
TOKEN_LINESTATEMENT_END = intern('linestatement_end')
|
|
||||||
TOKEN_LINECOMMENT_BEGIN = intern('linecomment_begin')
|
|
||||||
TOKEN_LINECOMMENT_END = intern('linecomment_end')
|
|
||||||
TOKEN_LINECOMMENT = intern('linecomment')
|
|
||||||
TOKEN_DATA = intern('data')
|
|
||||||
TOKEN_INITIAL = intern('initial')
|
|
||||||
TOKEN_EOF = intern('eof')
|
|
||||||
|
|
||||||
# bind operators to token types
|
|
||||||
operators = {
|
|
||||||
'+': TOKEN_ADD,
|
|
||||||
'-': TOKEN_SUB,
|
|
||||||
'/': TOKEN_DIV,
|
|
||||||
'//': TOKEN_FLOORDIV,
|
|
||||||
'*': TOKEN_MUL,
|
|
||||||
'%': TOKEN_MOD,
|
|
||||||
'**': TOKEN_POW,
|
|
||||||
'~': TOKEN_TILDE,
|
|
||||||
'[': TOKEN_LBRACKET,
|
|
||||||
']': TOKEN_RBRACKET,
|
|
||||||
'(': TOKEN_LPAREN,
|
|
||||||
')': TOKEN_RPAREN,
|
|
||||||
'{': TOKEN_LBRACE,
|
|
||||||
'}': TOKEN_RBRACE,
|
|
||||||
'==': TOKEN_EQ,
|
|
||||||
'!=': TOKEN_NE,
|
|
||||||
'>': TOKEN_GT,
|
|
||||||
'>=': TOKEN_GTEQ,
|
|
||||||
'<': TOKEN_LT,
|
|
||||||
'<=': TOKEN_LTEQ,
|
|
||||||
'=': TOKEN_ASSIGN,
|
|
||||||
'.': TOKEN_DOT,
|
|
||||||
':': TOKEN_COLON,
|
|
||||||
'|': TOKEN_PIPE,
|
|
||||||
',': TOKEN_COMMA,
|
|
||||||
';': TOKEN_SEMICOLON
|
|
||||||
}
|
|
||||||
|
|
||||||
reverse_operators = dict([(v, k) for k, v in operators.iteritems()])
|
|
||||||
assert len(operators) == len(reverse_operators), 'operators dropped'
|
|
||||||
operator_re = re.compile('(%s)' % '|'.join(re.escape(x) for x in
|
|
||||||
sorted(operators, key=lambda x: -len(x))))
|
|
||||||
|
|
||||||
ignored_tokens = frozenset([TOKEN_COMMENT_BEGIN, TOKEN_COMMENT,
|
|
||||||
TOKEN_COMMENT_END, TOKEN_WHITESPACE,
|
|
||||||
TOKEN_WHITESPACE, TOKEN_LINECOMMENT_BEGIN,
|
|
||||||
TOKEN_LINECOMMENT_END, TOKEN_LINECOMMENT])
|
|
||||||
ignore_if_empty = frozenset([TOKEN_WHITESPACE, TOKEN_DATA,
|
|
||||||
TOKEN_COMMENT, TOKEN_LINECOMMENT])
|
|
||||||
|
|
||||||
|
|
||||||
def _describe_token_type(token_type):
|
|
||||||
if token_type in reverse_operators:
|
|
||||||
return reverse_operators[token_type]
|
|
||||||
return {
|
|
||||||
TOKEN_COMMENT_BEGIN: 'begin of comment',
|
|
||||||
TOKEN_COMMENT_END: 'end of comment',
|
|
||||||
TOKEN_COMMENT: 'comment',
|
|
||||||
TOKEN_LINECOMMENT: 'comment',
|
|
||||||
TOKEN_BLOCK_BEGIN: 'begin of statement block',
|
|
||||||
TOKEN_BLOCK_END: 'end of statement block',
|
|
||||||
TOKEN_VARIABLE_BEGIN: 'begin of print statement',
|
|
||||||
TOKEN_VARIABLE_END: 'end of print statement',
|
|
||||||
TOKEN_LINESTATEMENT_BEGIN: 'begin of line statement',
|
|
||||||
TOKEN_LINESTATEMENT_END: 'end of line statement',
|
|
||||||
TOKEN_DATA: 'template data / text',
|
|
||||||
TOKEN_EOF: 'end of template'
|
|
||||||
}.get(token_type, token_type)
|
|
||||||
|
|
||||||
|
|
||||||
def describe_token(token):
|
|
||||||
"""Returns a description of the token."""
|
|
||||||
if token.type == 'name':
|
|
||||||
return token.value
|
|
||||||
return _describe_token_type(token.type)
|
|
||||||
|
|
||||||
|
|
||||||
def describe_token_expr(expr):
|
|
||||||
"""Like `describe_token` but for token expressions."""
|
|
||||||
if ':' in expr:
|
|
||||||
type, value = expr.split(':', 1)
|
|
||||||
if type == 'name':
|
|
||||||
return value
|
|
||||||
else:
|
|
||||||
type = expr
|
|
||||||
return _describe_token_type(type)
|
|
||||||
|
|
||||||
|
|
||||||
def count_newlines(value):
|
|
||||||
"""Count the number of newline characters in the string. This is
|
|
||||||
useful for extensions that filter a stream.
|
|
||||||
"""
|
|
||||||
return len(newline_re.findall(value))
|
|
||||||
|
|
||||||
|
|
||||||
def compile_rules(environment):
|
|
||||||
"""Compiles all the rules from the environment into a list of rules."""
|
|
||||||
e = re.escape
|
|
||||||
rules = [
|
|
||||||
(len(environment.comment_start_string), 'comment',
|
|
||||||
e(environment.comment_start_string)),
|
|
||||||
(len(environment.block_start_string), 'block',
|
|
||||||
e(environment.block_start_string)),
|
|
||||||
(len(environment.variable_start_string), 'variable',
|
|
||||||
e(environment.variable_start_string))
|
|
||||||
]
|
|
||||||
|
|
||||||
if environment.line_statement_prefix is not None:
|
|
||||||
rules.append((len(environment.line_statement_prefix), 'linestatement',
|
|
||||||
r'^\s*' + e(environment.line_statement_prefix)))
|
|
||||||
if environment.line_comment_prefix is not None:
|
|
||||||
rules.append((len(environment.line_comment_prefix), 'linecomment',
|
|
||||||
r'(?:^|(?<=\S))[^\S\r\n]*' +
|
|
||||||
e(environment.line_comment_prefix)))
|
|
||||||
|
|
||||||
return [x[1:] for x in sorted(rules, reverse=True)]
|
|
||||||
|
|
||||||
|
|
||||||
class Failure(object):
|
|
||||||
"""Class that raises a `TemplateSyntaxError` if called.
|
|
||||||
Used by the `Lexer` to specify known errors.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, message, cls=TemplateSyntaxError):
|
|
||||||
self.message = message
|
|
||||||
self.error_class = cls
|
|
||||||
|
|
||||||
def __call__(self, lineno, filename):
|
|
||||||
raise self.error_class(self.message, lineno, filename)
|
|
||||||
|
|
||||||
|
|
||||||
class Token(tuple):
|
|
||||||
"""Token class."""
|
|
||||||
__slots__ = ()
|
|
||||||
lineno, type, value = (property(itemgetter(x)) for x in range(3))
|
|
||||||
|
|
||||||
def __new__(cls, lineno, type, value):
|
|
||||||
return tuple.__new__(cls, (lineno, intern(str(type)), value))
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
if self.type in reverse_operators:
|
|
||||||
return reverse_operators[self.type]
|
|
||||||
elif self.type == 'name':
|
|
||||||
return self.value
|
|
||||||
return self.type
|
|
||||||
|
|
||||||
def test(self, expr):
|
|
||||||
"""Test a token against a token expression. This can either be a
|
|
||||||
token type or ``'token_type:token_value'``. This can only test
|
|
||||||
against string values and types.
|
|
||||||
"""
|
|
||||||
# here we do a regular string equality check as test_any is usually
|
|
||||||
# passed an iterable of not interned strings.
|
|
||||||
if self.type == expr:
|
|
||||||
return True
|
|
||||||
elif ':' in expr:
|
|
||||||
return expr.split(':', 1) == [self.type, self.value]
|
|
||||||
return False
|
|
||||||
|
|
||||||
def test_any(self, *iterable):
|
|
||||||
"""Test against multiple token expressions."""
|
|
||||||
for expr in iterable:
|
|
||||||
if self.test(expr):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'Token(%r, %r, %r)' % (
|
|
||||||
self.lineno,
|
|
||||||
self.type,
|
|
||||||
self.value
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TokenStreamIterator(object):
|
|
||||||
"""The iterator for tokenstreams. Iterate over the stream
|
|
||||||
until the eof token is reached.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, stream):
|
|
||||||
self.stream = stream
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def next(self):
|
|
||||||
token = self.stream.current
|
|
||||||
if token.type is TOKEN_EOF:
|
|
||||||
self.stream.close()
|
|
||||||
raise StopIteration()
|
|
||||||
next(self.stream)
|
|
||||||
return token
|
|
||||||
|
|
||||||
|
|
||||||
class TokenStream(object):
|
|
||||||
"""A token stream is an iterable that yields :class:`Token`\s. The
|
|
||||||
parser however does not iterate over it but calls :meth:`next` to go
|
|
||||||
one token ahead. The current active token is stored as :attr:`current`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, generator, name, filename):
|
|
||||||
self._next = iter(generator).next
|
|
||||||
self._pushed = deque()
|
|
||||||
self.name = name
|
|
||||||
self.filename = filename
|
|
||||||
self.closed = False
|
|
||||||
self.current = Token(1, TOKEN_INITIAL, '')
|
|
||||||
next(self)
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return TokenStreamIterator(self)
|
|
||||||
|
|
||||||
def __nonzero__(self):
|
|
||||||
return bool(self._pushed) or self.current.type is not TOKEN_EOF
|
|
||||||
|
|
||||||
eos = property(lambda x: not x, doc="Are we at the end of the stream?")
|
|
||||||
|
|
||||||
def push(self, token):
|
|
||||||
"""Push a token back to the stream."""
|
|
||||||
self._pushed.append(token)
|
|
||||||
|
|
||||||
def look(self):
|
|
||||||
"""Look at the next token."""
|
|
||||||
old_token = next(self)
|
|
||||||
result = self.current
|
|
||||||
self.push(result)
|
|
||||||
self.current = old_token
|
|
||||||
return result
|
|
||||||
|
|
||||||
def skip(self, n=1):
|
|
||||||
"""Got n tokens ahead."""
|
|
||||||
for x in xrange(n):
|
|
||||||
next(self)
|
|
||||||
|
|
||||||
def next_if(self, expr):
|
|
||||||
"""Perform the token test and return the token if it matched.
|
|
||||||
Otherwise the return value is `None`.
|
|
||||||
"""
|
|
||||||
if self.current.test(expr):
|
|
||||||
return next(self)
|
|
||||||
|
|
||||||
def skip_if(self, expr):
|
|
||||||
"""Like :meth:`next_if` but only returns `True` or `False`."""
|
|
||||||
return self.next_if(expr) is not None
|
|
||||||
|
|
||||||
def next(self):
|
|
||||||
"""Go one token ahead and return the old one"""
|
|
||||||
rv = self.current
|
|
||||||
if self._pushed:
|
|
||||||
self.current = self._pushed.popleft()
|
|
||||||
elif self.current.type is not TOKEN_EOF:
|
|
||||||
try:
|
|
||||||
self.current = self._next()
|
|
||||||
except StopIteration:
|
|
||||||
self.close()
|
|
||||||
return rv
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
"""Close the stream."""
|
|
||||||
self.current = Token(self.current.lineno, TOKEN_EOF, '')
|
|
||||||
self._next = None
|
|
||||||
self.closed = True
|
|
||||||
|
|
||||||
def expect(self, expr):
|
|
||||||
"""Expect a given token type and return it. This accepts the same
|
|
||||||
argument as :meth:`jinja2.lexer.Token.test`.
|
|
||||||
"""
|
|
||||||
if not self.current.test(expr):
|
|
||||||
expr = describe_token_expr(expr)
|
|
||||||
if self.current.type is TOKEN_EOF:
|
|
||||||
raise TemplateSyntaxError('unexpected end of template, '
|
|
||||||
'expected %r.' % expr,
|
|
||||||
self.current.lineno,
|
|
||||||
self.name, self.filename)
|
|
||||||
raise TemplateSyntaxError("expected token %r, got %r" %
|
|
||||||
(expr, describe_token(self.current)),
|
|
||||||
self.current.lineno,
|
|
||||||
self.name, self.filename)
|
|
||||||
try:
|
|
||||||
return self.current
|
|
||||||
finally:
|
|
||||||
next(self)
|
|
||||||
|
|
||||||
|
|
||||||
def get_lexer(environment):
|
|
||||||
"""Return a lexer which is probably cached."""
|
|
||||||
key = (environment.block_start_string,
|
|
||||||
environment.block_end_string,
|
|
||||||
environment.variable_start_string,
|
|
||||||
environment.variable_end_string,
|
|
||||||
environment.comment_start_string,
|
|
||||||
environment.comment_end_string,
|
|
||||||
environment.line_statement_prefix,
|
|
||||||
environment.line_comment_prefix,
|
|
||||||
environment.trim_blocks,
|
|
||||||
environment.newline_sequence)
|
|
||||||
lexer = _lexer_cache.get(key)
|
|
||||||
if lexer is None:
|
|
||||||
lexer = Lexer(environment)
|
|
||||||
_lexer_cache[key] = lexer
|
|
||||||
return lexer
|
|
||||||
|
|
||||||
|
|
||||||
class Lexer(object):
|
|
||||||
"""Class that implements a lexer for a given environment. Automatically
|
|
||||||
created by the environment class, usually you don't have to do that.
|
|
||||||
|
|
||||||
Note that the lexer is not automatically bound to an environment.
|
|
||||||
Multiple environments can share the same lexer.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, environment):
|
|
||||||
# shortcuts
|
|
||||||
c = lambda x: re.compile(x, re.M | re.S)
|
|
||||||
e = re.escape
|
|
||||||
|
|
||||||
# lexing rules for tags
|
|
||||||
tag_rules = [
|
|
||||||
(whitespace_re, TOKEN_WHITESPACE, None),
|
|
||||||
(float_re, TOKEN_FLOAT, None),
|
|
||||||
(integer_re, TOKEN_INTEGER, None),
|
|
||||||
(name_re, TOKEN_NAME, None),
|
|
||||||
(string_re, TOKEN_STRING, None),
|
|
||||||
(operator_re, TOKEN_OPERATOR, None)
|
|
||||||
]
|
|
||||||
|
|
||||||
# assamble the root lexing rule. because "|" is ungreedy
|
|
||||||
# we have to sort by length so that the lexer continues working
|
|
||||||
# as expected when we have parsing rules like <% for block and
|
|
||||||
# <%= for variables. (if someone wants asp like syntax)
|
|
||||||
# variables are just part of the rules if variable processing
|
|
||||||
# is required.
|
|
||||||
root_tag_rules = compile_rules(environment)
|
|
||||||
|
|
||||||
# block suffix if trimming is enabled
|
|
||||||
block_suffix_re = environment.trim_blocks and '\\n?' or ''
|
|
||||||
|
|
||||||
self.newline_sequence = environment.newline_sequence
|
|
||||||
|
|
||||||
# global lexing rules
|
|
||||||
self.rules = {
|
|
||||||
'root': [
|
|
||||||
# directives
|
|
||||||
(c('(.*?)(?:%s)' % '|'.join(
|
|
||||||
[r'(?P<raw_begin>(?:\s*%s\-|%s)\s*raw\s*(?:\-%s\s*|%s))' % (
|
|
||||||
e(environment.block_start_string),
|
|
||||||
e(environment.block_start_string),
|
|
||||||
e(environment.block_end_string),
|
|
||||||
e(environment.block_end_string)
|
|
||||||
)] + [
|
|
||||||
r'(?P<%s_begin>\s*%s\-|%s)' % (n, r, r)
|
|
||||||
for n, r in root_tag_rules
|
|
||||||
])), (TOKEN_DATA, '#bygroup'), '#bygroup'),
|
|
||||||
# data
|
|
||||||
(c('.+'), TOKEN_DATA, None)
|
|
||||||
],
|
|
||||||
# comments
|
|
||||||
TOKEN_COMMENT_BEGIN: [
|
|
||||||
(c(r'(.*?)((?:\-%s\s*|%s)%s)' % (
|
|
||||||
e(environment.comment_end_string),
|
|
||||||
e(environment.comment_end_string),
|
|
||||||
block_suffix_re
|
|
||||||
)), (TOKEN_COMMENT, TOKEN_COMMENT_END), '#pop'),
|
|
||||||
(c('(.)'), (Failure('Missing end of comment tag'),), None)
|
|
||||||
],
|
|
||||||
# blocks
|
|
||||||
TOKEN_BLOCK_BEGIN: [
|
|
||||||
(c('(?:\-%s\s*|%s)%s' % (
|
|
||||||
e(environment.block_end_string),
|
|
||||||
e(environment.block_end_string),
|
|
||||||
block_suffix_re
|
|
||||||
)), TOKEN_BLOCK_END, '#pop'),
|
|
||||||
] + tag_rules,
|
|
||||||
# variables
|
|
||||||
TOKEN_VARIABLE_BEGIN: [
|
|
||||||
(c('\-%s\s*|%s' % (
|
|
||||||
e(environment.variable_end_string),
|
|
||||||
e(environment.variable_end_string)
|
|
||||||
)), TOKEN_VARIABLE_END, '#pop')
|
|
||||||
] + tag_rules,
|
|
||||||
# raw block
|
|
||||||
TOKEN_RAW_BEGIN: [
|
|
||||||
(c('(.*?)((?:\s*%s\-|%s)\s*endraw\s*(?:\-%s\s*|%s%s))' % (
|
|
||||||
e(environment.block_start_string),
|
|
||||||
e(environment.block_start_string),
|
|
||||||
e(environment.block_end_string),
|
|
||||||
e(environment.block_end_string),
|
|
||||||
block_suffix_re
|
|
||||||
)), (TOKEN_DATA, TOKEN_RAW_END), '#pop'),
|
|
||||||
(c('(.)'), (Failure('Missing end of raw directive'),), None)
|
|
||||||
],
|
|
||||||
# line statements
|
|
||||||
TOKEN_LINESTATEMENT_BEGIN: [
|
|
||||||
(c(r'\s*(\n|$)'), TOKEN_LINESTATEMENT_END, '#pop')
|
|
||||||
] + tag_rules,
|
|
||||||
# line comments
|
|
||||||
TOKEN_LINECOMMENT_BEGIN: [
|
|
||||||
(c(r'(.*?)()(?=\n|$)'), (TOKEN_LINECOMMENT,
|
|
||||||
TOKEN_LINECOMMENT_END), '#pop')
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
def _normalize_newlines(self, value):
|
|
||||||
"""Called for strings and template data to normlize it to unicode."""
|
|
||||||
return newline_re.sub(self.newline_sequence, value)
|
|
||||||
|
|
||||||
def tokenize(self, source, name=None, filename=None, state=None):
|
|
||||||
"""Calls tokeniter + tokenize and wraps it in a token stream.
|
|
||||||
"""
|
|
||||||
stream = self.tokeniter(source, name, filename, state)
|
|
||||||
return TokenStream(self.wrap(stream, name, filename), name, filename)
|
|
||||||
|
|
||||||
def wrap(self, stream, name=None, filename=None):
|
|
||||||
"""This is called with the stream as returned by `tokenize` and wraps
|
|
||||||
every token in a :class:`Token` and converts the value.
|
|
||||||
"""
|
|
||||||
for lineno, token, value in stream:
|
|
||||||
if token in ignored_tokens:
|
|
||||||
continue
|
|
||||||
elif token == 'linestatement_begin':
|
|
||||||
token = 'block_begin'
|
|
||||||
elif token == 'linestatement_end':
|
|
||||||
token = 'block_end'
|
|
||||||
# we are not interested in those tokens in the parser
|
|
||||||
elif token in ('raw_begin', 'raw_end'):
|
|
||||||
continue
|
|
||||||
elif token == 'data':
|
|
||||||
value = self._normalize_newlines(value)
|
|
||||||
elif token == 'keyword':
|
|
||||||
token = value
|
|
||||||
elif token == 'name':
|
|
||||||
value = str(value)
|
|
||||||
elif token == 'string':
|
|
||||||
# try to unescape string
|
|
||||||
try:
|
|
||||||
value = self._normalize_newlines(value[1:-1]) \
|
|
||||||
.encode('ascii', 'backslashreplace') \
|
|
||||||
.decode('unicode-escape')
|
|
||||||
except Exception, e:
|
|
||||||
msg = str(e).split(':')[-1].strip()
|
|
||||||
raise TemplateSyntaxError(msg, lineno, name, filename)
|
|
||||||
# if we can express it as bytestring (ascii only)
|
|
||||||
# we do that for support of semi broken APIs
|
|
||||||
# as datetime.datetime.strftime. On python 3 this
|
|
||||||
# call becomes a noop thanks to 2to3
|
|
||||||
try:
|
|
||||||
value = str(value)
|
|
||||||
except UnicodeError:
|
|
||||||
pass
|
|
||||||
elif token == 'integer':
|
|
||||||
value = int(value)
|
|
||||||
elif token == 'float':
|
|
||||||
value = float(value)
|
|
||||||
elif token == 'operator':
|
|
||||||
token = operators[value]
|
|
||||||
yield Token(lineno, token, value)
|
|
||||||
|
|
||||||
def tokeniter(self, source, name, filename=None, state=None):
|
|
||||||
"""This method tokenizes the text and returns the tokens in a
|
|
||||||
generator. Use this method if you just want to tokenize a template.
|
|
||||||
"""
|
|
||||||
source = '\n'.join(unicode(source).splitlines())
|
|
||||||
pos = 0
|
|
||||||
lineno = 1
|
|
||||||
stack = ['root']
|
|
||||||
if state is not None and state != 'root':
|
|
||||||
assert state in ('variable', 'block'), 'invalid state'
|
|
||||||
stack.append(state + '_begin')
|
|
||||||
else:
|
|
||||||
state = 'root'
|
|
||||||
statetokens = self.rules[stack[-1]]
|
|
||||||
source_length = len(source)
|
|
||||||
|
|
||||||
balancing_stack = []
|
|
||||||
|
|
||||||
while 1:
|
|
||||||
# tokenizer loop
|
|
||||||
for regex, tokens, new_state in statetokens:
|
|
||||||
m = regex.match(source, pos)
|
|
||||||
# if no match we try again with the next rule
|
|
||||||
if m is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# we only match blocks and variables if brances / parentheses
|
|
||||||
# are balanced. continue parsing with the lower rule which
|
|
||||||
# is the operator rule. do this only if the end tags look
|
|
||||||
# like operators
|
|
||||||
if balancing_stack and \
|
|
||||||
tokens in ('variable_end', 'block_end',
|
|
||||||
'linestatement_end'):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# tuples support more options
|
|
||||||
if isinstance(tokens, tuple):
|
|
||||||
for idx, token in enumerate(tokens):
|
|
||||||
# failure group
|
|
||||||
if token.__class__ is Failure:
|
|
||||||
raise token(lineno, filename)
|
|
||||||
# bygroup is a bit more complex, in that case we
|
|
||||||
# yield for the current token the first named
|
|
||||||
# group that matched
|
|
||||||
elif token == '#bygroup':
|
|
||||||
for key, value in m.groupdict().iteritems():
|
|
||||||
if value is not None:
|
|
||||||
yield lineno, key, value
|
|
||||||
lineno += value.count('\n')
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
raise RuntimeError('%r wanted to resolve '
|
|
||||||
'the token dynamically'
|
|
||||||
' but no group matched'
|
|
||||||
% regex)
|
|
||||||
# normal group
|
|
||||||
else:
|
|
||||||
data = m.group(idx + 1)
|
|
||||||
if data or token not in ignore_if_empty:
|
|
||||||
yield lineno, token, data
|
|
||||||
lineno += data.count('\n')
|
|
||||||
|
|
||||||
# strings as token just are yielded as it.
|
|
||||||
else:
|
|
||||||
data = m.group()
|
|
||||||
# update brace/parentheses balance
|
|
||||||
if tokens == 'operator':
|
|
||||||
if data == '{':
|
|
||||||
balancing_stack.append('}')
|
|
||||||
elif data == '(':
|
|
||||||
balancing_stack.append(')')
|
|
||||||
elif data == '[':
|
|
||||||
balancing_stack.append(']')
|
|
||||||
elif data in ('}', ')', ']'):
|
|
||||||
if not balancing_stack:
|
|
||||||
raise TemplateSyntaxError('unexpected \'%s\'' %
|
|
||||||
data, lineno, name,
|
|
||||||
filename)
|
|
||||||
expected_op = balancing_stack.pop()
|
|
||||||
if expected_op != data:
|
|
||||||
raise TemplateSyntaxError('unexpected \'%s\', '
|
|
||||||
'expected \'%s\'' %
|
|
||||||
(data, expected_op),
|
|
||||||
lineno, name,
|
|
||||||
filename)
|
|
||||||
# yield items
|
|
||||||
if data or tokens not in ignore_if_empty:
|
|
||||||
yield lineno, tokens, data
|
|
||||||
lineno += data.count('\n')
|
|
||||||
|
|
||||||
# fetch new position into new variable so that we can check
|
|
||||||
# if there is a internal parsing error which would result
|
|
||||||
# in an infinite loop
|
|
||||||
pos2 = m.end()
|
|
||||||
|
|
||||||
# handle state changes
|
|
||||||
if new_state is not None:
|
|
||||||
# remove the uppermost state
|
|
||||||
if new_state == '#pop':
|
|
||||||
stack.pop()
|
|
||||||
# resolve the new state by group checking
|
|
||||||
elif new_state == '#bygroup':
|
|
||||||
for key, value in m.groupdict().iteritems():
|
|
||||||
if value is not None:
|
|
||||||
stack.append(key)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
raise RuntimeError('%r wanted to resolve the '
|
|
||||||
'new state dynamically but'
|
|
||||||
' no group matched' %
|
|
||||||
regex)
|
|
||||||
# direct state name given
|
|
||||||
else:
|
|
||||||
stack.append(new_state)
|
|
||||||
statetokens = self.rules[stack[-1]]
|
|
||||||
# we are still at the same position and no stack change.
|
|
||||||
# this means a loop without break condition, avoid that and
|
|
||||||
# raise error
|
|
||||||
elif pos2 == pos:
|
|
||||||
raise RuntimeError('%r yielded empty string without '
|
|
||||||
'stack change' % regex)
|
|
||||||
# publish new function and start again
|
|
||||||
pos = pos2
|
|
||||||
break
|
|
||||||
# if loop terminated without break we havn't found a single match
|
|
||||||
# either we are at the end of the file or we have a problem
|
|
||||||
else:
|
|
||||||
# end of text
|
|
||||||
if pos >= source_length:
|
|
||||||
return
|
|
||||||
# something went wrong
|
|
||||||
raise TemplateSyntaxError('unexpected char %r at %d' %
|
|
||||||
(source[pos], pos), lineno,
|
|
||||||
name, filename)
|
|
||||||
@@ -1,450 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
jinja2.loaders
|
|
||||||
~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Jinja loader classes.
|
|
||||||
|
|
||||||
:copyright: (c) 2010 by the Jinja Team.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import weakref
|
|
||||||
from types import ModuleType
|
|
||||||
from os import path
|
|
||||||
try:
|
|
||||||
from hashlib import sha1
|
|
||||||
except ImportError:
|
|
||||||
from sha import new as sha1
|
|
||||||
from jinja2.exceptions import TemplateNotFound
|
|
||||||
from jinja2.utils import LRUCache, open_if_exists, internalcode
|
|
||||||
|
|
||||||
|
|
||||||
def split_template_path(template):
|
|
||||||
"""Split a path into segments and perform a sanity check. If it detects
|
|
||||||
'..' in the path it will raise a `TemplateNotFound` error.
|
|
||||||
"""
|
|
||||||
pieces = []
|
|
||||||
for piece in template.split('/'):
|
|
||||||
if path.sep in piece \
|
|
||||||
or (path.altsep and path.altsep in piece) or \
|
|
||||||
piece == path.pardir:
|
|
||||||
raise TemplateNotFound(template)
|
|
||||||
elif piece and piece != '.':
|
|
||||||
pieces.append(piece)
|
|
||||||
return pieces
|
|
||||||
|
|
||||||
|
|
||||||
class BaseLoader(object):
|
|
||||||
"""Baseclass for all loaders. Subclass this and override `get_source` to
|
|
||||||
implement a custom loading mechanism. The environment provides a
|
|
||||||
`get_template` method that calls the loader's `load` method to get the
|
|
||||||
:class:`Template` object.
|
|
||||||
|
|
||||||
A very basic example for a loader that looks up templates on the file
|
|
||||||
system could look like this::
|
|
||||||
|
|
||||||
from jinja2 import BaseLoader, TemplateNotFound
|
|
||||||
from os.path import join, exists, getmtime
|
|
||||||
|
|
||||||
class MyLoader(BaseLoader):
|
|
||||||
|
|
||||||
def __init__(self, path):
|
|
||||||
self.path = path
|
|
||||||
|
|
||||||
def get_source(self, environment, template):
|
|
||||||
path = join(self.path, template)
|
|
||||||
if not exists(path):
|
|
||||||
raise TemplateNotFound(template)
|
|
||||||
mtime = getmtime(path)
|
|
||||||
with file(path) as f:
|
|
||||||
source = f.read().decode('utf-8')
|
|
||||||
return source, path, lambda: mtime == getmtime(path)
|
|
||||||
"""
|
|
||||||
|
|
||||||
#: if set to `False` it indicates that the loader cannot provide access
|
|
||||||
#: to the source of templates.
|
|
||||||
#:
|
|
||||||
#: .. versionadded:: 2.4
|
|
||||||
has_source_access = True
|
|
||||||
|
|
||||||
def get_source(self, environment, template):
|
|
||||||
"""Get the template source, filename and reload helper for a template.
|
|
||||||
It's passed the environment and template name and has to return a
|
|
||||||
tuple in the form ``(source, filename, uptodate)`` or raise a
|
|
||||||
`TemplateNotFound` error if it can't locate the template.
|
|
||||||
|
|
||||||
The source part of the returned tuple must be the source of the
|
|
||||||
template as unicode string or a ASCII bytestring. The filename should
|
|
||||||
be the name of the file on the filesystem if it was loaded from there,
|
|
||||||
otherwise `None`. The filename is used by python for the tracebacks
|
|
||||||
if no loader extension is used.
|
|
||||||
|
|
||||||
The last item in the tuple is the `uptodate` function. If auto
|
|
||||||
reloading is enabled it's always called to check if the template
|
|
||||||
changed. No arguments are passed so the function must store the
|
|
||||||
old state somewhere (for example in a closure). If it returns `False`
|
|
||||||
the template will be reloaded.
|
|
||||||
"""
|
|
||||||
if not self.has_source_access:
|
|
||||||
raise RuntimeError('%s cannot provide access to the source' %
|
|
||||||
self.__class__.__name__)
|
|
||||||
raise TemplateNotFound(template)
|
|
||||||
|
|
||||||
def list_templates(self):
|
|
||||||
"""Iterates over all templates. If the loader does not support that
|
|
||||||
it should raise a :exc:`TypeError` which is the default behavior.
|
|
||||||
"""
|
|
||||||
raise TypeError('this loader cannot iterate over all templates')
|
|
||||||
|
|
||||||
@internalcode
|
|
||||||
def load(self, environment, name, globals=None):
|
|
||||||
"""Loads a template. This method looks up the template in the cache
|
|
||||||
or loads one by calling :meth:`get_source`. Subclasses should not
|
|
||||||
override this method as loaders working on collections of other
|
|
||||||
loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`)
|
|
||||||
will not call this method but `get_source` directly.
|
|
||||||
"""
|
|
||||||
code = None
|
|
||||||
if globals is None:
|
|
||||||
globals = {}
|
|
||||||
|
|
||||||
# first we try to get the source for this template together
|
|
||||||
# with the filename and the uptodate function.
|
|
||||||
source, filename, uptodate = self.get_source(environment, name)
|
|
||||||
|
|
||||||
# try to load the code from the bytecode cache if there is a
|
|
||||||
# bytecode cache configured.
|
|
||||||
bcc = environment.bytecode_cache
|
|
||||||
if bcc is not None:
|
|
||||||
bucket = bcc.get_bucket(environment, name, filename, source)
|
|
||||||
code = bucket.code
|
|
||||||
|
|
||||||
# if we don't have code so far (not cached, no longer up to
|
|
||||||
# date) etc. we compile the template
|
|
||||||
if code is None:
|
|
||||||
code = environment.compile(source, name, filename)
|
|
||||||
|
|
||||||
# if the bytecode cache is available and the bucket doesn't
|
|
||||||
# have a code so far, we give the bucket the new code and put
|
|
||||||
# it back to the bytecode cache.
|
|
||||||
if bcc is not None and bucket.code is None:
|
|
||||||
bucket.code = code
|
|
||||||
bcc.set_bucket(bucket)
|
|
||||||
|
|
||||||
return environment.template_class.from_code(environment, code,
|
|
||||||
globals, uptodate)
|
|
||||||
|
|
||||||
|
|
||||||
class FileSystemLoader(BaseLoader):
|
|
||||||
"""Loads templates from the file system. This loader can find templates
|
|
||||||
in folders on the file system and is the preferred way to load them.
|
|
||||||
|
|
||||||
The loader takes the path to the templates as string, or if multiple
|
|
||||||
locations are wanted a list of them which is then looked up in the
|
|
||||||
given order:
|
|
||||||
|
|
||||||
>>> loader = FileSystemLoader('/path/to/templates')
|
|
||||||
>>> loader = FileSystemLoader(['/path/to/templates', '/other/path'])
|
|
||||||
|
|
||||||
Per default the template encoding is ``'utf-8'`` which can be changed
|
|
||||||
by setting the `encoding` parameter to something else.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, searchpath, encoding='utf-8'):
|
|
||||||
if isinstance(searchpath, basestring):
|
|
||||||
searchpath = [searchpath]
|
|
||||||
self.searchpath = list(searchpath)
|
|
||||||
self.encoding = encoding
|
|
||||||
|
|
||||||
def get_source(self, environment, template):
|
|
||||||
pieces = split_template_path(template)
|
|
||||||
for searchpath in self.searchpath:
|
|
||||||
filename = path.join(searchpath, *pieces)
|
|
||||||
f = open_if_exists(filename)
|
|
||||||
if f is None:
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
contents = f.read().decode(self.encoding)
|
|
||||||
finally:
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
mtime = path.getmtime(filename)
|
|
||||||
def uptodate():
|
|
||||||
try:
|
|
||||||
return path.getmtime(filename) == mtime
|
|
||||||
except OSError:
|
|
||||||
return False
|
|
||||||
return contents, filename, uptodate
|
|
||||||
raise TemplateNotFound(template)
|
|
||||||
|
|
||||||
def list_templates(self):
|
|
||||||
found = set()
|
|
||||||
for searchpath in self.searchpath:
|
|
||||||
for dirpath, dirnames, filenames in os.walk(searchpath):
|
|
||||||
for filename in filenames:
|
|
||||||
template = os.path.join(dirpath, filename) \
|
|
||||||
[len(searchpath):].strip(os.path.sep) \
|
|
||||||
.replace(os.path.sep, '/')
|
|
||||||
if template[:2] == './':
|
|
||||||
template = template[2:]
|
|
||||||
if template not in found:
|
|
||||||
found.add(template)
|
|
||||||
return sorted(found)
|
|
||||||
|
|
||||||
|
|
||||||
class PackageLoader(BaseLoader):
|
|
||||||
"""Load templates from python eggs or packages. It is constructed with
|
|
||||||
the name of the python package and the path to the templates in that
|
|
||||||
package::
|
|
||||||
|
|
||||||
loader = PackageLoader('mypackage', 'views')
|
|
||||||
|
|
||||||
If the package path is not given, ``'templates'`` is assumed.
|
|
||||||
|
|
||||||
Per default the template encoding is ``'utf-8'`` which can be changed
|
|
||||||
by setting the `encoding` parameter to something else. Due to the nature
|
|
||||||
of eggs it's only possible to reload templates if the package was loaded
|
|
||||||
from the file system and not a zip file.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, package_name, package_path='templates',
|
|
||||||
encoding='utf-8'):
|
|
||||||
from pkg_resources import DefaultProvider, ResourceManager, \
|
|
||||||
get_provider
|
|
||||||
provider = get_provider(package_name)
|
|
||||||
self.encoding = encoding
|
|
||||||
self.manager = ResourceManager()
|
|
||||||
self.filesystem_bound = isinstance(provider, DefaultProvider)
|
|
||||||
self.provider = provider
|
|
||||||
self.package_path = package_path
|
|
||||||
|
|
||||||
def get_source(self, environment, template):
|
|
||||||
pieces = split_template_path(template)
|
|
||||||
p = '/'.join((self.package_path,) + tuple(pieces))
|
|
||||||
if not self.provider.has_resource(p):
|
|
||||||
raise TemplateNotFound(template)
|
|
||||||
|
|
||||||
filename = uptodate = None
|
|
||||||
if self.filesystem_bound:
|
|
||||||
filename = self.provider.get_resource_filename(self.manager, p)
|
|
||||||
mtime = path.getmtime(filename)
|
|
||||||
def uptodate():
|
|
||||||
try:
|
|
||||||
return path.getmtime(filename) == mtime
|
|
||||||
except OSError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
source = self.provider.get_resource_string(self.manager, p)
|
|
||||||
return source.decode(self.encoding), filename, uptodate
|
|
||||||
|
|
||||||
def list_templates(self):
|
|
||||||
path = self.package_path
|
|
||||||
if path[:2] == './':
|
|
||||||
path = path[2:]
|
|
||||||
elif path == '.':
|
|
||||||
path = ''
|
|
||||||
offset = len(path)
|
|
||||||
results = []
|
|
||||||
def _walk(path):
|
|
||||||
for filename in self.provider.resource_listdir(path):
|
|
||||||
fullname = path + '/' + filename
|
|
||||||
if self.provider.resource_isdir(fullname):
|
|
||||||
_walk(fullname)
|
|
||||||
else:
|
|
||||||
results.append(fullname[offset:].lstrip('/'))
|
|
||||||
_walk(path)
|
|
||||||
results.sort()
|
|
||||||
return results
|
|
||||||
|
|
||||||
|
|
||||||
class DictLoader(BaseLoader):
|
|
||||||
"""Loads a template from a python dict. It's passed a dict of unicode
|
|
||||||
strings bound to template names. This loader is useful for unittesting:
|
|
||||||
|
|
||||||
>>> loader = DictLoader({'index.html': 'source here'})
|
|
||||||
|
|
||||||
Because auto reloading is rarely useful this is disabled per default.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, mapping):
|
|
||||||
self.mapping = mapping
|
|
||||||
|
|
||||||
def get_source(self, environment, template):
|
|
||||||
if template in self.mapping:
|
|
||||||
source = self.mapping[template]
|
|
||||||
return source, None, lambda: source != self.mapping.get(template)
|
|
||||||
raise TemplateNotFound(template)
|
|
||||||
|
|
||||||
def list_templates(self):
|
|
||||||
return sorted(self.mapping)
|
|
||||||
|
|
||||||
|
|
||||||
class FunctionLoader(BaseLoader):
|
|
||||||
"""A loader that is passed a function which does the loading. The
|
|
||||||
function becomes the name of the template passed and has to return either
|
|
||||||
an unicode string with the template source, a tuple in the form ``(source,
|
|
||||||
filename, uptodatefunc)`` or `None` if the template does not exist.
|
|
||||||
|
|
||||||
>>> def load_template(name):
|
|
||||||
... if name == 'index.html':
|
|
||||||
... return '...'
|
|
||||||
...
|
|
||||||
>>> loader = FunctionLoader(load_template)
|
|
||||||
|
|
||||||
The `uptodatefunc` is a function that is called if autoreload is enabled
|
|
||||||
and has to return `True` if the template is still up to date. For more
|
|
||||||
details have a look at :meth:`BaseLoader.get_source` which has the same
|
|
||||||
return value.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, load_func):
|
|
||||||
self.load_func = load_func
|
|
||||||
|
|
||||||
def get_source(self, environment, template):
|
|
||||||
rv = self.load_func(template)
|
|
||||||
if rv is None:
|
|
||||||
raise TemplateNotFound(template)
|
|
||||||
elif isinstance(rv, basestring):
|
|
||||||
return rv, None, None
|
|
||||||
return rv
|
|
||||||
|
|
||||||
|
|
||||||
class PrefixLoader(BaseLoader):
|
|
||||||
"""A loader that is passed a dict of loaders where each loader is bound
|
|
||||||
to a prefix. The prefix is delimited from the template by a slash per
|
|
||||||
default, which can be changed by setting the `delimiter` argument to
|
|
||||||
something else::
|
|
||||||
|
|
||||||
loader = PrefixLoader({
|
|
||||||
'app1': PackageLoader('mypackage.app1'),
|
|
||||||
'app2': PackageLoader('mypackage.app2')
|
|
||||||
})
|
|
||||||
|
|
||||||
By loading ``'app1/index.html'`` the file from the app1 package is loaded,
|
|
||||||
by loading ``'app2/index.html'`` the file from the second.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, mapping, delimiter='/'):
|
|
||||||
self.mapping = mapping
|
|
||||||
self.delimiter = delimiter
|
|
||||||
|
|
||||||
def get_source(self, environment, template):
|
|
||||||
try:
|
|
||||||
prefix, name = template.split(self.delimiter, 1)
|
|
||||||
loader = self.mapping[prefix]
|
|
||||||
except (ValueError, KeyError):
|
|
||||||
raise TemplateNotFound(template)
|
|
||||||
try:
|
|
||||||
return loader.get_source(environment, name)
|
|
||||||
except TemplateNotFound:
|
|
||||||
# re-raise the exception with the correct fileame here.
|
|
||||||
# (the one that includes the prefix)
|
|
||||||
raise TemplateNotFound(template)
|
|
||||||
|
|
||||||
def list_templates(self):
|
|
||||||
result = []
|
|
||||||
for prefix, loader in self.mapping.iteritems():
|
|
||||||
for template in loader.list_templates():
|
|
||||||
result.append(prefix + self.delimiter + template)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
class ChoiceLoader(BaseLoader):
|
|
||||||
"""This loader works like the `PrefixLoader` just that no prefix is
|
|
||||||
specified. If a template could not be found by one loader the next one
|
|
||||||
is tried.
|
|
||||||
|
|
||||||
>>> loader = ChoiceLoader([
|
|
||||||
... FileSystemLoader('/path/to/user/templates'),
|
|
||||||
... FileSystemLoader('/path/to/system/templates')
|
|
||||||
... ])
|
|
||||||
|
|
||||||
This is useful if you want to allow users to override builtin templates
|
|
||||||
from a different location.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, loaders):
|
|
||||||
self.loaders = loaders
|
|
||||||
|
|
||||||
def get_source(self, environment, template):
|
|
||||||
for loader in self.loaders:
|
|
||||||
try:
|
|
||||||
return loader.get_source(environment, template)
|
|
||||||
except TemplateNotFound:
|
|
||||||
pass
|
|
||||||
raise TemplateNotFound(template)
|
|
||||||
|
|
||||||
def list_templates(self):
|
|
||||||
found = set()
|
|
||||||
for loader in self.loaders:
|
|
||||||
found.update(loader.list_templates())
|
|
||||||
return sorted(found)
|
|
||||||
|
|
||||||
|
|
||||||
class _TemplateModule(ModuleType):
|
|
||||||
"""Like a normal module but with support for weak references"""
|
|
||||||
|
|
||||||
|
|
||||||
class ModuleLoader(BaseLoader):
|
|
||||||
"""This loader loads templates from precompiled templates.
|
|
||||||
|
|
||||||
Example usage:
|
|
||||||
|
|
||||||
>>> loader = ChoiceLoader([
|
|
||||||
... ModuleLoader('/path/to/compiled/templates'),
|
|
||||||
... FileSystemLoader('/path/to/templates')
|
|
||||||
... ])
|
|
||||||
|
|
||||||
Templates can be precompiled with :meth:`Environment.compile_templates`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
has_source_access = False
|
|
||||||
|
|
||||||
def __init__(self, path):
|
|
||||||
package_name = '_jinja2_module_templates_%x' % id(self)
|
|
||||||
|
|
||||||
# create a fake module that looks for the templates in the
|
|
||||||
# path given.
|
|
||||||
mod = _TemplateModule(package_name)
|
|
||||||
if isinstance(path, basestring):
|
|
||||||
path = [path]
|
|
||||||
else:
|
|
||||||
path = list(path)
|
|
||||||
mod.__path__ = path
|
|
||||||
|
|
||||||
sys.modules[package_name] = weakref.proxy(mod,
|
|
||||||
lambda x: sys.modules.pop(package_name, None))
|
|
||||||
|
|
||||||
# the only strong reference, the sys.modules entry is weak
|
|
||||||
# so that the garbage collector can remove it once the
|
|
||||||
# loader that created it goes out of business.
|
|
||||||
self.module = mod
|
|
||||||
self.package_name = package_name
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_template_key(name):
|
|
||||||
return 'tmpl_' + sha1(name.encode('utf-8')).hexdigest()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_module_filename(name):
|
|
||||||
return ModuleLoader.get_template_key(name) + '.py'
|
|
||||||
|
|
||||||
@internalcode
|
|
||||||
def load(self, environment, name, globals=None):
|
|
||||||
key = self.get_template_key(name)
|
|
||||||
module = '%s.%s' % (self.package_name, key)
|
|
||||||
mod = getattr(self.module, module, None)
|
|
||||||
if mod is None:
|
|
||||||
try:
|
|
||||||
mod = __import__(module, None, None, ['root'])
|
|
||||||
except ImportError:
|
|
||||||
raise TemplateNotFound(name)
|
|
||||||
|
|
||||||
# remove the entry from sys.modules, we only want the attribute
|
|
||||||
# on the module object we have stored on the loader.
|
|
||||||
sys.modules.pop(module, None)
|
|
||||||
|
|
||||||
return environment.template_class.from_module_dict(
|
|
||||||
environment, mod.__dict__, globals)
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
jinja2.meta
|
|
||||||
~~~~~~~~~~~
|
|
||||||
|
|
||||||
This module implements various functions that exposes information about
|
|
||||||
templates that might be interesting for various kinds of applications.
|
|
||||||
|
|
||||||
:copyright: (c) 2010 by the Jinja Team, see AUTHORS for more details.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
from jinja2 import nodes
|
|
||||||
from jinja2.compiler import CodeGenerator
|
|
||||||
|
|
||||||
|
|
||||||
class TrackingCodeGenerator(CodeGenerator):
|
|
||||||
"""We abuse the code generator for introspection."""
|
|
||||||
|
|
||||||
def __init__(self, environment):
|
|
||||||
CodeGenerator.__init__(self, environment, '<introspection>',
|
|
||||||
'<introspection>')
|
|
||||||
self.undeclared_identifiers = set()
|
|
||||||
|
|
||||||
def write(self, x):
|
|
||||||
"""Don't write."""
|
|
||||||
|
|
||||||
def pull_locals(self, frame):
|
|
||||||
"""Remember all undeclared identifiers."""
|
|
||||||
self.undeclared_identifiers.update(frame.identifiers.undeclared)
|
|
||||||
|
|
||||||
|
|
||||||
def find_undeclared_variables(ast):
|
|
||||||
"""Returns a set of all variables in the AST that will be looked up from
|
|
||||||
the context at runtime. Because at compile time it's not known which
|
|
||||||
variables will be used depending on the path the execution takes at
|
|
||||||
runtime, all variables are returned.
|
|
||||||
|
|
||||||
>>> from jinja2 import Environment, meta
|
|
||||||
>>> env = Environment()
|
|
||||||
>>> ast = env.parse('{% set foo = 42 %}{{ bar + foo }}')
|
|
||||||
>>> meta.find_undeclared_variables(ast)
|
|
||||||
set(['bar'])
|
|
||||||
|
|
||||||
.. admonition:: Implementation
|
|
||||||
|
|
||||||
Internally the code generator is used for finding undeclared variables.
|
|
||||||
This is good to know because the code generator might raise a
|
|
||||||
:exc:`TemplateAssertionError` during compilation and as a matter of
|
|
||||||
fact this function can currently raise that exception as well.
|
|
||||||
"""
|
|
||||||
codegen = TrackingCodeGenerator(ast.environment)
|
|
||||||
codegen.visit(ast)
|
|
||||||
return codegen.undeclared_identifiers
|
|
||||||
|
|
||||||
|
|
||||||
def find_referenced_templates(ast):
|
|
||||||
"""Finds all the referenced templates from the AST. This will return an
|
|
||||||
iterator over all the hardcoded template extensions, inclusions and
|
|
||||||
imports. If dynamic inheritance or inclusion is used, `None` will be
|
|
||||||
yielded.
|
|
||||||
|
|
||||||
>>> from jinja2 import Environment, meta
|
|
||||||
>>> env = Environment()
|
|
||||||
>>> ast = env.parse('{% extends "layout.html" %}{% include helper %}')
|
|
||||||
>>> list(meta.find_referenced_templates(ast))
|
|
||||||
['layout.html', None]
|
|
||||||
|
|
||||||
This function is useful for dependency tracking. For example if you want
|
|
||||||
to rebuild parts of the website after a layout template has changed.
|
|
||||||
"""
|
|
||||||
for node in ast.find_all((nodes.Extends, nodes.FromImport, nodes.Import,
|
|
||||||
nodes.Include)):
|
|
||||||
if not isinstance(node.template, nodes.Const):
|
|
||||||
# a tuple with some non consts in there
|
|
||||||
if isinstance(node.template, (nodes.Tuple, nodes.List)):
|
|
||||||
for template_name in node.template.items:
|
|
||||||
# something const, only yield the strings and ignore
|
|
||||||
# non-string consts that really just make no sense
|
|
||||||
if isinstance(template_name, nodes.Const):
|
|
||||||
if isinstance(template_name.value, basestring):
|
|
||||||
yield template_name.value
|
|
||||||
# something dynamic in there
|
|
||||||
else:
|
|
||||||
yield None
|
|
||||||
# something dynamic we don't know about here
|
|
||||||
else:
|
|
||||||
yield None
|
|
||||||
continue
|
|
||||||
# constant is a basestring, direct template name
|
|
||||||
if isinstance(node.template.value, basestring):
|
|
||||||
yield node.template.value
|
|
||||||
# a tuple or list (latter *should* not happen) made of consts,
|
|
||||||
# yield the consts that are strings. We could warn here for
|
|
||||||
# non string values
|
|
||||||
elif isinstance(node, nodes.Include) and \
|
|
||||||
isinstance(node.template.value, (tuple, list)):
|
|
||||||
for template_name in node.template.value:
|
|
||||||
if isinstance(template_name, basestring):
|
|
||||||
yield template_name
|
|
||||||
# something else we don't care about, we could warn here
|
|
||||||
else:
|
|
||||||
yield None
|
|
||||||
@@ -1,910 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
jinja2.nodes
|
|
||||||
~~~~~~~~~~~~
|
|
||||||
|
|
||||||
This module implements additional nodes derived from the ast base node.
|
|
||||||
|
|
||||||
It also provides some node tree helper functions like `in_lineno` and
|
|
||||||
`get_nodes` used by the parser and translator in order to normalize
|
|
||||||
python and jinja nodes.
|
|
||||||
|
|
||||||
:copyright: (c) 2010 by the Jinja Team.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
import operator
|
|
||||||
from itertools import chain, izip
|
|
||||||
from collections import deque
|
|
||||||
from jinja2.utils import Markup, MethodType, FunctionType
|
|
||||||
|
|
||||||
|
|
||||||
#: the types we support for context functions
|
|
||||||
_context_function_types = (FunctionType, MethodType)
|
|
||||||
|
|
||||||
|
|
||||||
_binop_to_func = {
|
|
||||||
'*': operator.mul,
|
|
||||||
'/': operator.truediv,
|
|
||||||
'//': operator.floordiv,
|
|
||||||
'**': operator.pow,
|
|
||||||
'%': operator.mod,
|
|
||||||
'+': operator.add,
|
|
||||||
'-': operator.sub
|
|
||||||
}
|
|
||||||
|
|
||||||
_uaop_to_func = {
|
|
||||||
'not': operator.not_,
|
|
||||||
'+': operator.pos,
|
|
||||||
'-': operator.neg
|
|
||||||
}
|
|
||||||
|
|
||||||
_cmpop_to_func = {
|
|
||||||
'eq': operator.eq,
|
|
||||||
'ne': operator.ne,
|
|
||||||
'gt': operator.gt,
|
|
||||||
'gteq': operator.ge,
|
|
||||||
'lt': operator.lt,
|
|
||||||
'lteq': operator.le,
|
|
||||||
'in': lambda a, b: a in b,
|
|
||||||
'notin': lambda a, b: a not in b
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Impossible(Exception):
|
|
||||||
"""Raised if the node could not perform a requested action."""
|
|
||||||
|
|
||||||
|
|
||||||
class NodeType(type):
|
|
||||||
"""A metaclass for nodes that handles the field and attribute
|
|
||||||
inheritance. fields and attributes from the parent class are
|
|
||||||
automatically forwarded to the child."""
|
|
||||||
|
|
||||||
def __new__(cls, name, bases, d):
|
|
||||||
for attr in 'fields', 'attributes':
|
|
||||||
storage = []
|
|
||||||
storage.extend(getattr(bases[0], attr, ()))
|
|
||||||
storage.extend(d.get(attr, ()))
|
|
||||||
assert len(bases) == 1, 'multiple inheritance not allowed'
|
|
||||||
assert len(storage) == len(set(storage)), 'layout conflict'
|
|
||||||
d[attr] = tuple(storage)
|
|
||||||
d.setdefault('abstract', False)
|
|
||||||
return type.__new__(cls, name, bases, d)
|
|
||||||
|
|
||||||
|
|
||||||
class EvalContext(object):
|
|
||||||
"""Holds evaluation time information. Custom attributes can be attached
|
|
||||||
to it in extensions.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, environment, template_name=None):
|
|
||||||
self.environment = environment
|
|
||||||
if callable(environment.autoescape):
|
|
||||||
self.autoescape = environment.autoescape(template_name)
|
|
||||||
else:
|
|
||||||
self.autoescape = environment.autoescape
|
|
||||||
self.volatile = False
|
|
||||||
|
|
||||||
def save(self):
|
|
||||||
return self.__dict__.copy()
|
|
||||||
|
|
||||||
def revert(self, old):
|
|
||||||
self.__dict__.clear()
|
|
||||||
self.__dict__.update(old)
|
|
||||||
|
|
||||||
|
|
||||||
def get_eval_context(node, ctx):
|
|
||||||
if ctx is None:
|
|
||||||
if node.environment is None:
|
|
||||||
raise RuntimeError('if no eval context is passed, the '
|
|
||||||
'node must have an attached '
|
|
||||||
'environment.')
|
|
||||||
return EvalContext(node.environment)
|
|
||||||
return ctx
|
|
||||||
|
|
||||||
|
|
||||||
class Node(object):
|
|
||||||
"""Baseclass for all Jinja2 nodes. There are a number of nodes available
|
|
||||||
of different types. There are three major types:
|
|
||||||
|
|
||||||
- :class:`Stmt`: statements
|
|
||||||
- :class:`Expr`: expressions
|
|
||||||
- :class:`Helper`: helper nodes
|
|
||||||
- :class:`Template`: the outermost wrapper node
|
|
||||||
|
|
||||||
All nodes have fields and attributes. Fields may be other nodes, lists,
|
|
||||||
or arbitrary values. Fields are passed to the constructor as regular
|
|
||||||
positional arguments, attributes as keyword arguments. Each node has
|
|
||||||
two attributes: `lineno` (the line number of the node) and `environment`.
|
|
||||||
The `environment` attribute is set at the end of the parsing process for
|
|
||||||
all nodes automatically.
|
|
||||||
"""
|
|
||||||
__metaclass__ = NodeType
|
|
||||||
fields = ()
|
|
||||||
attributes = ('lineno', 'environment')
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
def __init__(self, *fields, **attributes):
|
|
||||||
if self.abstract:
|
|
||||||
raise TypeError('abstract nodes are not instanciable')
|
|
||||||
if fields:
|
|
||||||
if len(fields) != len(self.fields):
|
|
||||||
if not self.fields:
|
|
||||||
raise TypeError('%r takes 0 arguments' %
|
|
||||||
self.__class__.__name__)
|
|
||||||
raise TypeError('%r takes 0 or %d argument%s' % (
|
|
||||||
self.__class__.__name__,
|
|
||||||
len(self.fields),
|
|
||||||
len(self.fields) != 1 and 's' or ''
|
|
||||||
))
|
|
||||||
for name, arg in izip(self.fields, fields):
|
|
||||||
setattr(self, name, arg)
|
|
||||||
for attr in self.attributes:
|
|
||||||
setattr(self, attr, attributes.pop(attr, None))
|
|
||||||
if attributes:
|
|
||||||
raise TypeError('unknown attribute %r' %
|
|
||||||
iter(attributes).next())
|
|
||||||
|
|
||||||
def iter_fields(self, exclude=None, only=None):
|
|
||||||
"""This method iterates over all fields that are defined and yields
|
|
||||||
``(key, value)`` tuples. Per default all fields are returned, but
|
|
||||||
it's possible to limit that to some fields by providing the `only`
|
|
||||||
parameter or to exclude some using the `exclude` parameter. Both
|
|
||||||
should be sets or tuples of field names.
|
|
||||||
"""
|
|
||||||
for name in self.fields:
|
|
||||||
if (exclude is only is None) or \
|
|
||||||
(exclude is not None and name not in exclude) or \
|
|
||||||
(only is not None and name in only):
|
|
||||||
try:
|
|
||||||
yield name, getattr(self, name)
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def iter_child_nodes(self, exclude=None, only=None):
|
|
||||||
"""Iterates over all direct child nodes of the node. This iterates
|
|
||||||
over all fields and yields the values of they are nodes. If the value
|
|
||||||
of a field is a list all the nodes in that list are returned.
|
|
||||||
"""
|
|
||||||
for field, item in self.iter_fields(exclude, only):
|
|
||||||
if isinstance(item, list):
|
|
||||||
for n in item:
|
|
||||||
if isinstance(n, Node):
|
|
||||||
yield n
|
|
||||||
elif isinstance(item, Node):
|
|
||||||
yield item
|
|
||||||
|
|
||||||
def find(self, node_type):
|
|
||||||
"""Find the first node of a given type. If no such node exists the
|
|
||||||
return value is `None`.
|
|
||||||
"""
|
|
||||||
for result in self.find_all(node_type):
|
|
||||||
return result
|
|
||||||
|
|
||||||
def find_all(self, node_type):
|
|
||||||
"""Find all the nodes of a given type. If the type is a tuple,
|
|
||||||
the check is performed for any of the tuple items.
|
|
||||||
"""
|
|
||||||
for child in self.iter_child_nodes():
|
|
||||||
if isinstance(child, node_type):
|
|
||||||
yield child
|
|
||||||
for result in child.find_all(node_type):
|
|
||||||
yield result
|
|
||||||
|
|
||||||
def set_ctx(self, ctx):
|
|
||||||
"""Reset the context of a node and all child nodes. Per default the
|
|
||||||
parser will all generate nodes that have a 'load' context as it's the
|
|
||||||
most common one. This method is used in the parser to set assignment
|
|
||||||
targets and other nodes to a store context.
|
|
||||||
"""
|
|
||||||
todo = deque([self])
|
|
||||||
while todo:
|
|
||||||
node = todo.popleft()
|
|
||||||
if 'ctx' in node.fields:
|
|
||||||
node.ctx = ctx
|
|
||||||
todo.extend(node.iter_child_nodes())
|
|
||||||
return self
|
|
||||||
|
|
||||||
def set_lineno(self, lineno, override=False):
|
|
||||||
"""Set the line numbers of the node and children."""
|
|
||||||
todo = deque([self])
|
|
||||||
while todo:
|
|
||||||
node = todo.popleft()
|
|
||||||
if 'lineno' in node.attributes:
|
|
||||||
if node.lineno is None or override:
|
|
||||||
node.lineno = lineno
|
|
||||||
todo.extend(node.iter_child_nodes())
|
|
||||||
return self
|
|
||||||
|
|
||||||
def set_environment(self, environment):
|
|
||||||
"""Set the environment for all nodes."""
|
|
||||||
todo = deque([self])
|
|
||||||
while todo:
|
|
||||||
node = todo.popleft()
|
|
||||||
node.environment = environment
|
|
||||||
todo.extend(node.iter_child_nodes())
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return type(self) is type(other) and \
|
|
||||||
tuple(self.iter_fields()) == tuple(other.iter_fields())
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return not self.__eq__(other)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '%s(%s)' % (
|
|
||||||
self.__class__.__name__,
|
|
||||||
', '.join('%s=%r' % (arg, getattr(self, arg, None)) for
|
|
||||||
arg in self.fields)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Stmt(Node):
|
|
||||||
"""Base node for all statements."""
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
|
|
||||||
class Helper(Node):
|
|
||||||
"""Nodes that exist in a specific context only."""
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
|
|
||||||
class Template(Node):
|
|
||||||
"""Node that represents a template. This must be the outermost node that
|
|
||||||
is passed to the compiler.
|
|
||||||
"""
|
|
||||||
fields = ('body',)
|
|
||||||
|
|
||||||
|
|
||||||
class Output(Stmt):
|
|
||||||
"""A node that holds multiple expressions which are then printed out.
|
|
||||||
This is used both for the `print` statement and the regular template data.
|
|
||||||
"""
|
|
||||||
fields = ('nodes',)
|
|
||||||
|
|
||||||
|
|
||||||
class Extends(Stmt):
|
|
||||||
"""Represents an extends statement."""
|
|
||||||
fields = ('template',)
|
|
||||||
|
|
||||||
|
|
||||||
class For(Stmt):
|
|
||||||
"""The for loop. `target` is the target for the iteration (usually a
|
|
||||||
:class:`Name` or :class:`Tuple`), `iter` the iterable. `body` is a list
|
|
||||||
of nodes that are used as loop-body, and `else_` a list of nodes for the
|
|
||||||
`else` block. If no else node exists it has to be an empty list.
|
|
||||||
|
|
||||||
For filtered nodes an expression can be stored as `test`, otherwise `None`.
|
|
||||||
"""
|
|
||||||
fields = ('target', 'iter', 'body', 'else_', 'test', 'recursive')
|
|
||||||
|
|
||||||
|
|
||||||
class If(Stmt):
|
|
||||||
"""If `test` is true, `body` is rendered, else `else_`."""
|
|
||||||
fields = ('test', 'body', 'else_')
|
|
||||||
|
|
||||||
|
|
||||||
class Macro(Stmt):
|
|
||||||
"""A macro definition. `name` is the name of the macro, `args` a list of
|
|
||||||
arguments and `defaults` a list of defaults if there are any. `body` is
|
|
||||||
a list of nodes for the macro body.
|
|
||||||
"""
|
|
||||||
fields = ('name', 'args', 'defaults', 'body')
|
|
||||||
|
|
||||||
|
|
||||||
class CallBlock(Stmt):
|
|
||||||
"""Like a macro without a name but a call instead. `call` is called with
|
|
||||||
the unnamed macro as `caller` argument this node holds.
|
|
||||||
"""
|
|
||||||
fields = ('call', 'args', 'defaults', 'body')
|
|
||||||
|
|
||||||
|
|
||||||
class FilterBlock(Stmt):
|
|
||||||
"""Node for filter sections."""
|
|
||||||
fields = ('body', 'filter')
|
|
||||||
|
|
||||||
|
|
||||||
class Block(Stmt):
|
|
||||||
"""A node that represents a block."""
|
|
||||||
fields = ('name', 'body', 'scoped')
|
|
||||||
|
|
||||||
|
|
||||||
class Include(Stmt):
|
|
||||||
"""A node that represents the include tag."""
|
|
||||||
fields = ('template', 'with_context', 'ignore_missing')
|
|
||||||
|
|
||||||
|
|
||||||
class Import(Stmt):
|
|
||||||
"""A node that represents the import tag."""
|
|
||||||
fields = ('template', 'target', 'with_context')
|
|
||||||
|
|
||||||
|
|
||||||
class FromImport(Stmt):
|
|
||||||
"""A node that represents the from import tag. It's important to not
|
|
||||||
pass unsafe names to the name attribute. The compiler translates the
|
|
||||||
attribute lookups directly into getattr calls and does *not* use the
|
|
||||||
subscript callback of the interface. As exported variables may not
|
|
||||||
start with double underscores (which the parser asserts) this is not a
|
|
||||||
problem for regular Jinja code, but if this node is used in an extension
|
|
||||||
extra care must be taken.
|
|
||||||
|
|
||||||
The list of names may contain tuples if aliases are wanted.
|
|
||||||
"""
|
|
||||||
fields = ('template', 'names', 'with_context')
|
|
||||||
|
|
||||||
|
|
||||||
class ExprStmt(Stmt):
|
|
||||||
"""A statement that evaluates an expression and discards the result."""
|
|
||||||
fields = ('node',)
|
|
||||||
|
|
||||||
|
|
||||||
class Assign(Stmt):
|
|
||||||
"""Assigns an expression to a target."""
|
|
||||||
fields = ('target', 'node')
|
|
||||||
|
|
||||||
|
|
||||||
class Expr(Node):
|
|
||||||
"""Baseclass for all expressions."""
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
def as_const(self, eval_ctx=None):
|
|
||||||
"""Return the value of the expression as constant or raise
|
|
||||||
:exc:`Impossible` if this was not possible.
|
|
||||||
|
|
||||||
An :class:`EvalContext` can be provided, if none is given
|
|
||||||
a default context is created which requires the nodes to have
|
|
||||||
an attached environment.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.4
|
|
||||||
the `eval_ctx` parameter was added.
|
|
||||||
"""
|
|
||||||
raise Impossible()
|
|
||||||
|
|
||||||
def can_assign(self):
|
|
||||||
"""Check if it's possible to assign something to this node."""
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class BinExpr(Expr):
|
|
||||||
"""Baseclass for all binary expressions."""
|
|
||||||
fields = ('left', 'right')
|
|
||||||
operator = None
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
def as_const(self, eval_ctx=None):
|
|
||||||
eval_ctx = get_eval_context(self, eval_ctx)
|
|
||||||
# intercepted operators cannot be folded at compile time
|
|
||||||
if self.environment.sandboxed and \
|
|
||||||
self.operator in self.environment.intercepted_binops:
|
|
||||||
raise Impossible()
|
|
||||||
f = _binop_to_func[self.operator]
|
|
||||||
try:
|
|
||||||
return f(self.left.as_const(eval_ctx), self.right.as_const(eval_ctx))
|
|
||||||
except Exception:
|
|
||||||
raise Impossible()
|
|
||||||
|
|
||||||
|
|
||||||
class UnaryExpr(Expr):
|
|
||||||
"""Baseclass for all unary expressions."""
|
|
||||||
fields = ('node',)
|
|
||||||
operator = None
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
def as_const(self, eval_ctx=None):
|
|
||||||
eval_ctx = get_eval_context(self, eval_ctx)
|
|
||||||
# intercepted operators cannot be folded at compile time
|
|
||||||
if self.environment.sandboxed and \
|
|
||||||
self.operator in self.environment.intercepted_unops:
|
|
||||||
raise Impossible()
|
|
||||||
f = _uaop_to_func[self.operator]
|
|
||||||
try:
|
|
||||||
return f(self.node.as_const(eval_ctx))
|
|
||||||
except Exception:
|
|
||||||
raise Impossible()
|
|
||||||
|
|
||||||
|
|
||||||
class Name(Expr):
|
|
||||||
"""Looks up a name or stores a value in a name.
|
|
||||||
The `ctx` of the node can be one of the following values:
|
|
||||||
|
|
||||||
- `store`: store a value in the name
|
|
||||||
- `load`: load that name
|
|
||||||
- `param`: like `store` but if the name was defined as function parameter.
|
|
||||||
"""
|
|
||||||
fields = ('name', 'ctx')
|
|
||||||
|
|
||||||
def can_assign(self):
|
|
||||||
return self.name not in ('true', 'false', 'none',
|
|
||||||
'True', 'False', 'None')
|
|
||||||
|
|
||||||
|
|
||||||
class Literal(Expr):
|
|
||||||
"""Baseclass for literals."""
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
|
|
||||||
class Const(Literal):
|
|
||||||
"""All constant values. The parser will return this node for simple
|
|
||||||
constants such as ``42`` or ``"foo"`` but it can be used to store more
|
|
||||||
complex values such as lists too. Only constants with a safe
|
|
||||||
representation (objects where ``eval(repr(x)) == x`` is true).
|
|
||||||
"""
|
|
||||||
fields = ('value',)
|
|
||||||
|
|
||||||
def as_const(self, eval_ctx=None):
|
|
||||||
return self.value
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_untrusted(cls, value, lineno=None, environment=None):
|
|
||||||
"""Return a const object if the value is representable as
|
|
||||||
constant value in the generated code, otherwise it will raise
|
|
||||||
an `Impossible` exception.
|
|
||||||
"""
|
|
||||||
from compiler import has_safe_repr
|
|
||||||
if not has_safe_repr(value):
|
|
||||||
raise Impossible()
|
|
||||||
return cls(value, lineno=lineno, environment=environment)
|
|
||||||
|
|
||||||
|
|
||||||
class TemplateData(Literal):
|
|
||||||
"""A constant template string."""
|
|
||||||
fields = ('data',)
|
|
||||||
|
|
||||||
def as_const(self, eval_ctx=None):
|
|
||||||
eval_ctx = get_eval_context(self, eval_ctx)
|
|
||||||
if eval_ctx.volatile:
|
|
||||||
raise Impossible()
|
|
||||||
if eval_ctx.autoescape:
|
|
||||||
return Markup(self.data)
|
|
||||||
return self.data
|
|
||||||
|
|
||||||
|
|
||||||
class Tuple(Literal):
|
|
||||||
"""For loop unpacking and some other things like multiple arguments
|
|
||||||
for subscripts. Like for :class:`Name` `ctx` specifies if the tuple
|
|
||||||
is used for loading the names or storing.
|
|
||||||
"""
|
|
||||||
fields = ('items', 'ctx')
|
|
||||||
|
|
||||||
def as_const(self, eval_ctx=None):
|
|
||||||
eval_ctx = get_eval_context(self, eval_ctx)
|
|
||||||
return tuple(x.as_const(eval_ctx) for x in self.items)
|
|
||||||
|
|
||||||
def can_assign(self):
|
|
||||||
for item in self.items:
|
|
||||||
if not item.can_assign():
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class List(Literal):
|
|
||||||
"""Any list literal such as ``[1, 2, 3]``"""
|
|
||||||
fields = ('items',)
|
|
||||||
|
|
||||||
def as_const(self, eval_ctx=None):
|
|
||||||
eval_ctx = get_eval_context(self, eval_ctx)
|
|
||||||
return [x.as_const(eval_ctx) for x in self.items]
|
|
||||||
|
|
||||||
|
|
||||||
class Dict(Literal):
|
|
||||||
"""Any dict literal such as ``{1: 2, 3: 4}``. The items must be a list of
|
|
||||||
:class:`Pair` nodes.
|
|
||||||
"""
|
|
||||||
fields = ('items',)
|
|
||||||
|
|
||||||
def as_const(self, eval_ctx=None):
|
|
||||||
eval_ctx = get_eval_context(self, eval_ctx)
|
|
||||||
return dict(x.as_const(eval_ctx) for x in self.items)
|
|
||||||
|
|
||||||
|
|
||||||
class Pair(Helper):
|
|
||||||
"""A key, value pair for dicts."""
|
|
||||||
fields = ('key', 'value')
|
|
||||||
|
|
||||||
def as_const(self, eval_ctx=None):
|
|
||||||
eval_ctx = get_eval_context(self, eval_ctx)
|
|
||||||
return self.key.as_const(eval_ctx), self.value.as_const(eval_ctx)
|
|
||||||
|
|
||||||
|
|
||||||
class Keyword(Helper):
|
|
||||||
"""A key, value pair for keyword arguments where key is a string."""
|
|
||||||
fields = ('key', 'value')
|
|
||||||
|
|
||||||
def as_const(self, eval_ctx=None):
|
|
||||||
eval_ctx = get_eval_context(self, eval_ctx)
|
|
||||||
return self.key, self.value.as_const(eval_ctx)
|
|
||||||
|
|
||||||
|
|
||||||
class CondExpr(Expr):
|
|
||||||
"""A conditional expression (inline if expression). (``{{
|
|
||||||
foo if bar else baz }}``)
|
|
||||||
"""
|
|
||||||
fields = ('test', 'expr1', 'expr2')
|
|
||||||
|
|
||||||
def as_const(self, eval_ctx=None):
|
|
||||||
eval_ctx = get_eval_context(self, eval_ctx)
|
|
||||||
if self.test.as_const(eval_ctx):
|
|
||||||
return self.expr1.as_const(eval_ctx)
|
|
||||||
|
|
||||||
# if we evaluate to an undefined object, we better do that at runtime
|
|
||||||
if self.expr2 is None:
|
|
||||||
raise Impossible()
|
|
||||||
|
|
||||||
return self.expr2.as_const(eval_ctx)
|
|
||||||
|
|
||||||
|
|
||||||
class Filter(Expr):
|
|
||||||
"""This node applies a filter on an expression. `name` is the name of
|
|
||||||
the filter, the rest of the fields are the same as for :class:`Call`.
|
|
||||||
|
|
||||||
If the `node` of a filter is `None` the contents of the last buffer are
|
|
||||||
filtered. Buffers are created by macros and filter blocks.
|
|
||||||
"""
|
|
||||||
fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
|
|
||||||
|
|
||||||
def as_const(self, eval_ctx=None):
|
|
||||||
eval_ctx = get_eval_context(self, eval_ctx)
|
|
||||||
if eval_ctx.volatile or self.node is None:
|
|
||||||
raise Impossible()
|
|
||||||
# we have to be careful here because we call filter_ below.
|
|
||||||
# if this variable would be called filter, 2to3 would wrap the
|
|
||||||
# call in a list beause it is assuming we are talking about the
|
|
||||||
# builtin filter function here which no longer returns a list in
|
|
||||||
# python 3. because of that, do not rename filter_ to filter!
|
|
||||||
filter_ = self.environment.filters.get(self.name)
|
|
||||||
if filter_ is None or getattr(filter_, 'contextfilter', False):
|
|
||||||
raise Impossible()
|
|
||||||
obj = self.node.as_const(eval_ctx)
|
|
||||||
args = [x.as_const(eval_ctx) for x in self.args]
|
|
||||||
if getattr(filter_, 'evalcontextfilter', False):
|
|
||||||
args.insert(0, eval_ctx)
|
|
||||||
elif getattr(filter_, 'environmentfilter', False):
|
|
||||||
args.insert(0, self.environment)
|
|
||||||
kwargs = dict(x.as_const(eval_ctx) for x in self.kwargs)
|
|
||||||
if self.dyn_args is not None:
|
|
||||||
try:
|
|
||||||
args.extend(self.dyn_args.as_const(eval_ctx))
|
|
||||||
except Exception:
|
|
||||||
raise Impossible()
|
|
||||||
if self.dyn_kwargs is not None:
|
|
||||||
try:
|
|
||||||
kwargs.update(self.dyn_kwargs.as_const(eval_ctx))
|
|
||||||
except Exception:
|
|
||||||
raise Impossible()
|
|
||||||
try:
|
|
||||||
return filter_(obj, *args, **kwargs)
|
|
||||||
except Exception:
|
|
||||||
raise Impossible()
|
|
||||||
|
|
||||||
|
|
||||||
class Test(Expr):
|
|
||||||
"""Applies a test on an expression. `name` is the name of the test, the
|
|
||||||
rest of the fields are the same as for :class:`Call`.
|
|
||||||
"""
|
|
||||||
fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
|
|
||||||
|
|
||||||
|
|
||||||
class Call(Expr):
|
|
||||||
"""Calls an expression. `args` is a list of arguments, `kwargs` a list
|
|
||||||
of keyword arguments (list of :class:`Keyword` nodes), and `dyn_args`
|
|
||||||
and `dyn_kwargs` has to be either `None` or a node that is used as
|
|
||||||
node for dynamic positional (``*args``) or keyword (``**kwargs``)
|
|
||||||
arguments.
|
|
||||||
"""
|
|
||||||
fields = ('node', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
|
|
||||||
|
|
||||||
def as_const(self, eval_ctx=None):
|
|
||||||
eval_ctx = get_eval_context(self, eval_ctx)
|
|
||||||
if eval_ctx.volatile:
|
|
||||||
raise Impossible()
|
|
||||||
obj = self.node.as_const(eval_ctx)
|
|
||||||
|
|
||||||
# don't evaluate context functions
|
|
||||||
args = [x.as_const(eval_ctx) for x in self.args]
|
|
||||||
if isinstance(obj, _context_function_types):
|
|
||||||
if getattr(obj, 'contextfunction', False):
|
|
||||||
raise Impossible()
|
|
||||||
elif getattr(obj, 'evalcontextfunction', False):
|
|
||||||
args.insert(0, eval_ctx)
|
|
||||||
elif getattr(obj, 'environmentfunction', False):
|
|
||||||
args.insert(0, self.environment)
|
|
||||||
|
|
||||||
kwargs = dict(x.as_const(eval_ctx) for x in self.kwargs)
|
|
||||||
if self.dyn_args is not None:
|
|
||||||
try:
|
|
||||||
args.extend(self.dyn_args.as_const(eval_ctx))
|
|
||||||
except Exception:
|
|
||||||
raise Impossible()
|
|
||||||
if self.dyn_kwargs is not None:
|
|
||||||
try:
|
|
||||||
kwargs.update(self.dyn_kwargs.as_const(eval_ctx))
|
|
||||||
except Exception:
|
|
||||||
raise Impossible()
|
|
||||||
try:
|
|
||||||
return obj(*args, **kwargs)
|
|
||||||
except Exception:
|
|
||||||
raise Impossible()
|
|
||||||
|
|
||||||
|
|
||||||
class Getitem(Expr):
|
|
||||||
"""Get an attribute or item from an expression and prefer the item."""
|
|
||||||
fields = ('node', 'arg', 'ctx')
|
|
||||||
|
|
||||||
def as_const(self, eval_ctx=None):
|
|
||||||
eval_ctx = get_eval_context(self, eval_ctx)
|
|
||||||
if self.ctx != 'load':
|
|
||||||
raise Impossible()
|
|
||||||
try:
|
|
||||||
return self.environment.getitem(self.node.as_const(eval_ctx),
|
|
||||||
self.arg.as_const(eval_ctx))
|
|
||||||
except Exception:
|
|
||||||
raise Impossible()
|
|
||||||
|
|
||||||
def can_assign(self):
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class Getattr(Expr):
|
|
||||||
"""Get an attribute or item from an expression that is a ascii-only
|
|
||||||
bytestring and prefer the attribute.
|
|
||||||
"""
|
|
||||||
fields = ('node', 'attr', 'ctx')
|
|
||||||
|
|
||||||
def as_const(self, eval_ctx=None):
|
|
||||||
if self.ctx != 'load':
|
|
||||||
raise Impossible()
|
|
||||||
try:
|
|
||||||
eval_ctx = get_eval_context(self, eval_ctx)
|
|
||||||
return self.environment.getattr(self.node.as_const(eval_ctx),
|
|
||||||
self.attr)
|
|
||||||
except Exception:
|
|
||||||
raise Impossible()
|
|
||||||
|
|
||||||
def can_assign(self):
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class Slice(Expr):
|
|
||||||
"""Represents a slice object. This must only be used as argument for
|
|
||||||
:class:`Subscript`.
|
|
||||||
"""
|
|
||||||
fields = ('start', 'stop', 'step')
|
|
||||||
|
|
||||||
def as_const(self, eval_ctx=None):
|
|
||||||
eval_ctx = get_eval_context(self, eval_ctx)
|
|
||||||
def const(obj):
|
|
||||||
if obj is None:
|
|
||||||
return None
|
|
||||||
return obj.as_const(eval_ctx)
|
|
||||||
return slice(const(self.start), const(self.stop), const(self.step))
|
|
||||||
|
|
||||||
|
|
||||||
class Concat(Expr):
|
|
||||||
"""Concatenates the list of expressions provided after converting them to
|
|
||||||
unicode.
|
|
||||||
"""
|
|
||||||
fields = ('nodes',)
|
|
||||||
|
|
||||||
def as_const(self, eval_ctx=None):
|
|
||||||
eval_ctx = get_eval_context(self, eval_ctx)
|
|
||||||
return ''.join(unicode(x.as_const(eval_ctx)) for x in self.nodes)
|
|
||||||
|
|
||||||
|
|
||||||
class Compare(Expr):
|
|
||||||
"""Compares an expression with some other expressions. `ops` must be a
|
|
||||||
list of :class:`Operand`\s.
|
|
||||||
"""
|
|
||||||
fields = ('expr', 'ops')
|
|
||||||
|
|
||||||
def as_const(self, eval_ctx=None):
|
|
||||||
eval_ctx = get_eval_context(self, eval_ctx)
|
|
||||||
result = value = self.expr.as_const(eval_ctx)
|
|
||||||
try:
|
|
||||||
for op in self.ops:
|
|
||||||
new_value = op.expr.as_const(eval_ctx)
|
|
||||||
result = _cmpop_to_func[op.op](value, new_value)
|
|
||||||
value = new_value
|
|
||||||
except Exception:
|
|
||||||
raise Impossible()
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
class Operand(Helper):
|
|
||||||
"""Holds an operator and an expression."""
|
|
||||||
fields = ('op', 'expr')
|
|
||||||
|
|
||||||
if __debug__:
|
|
||||||
Operand.__doc__ += '\nThe following operators are available: ' + \
|
|
||||||
', '.join(sorted('``%s``' % x for x in set(_binop_to_func) |
|
|
||||||
set(_uaop_to_func) | set(_cmpop_to_func)))
|
|
||||||
|
|
||||||
|
|
||||||
class Mul(BinExpr):
|
|
||||||
"""Multiplies the left with the right node."""
|
|
||||||
operator = '*'
|
|
||||||
|
|
||||||
|
|
||||||
class Div(BinExpr):
|
|
||||||
"""Divides the left by the right node."""
|
|
||||||
operator = '/'
|
|
||||||
|
|
||||||
|
|
||||||
class FloorDiv(BinExpr):
|
|
||||||
"""Divides the left by the right node and truncates conver the
|
|
||||||
result into an integer by truncating.
|
|
||||||
"""
|
|
||||||
operator = '//'
|
|
||||||
|
|
||||||
|
|
||||||
class Add(BinExpr):
|
|
||||||
"""Add the left to the right node."""
|
|
||||||
operator = '+'
|
|
||||||
|
|
||||||
|
|
||||||
class Sub(BinExpr):
|
|
||||||
"""Substract the right from the left node."""
|
|
||||||
operator = '-'
|
|
||||||
|
|
||||||
|
|
||||||
class Mod(BinExpr):
|
|
||||||
"""Left modulo right."""
|
|
||||||
operator = '%'
|
|
||||||
|
|
||||||
|
|
||||||
class Pow(BinExpr):
|
|
||||||
"""Left to the power of right."""
|
|
||||||
operator = '**'
|
|
||||||
|
|
||||||
|
|
||||||
class And(BinExpr):
|
|
||||||
"""Short circuited AND."""
|
|
||||||
operator = 'and'
|
|
||||||
|
|
||||||
def as_const(self, eval_ctx=None):
|
|
||||||
eval_ctx = get_eval_context(self, eval_ctx)
|
|
||||||
return self.left.as_const(eval_ctx) and self.right.as_const(eval_ctx)
|
|
||||||
|
|
||||||
|
|
||||||
class Or(BinExpr):
|
|
||||||
"""Short circuited OR."""
|
|
||||||
operator = 'or'
|
|
||||||
|
|
||||||
def as_const(self, eval_ctx=None):
|
|
||||||
eval_ctx = get_eval_context(self, eval_ctx)
|
|
||||||
return self.left.as_const(eval_ctx) or self.right.as_const(eval_ctx)
|
|
||||||
|
|
||||||
|
|
||||||
class Not(UnaryExpr):
|
|
||||||
"""Negate the expression."""
|
|
||||||
operator = 'not'
|
|
||||||
|
|
||||||
|
|
||||||
class Neg(UnaryExpr):
|
|
||||||
"""Make the expression negative."""
|
|
||||||
operator = '-'
|
|
||||||
|
|
||||||
|
|
||||||
class Pos(UnaryExpr):
|
|
||||||
"""Make the expression positive (noop for most expressions)"""
|
|
||||||
operator = '+'
|
|
||||||
|
|
||||||
|
|
||||||
# Helpers for extensions
|
|
||||||
|
|
||||||
|
|
||||||
class EnvironmentAttribute(Expr):
|
|
||||||
"""Loads an attribute from the environment object. This is useful for
|
|
||||||
extensions that want to call a callback stored on the environment.
|
|
||||||
"""
|
|
||||||
fields = ('name',)
|
|
||||||
|
|
||||||
|
|
||||||
class ExtensionAttribute(Expr):
|
|
||||||
"""Returns the attribute of an extension bound to the environment.
|
|
||||||
The identifier is the identifier of the :class:`Extension`.
|
|
||||||
|
|
||||||
This node is usually constructed by calling the
|
|
||||||
:meth:`~jinja2.ext.Extension.attr` method on an extension.
|
|
||||||
"""
|
|
||||||
fields = ('identifier', 'name')
|
|
||||||
|
|
||||||
|
|
||||||
class ImportedName(Expr):
|
|
||||||
"""If created with an import name the import name is returned on node
|
|
||||||
access. For example ``ImportedName('cgi.escape')`` returns the `escape`
|
|
||||||
function from the cgi module on evaluation. Imports are optimized by the
|
|
||||||
compiler so there is no need to assign them to local variables.
|
|
||||||
"""
|
|
||||||
fields = ('importname',)
|
|
||||||
|
|
||||||
|
|
||||||
class InternalName(Expr):
|
|
||||||
"""An internal name in the compiler. You cannot create these nodes
|
|
||||||
yourself but the parser provides a
|
|
||||||
:meth:`~jinja2.parser.Parser.free_identifier` method that creates
|
|
||||||
a new identifier for you. This identifier is not available from the
|
|
||||||
template and is not threated specially by the compiler.
|
|
||||||
"""
|
|
||||||
fields = ('name',)
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
raise TypeError('Can\'t create internal names. Use the '
|
|
||||||
'`free_identifier` method on a parser.')
|
|
||||||
|
|
||||||
|
|
||||||
class MarkSafe(Expr):
|
|
||||||
"""Mark the wrapped expression as safe (wrap it as `Markup`)."""
|
|
||||||
fields = ('expr',)
|
|
||||||
|
|
||||||
def as_const(self, eval_ctx=None):
|
|
||||||
eval_ctx = get_eval_context(self, eval_ctx)
|
|
||||||
return Markup(self.expr.as_const(eval_ctx))
|
|
||||||
|
|
||||||
|
|
||||||
class MarkSafeIfAutoescape(Expr):
|
|
||||||
"""Mark the wrapped expression as safe (wrap it as `Markup`) but
|
|
||||||
only if autoescaping is active.
|
|
||||||
|
|
||||||
.. versionadded:: 2.5
|
|
||||||
"""
|
|
||||||
fields = ('expr',)
|
|
||||||
|
|
||||||
def as_const(self, eval_ctx=None):
|
|
||||||
eval_ctx = get_eval_context(self, eval_ctx)
|
|
||||||
if eval_ctx.volatile:
|
|
||||||
raise Impossible()
|
|
||||||
expr = self.expr.as_const(eval_ctx)
|
|
||||||
if eval_ctx.autoescape:
|
|
||||||
return Markup(expr)
|
|
||||||
return expr
|
|
||||||
|
|
||||||
|
|
||||||
class ContextReference(Expr):
|
|
||||||
"""Returns the current template context. It can be used like a
|
|
||||||
:class:`Name` node, with a ``'load'`` ctx and will return the
|
|
||||||
current :class:`~jinja2.runtime.Context` object.
|
|
||||||
|
|
||||||
Here an example that assigns the current template name to a
|
|
||||||
variable named `foo`::
|
|
||||||
|
|
||||||
Assign(Name('foo', ctx='store'),
|
|
||||||
Getattr(ContextReference(), 'name'))
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Continue(Stmt):
|
|
||||||
"""Continue a loop."""
|
|
||||||
|
|
||||||
|
|
||||||
class Break(Stmt):
|
|
||||||
"""Break a loop."""
|
|
||||||
|
|
||||||
|
|
||||||
class Scope(Stmt):
|
|
||||||
"""An artificial scope."""
|
|
||||||
fields = ('body',)
|
|
||||||
|
|
||||||
|
|
||||||
class EvalContextModifier(Stmt):
|
|
||||||
"""Modifies the eval context. For each option that should be modified,
|
|
||||||
a :class:`Keyword` has to be added to the :attr:`options` list.
|
|
||||||
|
|
||||||
Example to change the `autoescape` setting::
|
|
||||||
|
|
||||||
EvalContextModifier(options=[Keyword('autoescape', Const(True))])
|
|
||||||
"""
|
|
||||||
fields = ('options',)
|
|
||||||
|
|
||||||
|
|
||||||
class ScopedEvalContextModifier(EvalContextModifier):
|
|
||||||
"""Modifies the eval context and reverts it later. Works exactly like
|
|
||||||
:class:`EvalContextModifier` but will only modify the
|
|
||||||
:class:`~jinja2.nodes.EvalContext` for nodes in the :attr:`body`.
|
|
||||||
"""
|
|
||||||
fields = ('body',)
|
|
||||||
|
|
||||||
|
|
||||||
# make sure nobody creates custom nodes
|
|
||||||
def _failing_new(*args, **kwargs):
|
|
||||||
raise TypeError('can\'t create custom node types')
|
|
||||||
NodeType.__new__ = staticmethod(_failing_new); del _failing_new
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
jinja2.optimizer
|
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
The jinja optimizer is currently trying to constant fold a few expressions
|
|
||||||
and modify the AST in place so that it should be easier to evaluate it.
|
|
||||||
|
|
||||||
Because the AST does not contain all the scoping information and the
|
|
||||||
compiler has to find that out, we cannot do all the optimizations we
|
|
||||||
want. For example loop unrolling doesn't work because unrolled loops would
|
|
||||||
have a different scoping.
|
|
||||||
|
|
||||||
The solution would be a second syntax tree that has the scoping rules stored.
|
|
||||||
|
|
||||||
:copyright: (c) 2010 by the Jinja Team.
|
|
||||||
:license: BSD.
|
|
||||||
"""
|
|
||||||
from jinja2 import nodes
|
|
||||||
from jinja2.visitor import NodeTransformer
|
|
||||||
|
|
||||||
|
|
||||||
def optimize(node, environment):
|
|
||||||
"""The context hint can be used to perform an static optimization
|
|
||||||
based on the context given."""
|
|
||||||
optimizer = Optimizer(environment)
|
|
||||||
return optimizer.visit(node)
|
|
||||||
|
|
||||||
|
|
||||||
class Optimizer(NodeTransformer):
|
|
||||||
|
|
||||||
def __init__(self, environment):
|
|
||||||
self.environment = environment
|
|
||||||
|
|
||||||
def visit_If(self, node):
|
|
||||||
"""Eliminate dead code."""
|
|
||||||
# do not optimize ifs that have a block inside so that it doesn't
|
|
||||||
# break super().
|
|
||||||
if node.find(nodes.Block) is not None:
|
|
||||||
return self.generic_visit(node)
|
|
||||||
try:
|
|
||||||
val = self.visit(node.test).as_const()
|
|
||||||
except nodes.Impossible:
|
|
||||||
return self.generic_visit(node)
|
|
||||||
if val:
|
|
||||||
body = node.body
|
|
||||||
else:
|
|
||||||
body = node.else_
|
|
||||||
result = []
|
|
||||||
for node in body:
|
|
||||||
result.extend(self.visit_list(node))
|
|
||||||
return result
|
|
||||||
|
|
||||||
def fold(self, node):
|
|
||||||
"""Do constant folding."""
|
|
||||||
node = self.generic_visit(node)
|
|
||||||
try:
|
|
||||||
return nodes.Const.from_untrusted(node.as_const(),
|
|
||||||
lineno=node.lineno,
|
|
||||||
environment=self.environment)
|
|
||||||
except nodes.Impossible:
|
|
||||||
return node
|
|
||||||
|
|
||||||
visit_Add = visit_Sub = visit_Mul = visit_Div = visit_FloorDiv = \
|
|
||||||
visit_Pow = visit_Mod = visit_And = visit_Or = visit_Pos = visit_Neg = \
|
|
||||||
visit_Not = visit_Compare = visit_Getitem = visit_Getattr = visit_Call = \
|
|
||||||
visit_Filter = visit_Test = visit_CondExpr = fold
|
|
||||||
del fold
|
|
||||||
@@ -1,896 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
jinja2.parser
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Implements the template parser.
|
|
||||||
|
|
||||||
:copyright: (c) 2010 by the Jinja Team.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
from jinja2 import nodes
|
|
||||||
from jinja2.exceptions import TemplateSyntaxError, TemplateAssertionError
|
|
||||||
from jinja2.utils import next
|
|
||||||
from jinja2.lexer import describe_token, describe_token_expr
|
|
||||||
|
|
||||||
|
|
||||||
#: statements that callinto
|
|
||||||
_statement_keywords = frozenset(['for', 'if', 'block', 'extends', 'print',
|
|
||||||
'macro', 'include', 'from', 'import',
|
|
||||||
'set'])
|
|
||||||
_compare_operators = frozenset(['eq', 'ne', 'lt', 'lteq', 'gt', 'gteq'])
|
|
||||||
|
|
||||||
|
|
||||||
class Parser(object):
|
|
||||||
"""This is the central parsing class Jinja2 uses. It's passed to
|
|
||||||
extensions and can be used to parse expressions or statements.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, environment, source, name=None, filename=None,
|
|
||||||
state=None):
|
|
||||||
self.environment = environment
|
|
||||||
self.stream = environment._tokenize(source, name, filename, state)
|
|
||||||
self.name = name
|
|
||||||
self.filename = filename
|
|
||||||
self.closed = False
|
|
||||||
self.extensions = {}
|
|
||||||
for extension in environment.iter_extensions():
|
|
||||||
for tag in extension.tags:
|
|
||||||
self.extensions[tag] = extension.parse
|
|
||||||
self._last_identifier = 0
|
|
||||||
self._tag_stack = []
|
|
||||||
self._end_token_stack = []
|
|
||||||
|
|
||||||
def fail(self, msg, lineno=None, exc=TemplateSyntaxError):
|
|
||||||
"""Convenience method that raises `exc` with the message, passed
|
|
||||||
line number or last line number as well as the current name and
|
|
||||||
filename.
|
|
||||||
"""
|
|
||||||
if lineno is None:
|
|
||||||
lineno = self.stream.current.lineno
|
|
||||||
raise exc(msg, lineno, self.name, self.filename)
|
|
||||||
|
|
||||||
def _fail_ut_eof(self, name, end_token_stack, lineno):
|
|
||||||
expected = []
|
|
||||||
for exprs in end_token_stack:
|
|
||||||
expected.extend(map(describe_token_expr, exprs))
|
|
||||||
if end_token_stack:
|
|
||||||
currently_looking = ' or '.join(
|
|
||||||
"'%s'" % describe_token_expr(expr)
|
|
||||||
for expr in end_token_stack[-1])
|
|
||||||
else:
|
|
||||||
currently_looking = None
|
|
||||||
|
|
||||||
if name is None:
|
|
||||||
message = ['Unexpected end of template.']
|
|
||||||
else:
|
|
||||||
message = ['Encountered unknown tag \'%s\'.' % name]
|
|
||||||
|
|
||||||
if currently_looking:
|
|
||||||
if name is not None and name in expected:
|
|
||||||
message.append('You probably made a nesting mistake. Jinja '
|
|
||||||
'is expecting this tag, but currently looking '
|
|
||||||
'for %s.' % currently_looking)
|
|
||||||
else:
|
|
||||||
message.append('Jinja was looking for the following tags: '
|
|
||||||
'%s.' % currently_looking)
|
|
||||||
|
|
||||||
if self._tag_stack:
|
|
||||||
message.append('The innermost block that needs to be '
|
|
||||||
'closed is \'%s\'.' % self._tag_stack[-1])
|
|
||||||
|
|
||||||
self.fail(' '.join(message), lineno)
|
|
||||||
|
|
||||||
def fail_unknown_tag(self, name, lineno=None):
|
|
||||||
"""Called if the parser encounters an unknown tag. Tries to fail
|
|
||||||
with a human readable error message that could help to identify
|
|
||||||
the problem.
|
|
||||||
"""
|
|
||||||
return self._fail_ut_eof(name, self._end_token_stack, lineno)
|
|
||||||
|
|
||||||
def fail_eof(self, end_tokens=None, lineno=None):
|
|
||||||
"""Like fail_unknown_tag but for end of template situations."""
|
|
||||||
stack = list(self._end_token_stack)
|
|
||||||
if end_tokens is not None:
|
|
||||||
stack.append(end_tokens)
|
|
||||||
return self._fail_ut_eof(None, stack, lineno)
|
|
||||||
|
|
||||||
def is_tuple_end(self, extra_end_rules=None):
|
|
||||||
"""Are we at the end of a tuple?"""
|
|
||||||
if self.stream.current.type in ('variable_end', 'block_end', 'rparen'):
|
|
||||||
return True
|
|
||||||
elif extra_end_rules is not None:
|
|
||||||
return self.stream.current.test_any(extra_end_rules)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def free_identifier(self, lineno=None):
|
|
||||||
"""Return a new free identifier as :class:`~jinja2.nodes.InternalName`."""
|
|
||||||
self._last_identifier += 1
|
|
||||||
rv = object.__new__(nodes.InternalName)
|
|
||||||
nodes.Node.__init__(rv, 'fi%d' % self._last_identifier, lineno=lineno)
|
|
||||||
return rv
|
|
||||||
|
|
||||||
def parse_statement(self):
|
|
||||||
"""Parse a single statement."""
|
|
||||||
token = self.stream.current
|
|
||||||
if token.type != 'name':
|
|
||||||
self.fail('tag name expected', token.lineno)
|
|
||||||
self._tag_stack.append(token.value)
|
|
||||||
pop_tag = True
|
|
||||||
try:
|
|
||||||
if token.value in _statement_keywords:
|
|
||||||
return getattr(self, 'parse_' + self.stream.current.value)()
|
|
||||||
if token.value == 'call':
|
|
||||||
return self.parse_call_block()
|
|
||||||
if token.value == 'filter':
|
|
||||||
return self.parse_filter_block()
|
|
||||||
ext = self.extensions.get(token.value)
|
|
||||||
if ext is not None:
|
|
||||||
return ext(self)
|
|
||||||
|
|
||||||
# did not work out, remove the token we pushed by accident
|
|
||||||
# from the stack so that the unknown tag fail function can
|
|
||||||
# produce a proper error message.
|
|
||||||
self._tag_stack.pop()
|
|
||||||
pop_tag = False
|
|
||||||
self.fail_unknown_tag(token.value, token.lineno)
|
|
||||||
finally:
|
|
||||||
if pop_tag:
|
|
||||||
self._tag_stack.pop()
|
|
||||||
|
|
||||||
def parse_statements(self, end_tokens, drop_needle=False):
|
|
||||||
"""Parse multiple statements into a list until one of the end tokens
|
|
||||||
is reached. This is used to parse the body of statements as it also
|
|
||||||
parses template data if appropriate. The parser checks first if the
|
|
||||||
current token is a colon and skips it if there is one. Then it checks
|
|
||||||
for the block end and parses until if one of the `end_tokens` is
|
|
||||||
reached. Per default the active token in the stream at the end of
|
|
||||||
the call is the matched end token. If this is not wanted `drop_needle`
|
|
||||||
can be set to `True` and the end token is removed.
|
|
||||||
"""
|
|
||||||
# the first token may be a colon for python compatibility
|
|
||||||
self.stream.skip_if('colon')
|
|
||||||
|
|
||||||
# in the future it would be possible to add whole code sections
|
|
||||||
# by adding some sort of end of statement token and parsing those here.
|
|
||||||
self.stream.expect('block_end')
|
|
||||||
result = self.subparse(end_tokens)
|
|
||||||
|
|
||||||
# we reached the end of the template too early, the subparser
|
|
||||||
# does not check for this, so we do that now
|
|
||||||
if self.stream.current.type == 'eof':
|
|
||||||
self.fail_eof(end_tokens)
|
|
||||||
|
|
||||||
if drop_needle:
|
|
||||||
next(self.stream)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def parse_set(self):
|
|
||||||
"""Parse an assign statement."""
|
|
||||||
lineno = next(self.stream).lineno
|
|
||||||
target = self.parse_assign_target()
|
|
||||||
self.stream.expect('assign')
|
|
||||||
expr = self.parse_tuple()
|
|
||||||
return nodes.Assign(target, expr, lineno=lineno)
|
|
||||||
|
|
||||||
def parse_for(self):
|
|
||||||
"""Parse a for loop."""
|
|
||||||
lineno = self.stream.expect('name:for').lineno
|
|
||||||
target = self.parse_assign_target(extra_end_rules=('name:in',))
|
|
||||||
self.stream.expect('name:in')
|
|
||||||
iter = self.parse_tuple(with_condexpr=False,
|
|
||||||
extra_end_rules=('name:recursive',))
|
|
||||||
test = None
|
|
||||||
if self.stream.skip_if('name:if'):
|
|
||||||
test = self.parse_expression()
|
|
||||||
recursive = self.stream.skip_if('name:recursive')
|
|
||||||
body = self.parse_statements(('name:endfor', 'name:else'))
|
|
||||||
if next(self.stream).value == 'endfor':
|
|
||||||
else_ = []
|
|
||||||
else:
|
|
||||||
else_ = self.parse_statements(('name:endfor',), drop_needle=True)
|
|
||||||
return nodes.For(target, iter, body, else_, test,
|
|
||||||
recursive, lineno=lineno)
|
|
||||||
|
|
||||||
def parse_if(self):
|
|
||||||
"""Parse an if construct."""
|
|
||||||
node = result = nodes.If(lineno=self.stream.expect('name:if').lineno)
|
|
||||||
while 1:
|
|
||||||
node.test = self.parse_tuple(with_condexpr=False)
|
|
||||||
node.body = self.parse_statements(('name:elif', 'name:else',
|
|
||||||
'name:endif'))
|
|
||||||
token = next(self.stream)
|
|
||||||
if token.test('name:elif'):
|
|
||||||
new_node = nodes.If(lineno=self.stream.current.lineno)
|
|
||||||
node.else_ = [new_node]
|
|
||||||
node = new_node
|
|
||||||
continue
|
|
||||||
elif token.test('name:else'):
|
|
||||||
node.else_ = self.parse_statements(('name:endif',),
|
|
||||||
drop_needle=True)
|
|
||||||
else:
|
|
||||||
node.else_ = []
|
|
||||||
break
|
|
||||||
return result
|
|
||||||
|
|
||||||
def parse_block(self):
|
|
||||||
node = nodes.Block(lineno=next(self.stream).lineno)
|
|
||||||
node.name = self.stream.expect('name').value
|
|
||||||
node.scoped = self.stream.skip_if('name:scoped')
|
|
||||||
|
|
||||||
# common problem people encounter when switching from django
|
|
||||||
# to jinja. we do not support hyphens in block names, so let's
|
|
||||||
# raise a nicer error message in that case.
|
|
||||||
if self.stream.current.type == 'sub':
|
|
||||||
self.fail('Block names in Jinja have to be valid Python '
|
|
||||||
'identifiers and may not contain hypens, use an '
|
|
||||||
'underscore instead.')
|
|
||||||
|
|
||||||
node.body = self.parse_statements(('name:endblock',), drop_needle=True)
|
|
||||||
self.stream.skip_if('name:' + node.name)
|
|
||||||
return node
|
|
||||||
|
|
||||||
def parse_extends(self):
|
|
||||||
node = nodes.Extends(lineno=next(self.stream).lineno)
|
|
||||||
node.template = self.parse_expression()
|
|
||||||
return node
|
|
||||||
|
|
||||||
def parse_import_context(self, node, default):
|
|
||||||
if self.stream.current.test_any('name:with', 'name:without') and \
|
|
||||||
self.stream.look().test('name:context'):
|
|
||||||
node.with_context = next(self.stream).value == 'with'
|
|
||||||
self.stream.skip()
|
|
||||||
else:
|
|
||||||
node.with_context = default
|
|
||||||
return node
|
|
||||||
|
|
||||||
def parse_include(self):
|
|
||||||
node = nodes.Include(lineno=next(self.stream).lineno)
|
|
||||||
node.template = self.parse_expression()
|
|
||||||
if self.stream.current.test('name:ignore') and \
|
|
||||||
self.stream.look().test('name:missing'):
|
|
||||||
node.ignore_missing = True
|
|
||||||
self.stream.skip(2)
|
|
||||||
else:
|
|
||||||
node.ignore_missing = False
|
|
||||||
return self.parse_import_context(node, True)
|
|
||||||
|
|
||||||
def parse_import(self):
|
|
||||||
node = nodes.Import(lineno=next(self.stream).lineno)
|
|
||||||
node.template = self.parse_expression()
|
|
||||||
self.stream.expect('name:as')
|
|
||||||
node.target = self.parse_assign_target(name_only=True).name
|
|
||||||
return self.parse_import_context(node, False)
|
|
||||||
|
|
||||||
def parse_from(self):
|
|
||||||
node = nodes.FromImport(lineno=next(self.stream).lineno)
|
|
||||||
node.template = self.parse_expression()
|
|
||||||
self.stream.expect('name:import')
|
|
||||||
node.names = []
|
|
||||||
|
|
||||||
def parse_context():
|
|
||||||
if self.stream.current.value in ('with', 'without') and \
|
|
||||||
self.stream.look().test('name:context'):
|
|
||||||
node.with_context = next(self.stream).value == 'with'
|
|
||||||
self.stream.skip()
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
while 1:
|
|
||||||
if node.names:
|
|
||||||
self.stream.expect('comma')
|
|
||||||
if self.stream.current.type == 'name':
|
|
||||||
if parse_context():
|
|
||||||
break
|
|
||||||
target = self.parse_assign_target(name_only=True)
|
|
||||||
if target.name.startswith('_'):
|
|
||||||
self.fail('names starting with an underline can not '
|
|
||||||
'be imported', target.lineno,
|
|
||||||
exc=TemplateAssertionError)
|
|
||||||
if self.stream.skip_if('name:as'):
|
|
||||||
alias = self.parse_assign_target(name_only=True)
|
|
||||||
node.names.append((target.name, alias.name))
|
|
||||||
else:
|
|
||||||
node.names.append(target.name)
|
|
||||||
if parse_context() or self.stream.current.type != 'comma':
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
if not hasattr(node, 'with_context'):
|
|
||||||
node.with_context = False
|
|
||||||
self.stream.skip_if('comma')
|
|
||||||
return node
|
|
||||||
|
|
||||||
def parse_signature(self, node):
|
|
||||||
node.args = args = []
|
|
||||||
node.defaults = defaults = []
|
|
||||||
self.stream.expect('lparen')
|
|
||||||
while self.stream.current.type != 'rparen':
|
|
||||||
if args:
|
|
||||||
self.stream.expect('comma')
|
|
||||||
arg = self.parse_assign_target(name_only=True)
|
|
||||||
arg.set_ctx('param')
|
|
||||||
if self.stream.skip_if('assign'):
|
|
||||||
defaults.append(self.parse_expression())
|
|
||||||
args.append(arg)
|
|
||||||
self.stream.expect('rparen')
|
|
||||||
|
|
||||||
def parse_call_block(self):
|
|
||||||
node = nodes.CallBlock(lineno=next(self.stream).lineno)
|
|
||||||
if self.stream.current.type == 'lparen':
|
|
||||||
self.parse_signature(node)
|
|
||||||
else:
|
|
||||||
node.args = []
|
|
||||||
node.defaults = []
|
|
||||||
|
|
||||||
node.call = self.parse_expression()
|
|
||||||
if not isinstance(node.call, nodes.Call):
|
|
||||||
self.fail('expected call', node.lineno)
|
|
||||||
node.body = self.parse_statements(('name:endcall',), drop_needle=True)
|
|
||||||
return node
|
|
||||||
|
|
||||||
def parse_filter_block(self):
|
|
||||||
node = nodes.FilterBlock(lineno=next(self.stream).lineno)
|
|
||||||
node.filter = self.parse_filter(None, start_inline=True)
|
|
||||||
node.body = self.parse_statements(('name:endfilter',),
|
|
||||||
drop_needle=True)
|
|
||||||
return node
|
|
||||||
|
|
||||||
def parse_macro(self):
|
|
||||||
node = nodes.Macro(lineno=next(self.stream).lineno)
|
|
||||||
node.name = self.parse_assign_target(name_only=True).name
|
|
||||||
self.parse_signature(node)
|
|
||||||
node.body = self.parse_statements(('name:endmacro',),
|
|
||||||
drop_needle=True)
|
|
||||||
return node
|
|
||||||
|
|
||||||
def parse_print(self):
|
|
||||||
node = nodes.Output(lineno=next(self.stream).lineno)
|
|
||||||
node.nodes = []
|
|
||||||
while self.stream.current.type != 'block_end':
|
|
||||||
if node.nodes:
|
|
||||||
self.stream.expect('comma')
|
|
||||||
node.nodes.append(self.parse_expression())
|
|
||||||
return node
|
|
||||||
|
|
||||||
def parse_assign_target(self, with_tuple=True, name_only=False,
|
|
||||||
extra_end_rules=None):
|
|
||||||
"""Parse an assignment target. As Jinja2 allows assignments to
|
|
||||||
tuples, this function can parse all allowed assignment targets. Per
|
|
||||||
default assignments to tuples are parsed, that can be disable however
|
|
||||||
by setting `with_tuple` to `False`. If only assignments to names are
|
|
||||||
wanted `name_only` can be set to `True`. The `extra_end_rules`
|
|
||||||
parameter is forwarded to the tuple parsing function.
|
|
||||||
"""
|
|
||||||
if name_only:
|
|
||||||
token = self.stream.expect('name')
|
|
||||||
target = nodes.Name(token.value, 'store', lineno=token.lineno)
|
|
||||||
else:
|
|
||||||
if with_tuple:
|
|
||||||
target = self.parse_tuple(simplified=True,
|
|
||||||
extra_end_rules=extra_end_rules)
|
|
||||||
else:
|
|
||||||
target = self.parse_primary()
|
|
||||||
target.set_ctx('store')
|
|
||||||
if not target.can_assign():
|
|
||||||
self.fail('can\'t assign to %r' % target.__class__.
|
|
||||||
__name__.lower(), target.lineno)
|
|
||||||
return target
|
|
||||||
|
|
||||||
def parse_expression(self, with_condexpr=True):
|
|
||||||
"""Parse an expression. Per default all expressions are parsed, if
|
|
||||||
the optional `with_condexpr` parameter is set to `False` conditional
|
|
||||||
expressions are not parsed.
|
|
||||||
"""
|
|
||||||
if with_condexpr:
|
|
||||||
return self.parse_condexpr()
|
|
||||||
return self.parse_or()
|
|
||||||
|
|
||||||
def parse_condexpr(self):
|
|
||||||
lineno = self.stream.current.lineno
|
|
||||||
expr1 = self.parse_or()
|
|
||||||
while self.stream.skip_if('name:if'):
|
|
||||||
expr2 = self.parse_or()
|
|
||||||
if self.stream.skip_if('name:else'):
|
|
||||||
expr3 = self.parse_condexpr()
|
|
||||||
else:
|
|
||||||
expr3 = None
|
|
||||||
expr1 = nodes.CondExpr(expr2, expr1, expr3, lineno=lineno)
|
|
||||||
lineno = self.stream.current.lineno
|
|
||||||
return expr1
|
|
||||||
|
|
||||||
def parse_or(self):
|
|
||||||
lineno = self.stream.current.lineno
|
|
||||||
left = self.parse_and()
|
|
||||||
while self.stream.skip_if('name:or'):
|
|
||||||
right = self.parse_and()
|
|
||||||
left = nodes.Or(left, right, lineno=lineno)
|
|
||||||
lineno = self.stream.current.lineno
|
|
||||||
return left
|
|
||||||
|
|
||||||
def parse_and(self):
|
|
||||||
lineno = self.stream.current.lineno
|
|
||||||
left = self.parse_not()
|
|
||||||
while self.stream.skip_if('name:and'):
|
|
||||||
right = self.parse_not()
|
|
||||||
left = nodes.And(left, right, lineno=lineno)
|
|
||||||
lineno = self.stream.current.lineno
|
|
||||||
return left
|
|
||||||
|
|
||||||
def parse_not(self):
|
|
||||||
if self.stream.current.test('name:not'):
|
|
||||||
lineno = next(self.stream).lineno
|
|
||||||
return nodes.Not(self.parse_not(), lineno=lineno)
|
|
||||||
return self.parse_compare()
|
|
||||||
|
|
||||||
def parse_compare(self):
|
|
||||||
lineno = self.stream.current.lineno
|
|
||||||
expr = self.parse_add()
|
|
||||||
ops = []
|
|
||||||
while 1:
|
|
||||||
token_type = self.stream.current.type
|
|
||||||
if token_type in _compare_operators:
|
|
||||||
next(self.stream)
|
|
||||||
ops.append(nodes.Operand(token_type, self.parse_add()))
|
|
||||||
elif self.stream.skip_if('name:in'):
|
|
||||||
ops.append(nodes.Operand('in', self.parse_add()))
|
|
||||||
elif self.stream.current.test('name:not') and \
|
|
||||||
self.stream.look().test('name:in'):
|
|
||||||
self.stream.skip(2)
|
|
||||||
ops.append(nodes.Operand('notin', self.parse_add()))
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
lineno = self.stream.current.lineno
|
|
||||||
if not ops:
|
|
||||||
return expr
|
|
||||||
return nodes.Compare(expr, ops, lineno=lineno)
|
|
||||||
|
|
||||||
def parse_add(self):
|
|
||||||
lineno = self.stream.current.lineno
|
|
||||||
left = self.parse_sub()
|
|
||||||
while self.stream.current.type == 'add':
|
|
||||||
next(self.stream)
|
|
||||||
right = self.parse_sub()
|
|
||||||
left = nodes.Add(left, right, lineno=lineno)
|
|
||||||
lineno = self.stream.current.lineno
|
|
||||||
return left
|
|
||||||
|
|
||||||
def parse_sub(self):
|
|
||||||
lineno = self.stream.current.lineno
|
|
||||||
left = self.parse_concat()
|
|
||||||
while self.stream.current.type == 'sub':
|
|
||||||
next(self.stream)
|
|
||||||
right = self.parse_concat()
|
|
||||||
left = nodes.Sub(left, right, lineno=lineno)
|
|
||||||
lineno = self.stream.current.lineno
|
|
||||||
return left
|
|
||||||
|
|
||||||
def parse_concat(self):
|
|
||||||
lineno = self.stream.current.lineno
|
|
||||||
args = [self.parse_mul()]
|
|
||||||
while self.stream.current.type == 'tilde':
|
|
||||||
next(self.stream)
|
|
||||||
args.append(self.parse_mul())
|
|
||||||
if len(args) == 1:
|
|
||||||
return args[0]
|
|
||||||
return nodes.Concat(args, lineno=lineno)
|
|
||||||
|
|
||||||
def parse_mul(self):
|
|
||||||
lineno = self.stream.current.lineno
|
|
||||||
left = self.parse_div()
|
|
||||||
while self.stream.current.type == 'mul':
|
|
||||||
next(self.stream)
|
|
||||||
right = self.parse_div()
|
|
||||||
left = nodes.Mul(left, right, lineno=lineno)
|
|
||||||
lineno = self.stream.current.lineno
|
|
||||||
return left
|
|
||||||
|
|
||||||
def parse_div(self):
|
|
||||||
lineno = self.stream.current.lineno
|
|
||||||
left = self.parse_floordiv()
|
|
||||||
while self.stream.current.type == 'div':
|
|
||||||
next(self.stream)
|
|
||||||
right = self.parse_floordiv()
|
|
||||||
left = nodes.Div(left, right, lineno=lineno)
|
|
||||||
lineno = self.stream.current.lineno
|
|
||||||
return left
|
|
||||||
|
|
||||||
def parse_floordiv(self):
|
|
||||||
lineno = self.stream.current.lineno
|
|
||||||
left = self.parse_mod()
|
|
||||||
while self.stream.current.type == 'floordiv':
|
|
||||||
next(self.stream)
|
|
||||||
right = self.parse_mod()
|
|
||||||
left = nodes.FloorDiv(left, right, lineno=lineno)
|
|
||||||
lineno = self.stream.current.lineno
|
|
||||||
return left
|
|
||||||
|
|
||||||
def parse_mod(self):
|
|
||||||
lineno = self.stream.current.lineno
|
|
||||||
left = self.parse_pow()
|
|
||||||
while self.stream.current.type == 'mod':
|
|
||||||
next(self.stream)
|
|
||||||
right = self.parse_pow()
|
|
||||||
left = nodes.Mod(left, right, lineno=lineno)
|
|
||||||
lineno = self.stream.current.lineno
|
|
||||||
return left
|
|
||||||
|
|
||||||
def parse_pow(self):
|
|
||||||
lineno = self.stream.current.lineno
|
|
||||||
left = self.parse_unary()
|
|
||||||
while self.stream.current.type == 'pow':
|
|
||||||
next(self.stream)
|
|
||||||
right = self.parse_unary()
|
|
||||||
left = nodes.Pow(left, right, lineno=lineno)
|
|
||||||
lineno = self.stream.current.lineno
|
|
||||||
return left
|
|
||||||
|
|
||||||
def parse_unary(self, with_filter=True):
|
|
||||||
token_type = self.stream.current.type
|
|
||||||
lineno = self.stream.current.lineno
|
|
||||||
if token_type == 'sub':
|
|
||||||
next(self.stream)
|
|
||||||
node = nodes.Neg(self.parse_unary(False), lineno=lineno)
|
|
||||||
elif token_type == 'add':
|
|
||||||
next(self.stream)
|
|
||||||
node = nodes.Pos(self.parse_unary(False), lineno=lineno)
|
|
||||||
else:
|
|
||||||
node = self.parse_primary()
|
|
||||||
node = self.parse_postfix(node)
|
|
||||||
if with_filter:
|
|
||||||
node = self.parse_filter_expr(node)
|
|
||||||
return node
|
|
||||||
|
|
||||||
def parse_primary(self):
|
|
||||||
token = self.stream.current
|
|
||||||
if token.type == 'name':
|
|
||||||
if token.value in ('true', 'false', 'True', 'False'):
|
|
||||||
node = nodes.Const(token.value in ('true', 'True'),
|
|
||||||
lineno=token.lineno)
|
|
||||||
elif token.value in ('none', 'None'):
|
|
||||||
node = nodes.Const(None, lineno=token.lineno)
|
|
||||||
else:
|
|
||||||
node = nodes.Name(token.value, 'load', lineno=token.lineno)
|
|
||||||
next(self.stream)
|
|
||||||
elif token.type == 'string':
|
|
||||||
next(self.stream)
|
|
||||||
buf = [token.value]
|
|
||||||
lineno = token.lineno
|
|
||||||
while self.stream.current.type == 'string':
|
|
||||||
buf.append(self.stream.current.value)
|
|
||||||
next(self.stream)
|
|
||||||
node = nodes.Const(''.join(buf), lineno=lineno)
|
|
||||||
elif token.type in ('integer', 'float'):
|
|
||||||
next(self.stream)
|
|
||||||
node = nodes.Const(token.value, lineno=token.lineno)
|
|
||||||
elif token.type == 'lparen':
|
|
||||||
next(self.stream)
|
|
||||||
node = self.parse_tuple(explicit_parentheses=True)
|
|
||||||
self.stream.expect('rparen')
|
|
||||||
elif token.type == 'lbracket':
|
|
||||||
node = self.parse_list()
|
|
||||||
elif token.type == 'lbrace':
|
|
||||||
node = self.parse_dict()
|
|
||||||
else:
|
|
||||||
self.fail("unexpected '%s'" % describe_token(token), token.lineno)
|
|
||||||
return node
|
|
||||||
|
|
||||||
def parse_tuple(self, simplified=False, with_condexpr=True,
|
|
||||||
extra_end_rules=None, explicit_parentheses=False):
|
|
||||||
"""Works like `parse_expression` but if multiple expressions are
|
|
||||||
delimited by a comma a :class:`~jinja2.nodes.Tuple` node is created.
|
|
||||||
This method could also return a regular expression instead of a tuple
|
|
||||||
if no commas where found.
|
|
||||||
|
|
||||||
The default parsing mode is a full tuple. If `simplified` is `True`
|
|
||||||
only names and literals are parsed. The `no_condexpr` parameter is
|
|
||||||
forwarded to :meth:`parse_expression`.
|
|
||||||
|
|
||||||
Because tuples do not require delimiters and may end in a bogus comma
|
|
||||||
an extra hint is needed that marks the end of a tuple. For example
|
|
||||||
for loops support tuples between `for` and `in`. In that case the
|
|
||||||
`extra_end_rules` is set to ``['name:in']``.
|
|
||||||
|
|
||||||
`explicit_parentheses` is true if the parsing was triggered by an
|
|
||||||
expression in parentheses. This is used to figure out if an empty
|
|
||||||
tuple is a valid expression or not.
|
|
||||||
"""
|
|
||||||
lineno = self.stream.current.lineno
|
|
||||||
if simplified:
|
|
||||||
parse = self.parse_primary
|
|
||||||
elif with_condexpr:
|
|
||||||
parse = self.parse_expression
|
|
||||||
else:
|
|
||||||
parse = lambda: self.parse_expression(with_condexpr=False)
|
|
||||||
args = []
|
|
||||||
is_tuple = False
|
|
||||||
while 1:
|
|
||||||
if args:
|
|
||||||
self.stream.expect('comma')
|
|
||||||
if self.is_tuple_end(extra_end_rules):
|
|
||||||
break
|
|
||||||
args.append(parse())
|
|
||||||
if self.stream.current.type == 'comma':
|
|
||||||
is_tuple = True
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
lineno = self.stream.current.lineno
|
|
||||||
|
|
||||||
if not is_tuple:
|
|
||||||
if args:
|
|
||||||
return args[0]
|
|
||||||
|
|
||||||
# if we don't have explicit parentheses, an empty tuple is
|
|
||||||
# not a valid expression. This would mean nothing (literally
|
|
||||||
# nothing) in the spot of an expression would be an empty
|
|
||||||
# tuple.
|
|
||||||
if not explicit_parentheses:
|
|
||||||
self.fail('Expected an expression, got \'%s\'' %
|
|
||||||
describe_token(self.stream.current))
|
|
||||||
|
|
||||||
return nodes.Tuple(args, 'load', lineno=lineno)
|
|
||||||
|
|
||||||
def parse_list(self):
|
|
||||||
token = self.stream.expect('lbracket')
|
|
||||||
items = []
|
|
||||||
while self.stream.current.type != 'rbracket':
|
|
||||||
if items:
|
|
||||||
self.stream.expect('comma')
|
|
||||||
if self.stream.current.type == 'rbracket':
|
|
||||||
break
|
|
||||||
items.append(self.parse_expression())
|
|
||||||
self.stream.expect('rbracket')
|
|
||||||
return nodes.List(items, lineno=token.lineno)
|
|
||||||
|
|
||||||
def parse_dict(self):
|
|
||||||
token = self.stream.expect('lbrace')
|
|
||||||
items = []
|
|
||||||
while self.stream.current.type != 'rbrace':
|
|
||||||
if items:
|
|
||||||
self.stream.expect('comma')
|
|
||||||
if self.stream.current.type == 'rbrace':
|
|
||||||
break
|
|
||||||
key = self.parse_expression()
|
|
||||||
self.stream.expect('colon')
|
|
||||||
value = self.parse_expression()
|
|
||||||
items.append(nodes.Pair(key, value, lineno=key.lineno))
|
|
||||||
self.stream.expect('rbrace')
|
|
||||||
return nodes.Dict(items, lineno=token.lineno)
|
|
||||||
|
|
||||||
def parse_postfix(self, node):
|
|
||||||
while 1:
|
|
||||||
token_type = self.stream.current.type
|
|
||||||
if token_type == 'dot' or token_type == 'lbracket':
|
|
||||||
node = self.parse_subscript(node)
|
|
||||||
# calls are valid both after postfix expressions (getattr
|
|
||||||
# and getitem) as well as filters and tests
|
|
||||||
elif token_type == 'lparen':
|
|
||||||
node = self.parse_call(node)
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
return node
|
|
||||||
|
|
||||||
def parse_filter_expr(self, node):
|
|
||||||
while 1:
|
|
||||||
token_type = self.stream.current.type
|
|
||||||
if token_type == 'pipe':
|
|
||||||
node = self.parse_filter(node)
|
|
||||||
elif token_type == 'name' and self.stream.current.value == 'is':
|
|
||||||
node = self.parse_test(node)
|
|
||||||
# calls are valid both after postfix expressions (getattr
|
|
||||||
# and getitem) as well as filters and tests
|
|
||||||
elif token_type == 'lparen':
|
|
||||||
node = self.parse_call(node)
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
return node
|
|
||||||
|
|
||||||
def parse_subscript(self, node):
|
|
||||||
token = next(self.stream)
|
|
||||||
if token.type == 'dot':
|
|
||||||
attr_token = self.stream.current
|
|
||||||
next(self.stream)
|
|
||||||
if attr_token.type == 'name':
|
|
||||||
return nodes.Getattr(node, attr_token.value, 'load',
|
|
||||||
lineno=token.lineno)
|
|
||||||
elif attr_token.type != 'integer':
|
|
||||||
self.fail('expected name or number', attr_token.lineno)
|
|
||||||
arg = nodes.Const(attr_token.value, lineno=attr_token.lineno)
|
|
||||||
return nodes.Getitem(node, arg, 'load', lineno=token.lineno)
|
|
||||||
if token.type == 'lbracket':
|
|
||||||
priority_on_attribute = False
|
|
||||||
args = []
|
|
||||||
while self.stream.current.type != 'rbracket':
|
|
||||||
if args:
|
|
||||||
self.stream.expect('comma')
|
|
||||||
args.append(self.parse_subscribed())
|
|
||||||
self.stream.expect('rbracket')
|
|
||||||
if len(args) == 1:
|
|
||||||
arg = args[0]
|
|
||||||
else:
|
|
||||||
arg = nodes.Tuple(args, 'load', lineno=token.lineno)
|
|
||||||
return nodes.Getitem(node, arg, 'load', lineno=token.lineno)
|
|
||||||
self.fail('expected subscript expression', self.lineno)
|
|
||||||
|
|
||||||
def parse_subscribed(self):
|
|
||||||
lineno = self.stream.current.lineno
|
|
||||||
|
|
||||||
if self.stream.current.type == 'colon':
|
|
||||||
next(self.stream)
|
|
||||||
args = [None]
|
|
||||||
else:
|
|
||||||
node = self.parse_expression()
|
|
||||||
if self.stream.current.type != 'colon':
|
|
||||||
return node
|
|
||||||
next(self.stream)
|
|
||||||
args = [node]
|
|
||||||
|
|
||||||
if self.stream.current.type == 'colon':
|
|
||||||
args.append(None)
|
|
||||||
elif self.stream.current.type not in ('rbracket', 'comma'):
|
|
||||||
args.append(self.parse_expression())
|
|
||||||
else:
|
|
||||||
args.append(None)
|
|
||||||
|
|
||||||
if self.stream.current.type == 'colon':
|
|
||||||
next(self.stream)
|
|
||||||
if self.stream.current.type not in ('rbracket', 'comma'):
|
|
||||||
args.append(self.parse_expression())
|
|
||||||
else:
|
|
||||||
args.append(None)
|
|
||||||
else:
|
|
||||||
args.append(None)
|
|
||||||
|
|
||||||
return nodes.Slice(lineno=lineno, *args)
|
|
||||||
|
|
||||||
def parse_call(self, node):
|
|
||||||
token = self.stream.expect('lparen')
|
|
||||||
args = []
|
|
||||||
kwargs = []
|
|
||||||
dyn_args = dyn_kwargs = None
|
|
||||||
require_comma = False
|
|
||||||
|
|
||||||
def ensure(expr):
|
|
||||||
if not expr:
|
|
||||||
self.fail('invalid syntax for function call expression',
|
|
||||||
token.lineno)
|
|
||||||
|
|
||||||
while self.stream.current.type != 'rparen':
|
|
||||||
if require_comma:
|
|
||||||
self.stream.expect('comma')
|
|
||||||
# support for trailing comma
|
|
||||||
if self.stream.current.type == 'rparen':
|
|
||||||
break
|
|
||||||
if self.stream.current.type == 'mul':
|
|
||||||
ensure(dyn_args is None and dyn_kwargs is None)
|
|
||||||
next(self.stream)
|
|
||||||
dyn_args = self.parse_expression()
|
|
||||||
elif self.stream.current.type == 'pow':
|
|
||||||
ensure(dyn_kwargs is None)
|
|
||||||
next(self.stream)
|
|
||||||
dyn_kwargs = self.parse_expression()
|
|
||||||
else:
|
|
||||||
ensure(dyn_args is None and dyn_kwargs is None)
|
|
||||||
if self.stream.current.type == 'name' and \
|
|
||||||
self.stream.look().type == 'assign':
|
|
||||||
key = self.stream.current.value
|
|
||||||
self.stream.skip(2)
|
|
||||||
value = self.parse_expression()
|
|
||||||
kwargs.append(nodes.Keyword(key, value,
|
|
||||||
lineno=value.lineno))
|
|
||||||
else:
|
|
||||||
ensure(not kwargs)
|
|
||||||
args.append(self.parse_expression())
|
|
||||||
|
|
||||||
require_comma = True
|
|
||||||
self.stream.expect('rparen')
|
|
||||||
|
|
||||||
if node is None:
|
|
||||||
return args, kwargs, dyn_args, dyn_kwargs
|
|
||||||
return nodes.Call(node, args, kwargs, dyn_args, dyn_kwargs,
|
|
||||||
lineno=token.lineno)
|
|
||||||
|
|
||||||
def parse_filter(self, node, start_inline=False):
|
|
||||||
while self.stream.current.type == 'pipe' or start_inline:
|
|
||||||
if not start_inline:
|
|
||||||
next(self.stream)
|
|
||||||
token = self.stream.expect('name')
|
|
||||||
name = token.value
|
|
||||||
while self.stream.current.type == 'dot':
|
|
||||||
next(self.stream)
|
|
||||||
name += '.' + self.stream.expect('name').value
|
|
||||||
if self.stream.current.type == 'lparen':
|
|
||||||
args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None)
|
|
||||||
else:
|
|
||||||
args = []
|
|
||||||
kwargs = []
|
|
||||||
dyn_args = dyn_kwargs = None
|
|
||||||
node = nodes.Filter(node, name, args, kwargs, dyn_args,
|
|
||||||
dyn_kwargs, lineno=token.lineno)
|
|
||||||
start_inline = False
|
|
||||||
return node
|
|
||||||
|
|
||||||
def parse_test(self, node):
|
|
||||||
token = next(self.stream)
|
|
||||||
if self.stream.current.test('name:not'):
|
|
||||||
next(self.stream)
|
|
||||||
negated = True
|
|
||||||
else:
|
|
||||||
negated = False
|
|
||||||
name = self.stream.expect('name').value
|
|
||||||
while self.stream.current.type == 'dot':
|
|
||||||
next(self.stream)
|
|
||||||
name += '.' + self.stream.expect('name').value
|
|
||||||
dyn_args = dyn_kwargs = None
|
|
||||||
kwargs = []
|
|
||||||
if self.stream.current.type == 'lparen':
|
|
||||||
args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None)
|
|
||||||
elif self.stream.current.type in ('name', 'string', 'integer',
|
|
||||||
'float', 'lparen', 'lbracket',
|
|
||||||
'lbrace') and not \
|
|
||||||
self.stream.current.test_any('name:else', 'name:or',
|
|
||||||
'name:and'):
|
|
||||||
if self.stream.current.test('name:is'):
|
|
||||||
self.fail('You cannot chain multiple tests with is')
|
|
||||||
args = [self.parse_expression()]
|
|
||||||
else:
|
|
||||||
args = []
|
|
||||||
node = nodes.Test(node, name, args, kwargs, dyn_args,
|
|
||||||
dyn_kwargs, lineno=token.lineno)
|
|
||||||
if negated:
|
|
||||||
node = nodes.Not(node, lineno=token.lineno)
|
|
||||||
return node
|
|
||||||
|
|
||||||
def subparse(self, end_tokens=None):
|
|
||||||
body = []
|
|
||||||
data_buffer = []
|
|
||||||
add_data = data_buffer.append
|
|
||||||
|
|
||||||
if end_tokens is not None:
|
|
||||||
self._end_token_stack.append(end_tokens)
|
|
||||||
|
|
||||||
def flush_data():
|
|
||||||
if data_buffer:
|
|
||||||
lineno = data_buffer[0].lineno
|
|
||||||
body.append(nodes.Output(data_buffer[:], lineno=lineno))
|
|
||||||
del data_buffer[:]
|
|
||||||
|
|
||||||
try:
|
|
||||||
while self.stream:
|
|
||||||
token = self.stream.current
|
|
||||||
if token.type == 'data':
|
|
||||||
if token.value:
|
|
||||||
add_data(nodes.TemplateData(token.value,
|
|
||||||
lineno=token.lineno))
|
|
||||||
next(self.stream)
|
|
||||||
elif token.type == 'variable_begin':
|
|
||||||
next(self.stream)
|
|
||||||
add_data(self.parse_tuple(with_condexpr=True))
|
|
||||||
self.stream.expect('variable_end')
|
|
||||||
elif token.type == 'block_begin':
|
|
||||||
flush_data()
|
|
||||||
next(self.stream)
|
|
||||||
if end_tokens is not None and \
|
|
||||||
self.stream.current.test_any(*end_tokens):
|
|
||||||
return body
|
|
||||||
rv = self.parse_statement()
|
|
||||||
if isinstance(rv, list):
|
|
||||||
body.extend(rv)
|
|
||||||
else:
|
|
||||||
body.append(rv)
|
|
||||||
self.stream.expect('block_end')
|
|
||||||
else:
|
|
||||||
raise AssertionError('internal parsing error')
|
|
||||||
|
|
||||||
flush_data()
|
|
||||||
finally:
|
|
||||||
if end_tokens is not None:
|
|
||||||
self._end_token_stack.pop()
|
|
||||||
|
|
||||||
return body
|
|
||||||
|
|
||||||
def parse(self):
|
|
||||||
"""Parse the whole template into a `Template` node."""
|
|
||||||
result = nodes.Template(self.subparse(), lineno=1)
|
|
||||||
result.set_environment(self.environment)
|
|
||||||
return result
|
|
||||||
@@ -1,548 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
jinja2.runtime
|
|
||||||
~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Runtime helpers.
|
|
||||||
|
|
||||||
:copyright: (c) 2010 by the Jinja Team.
|
|
||||||
:license: BSD.
|
|
||||||
"""
|
|
||||||
from itertools import chain, imap
|
|
||||||
from jinja2.nodes import EvalContext, _context_function_types
|
|
||||||
from jinja2.utils import Markup, partial, soft_unicode, escape, missing, \
|
|
||||||
concat, internalcode, next, object_type_repr
|
|
||||||
from jinja2.exceptions import UndefinedError, TemplateRuntimeError, \
|
|
||||||
TemplateNotFound
|
|
||||||
|
|
||||||
|
|
||||||
# these variables are exported to the template runtime
|
|
||||||
__all__ = ['LoopContext', 'TemplateReference', 'Macro', 'Markup',
|
|
||||||
'TemplateRuntimeError', 'missing', 'concat', 'escape',
|
|
||||||
'markup_join', 'unicode_join', 'to_string', 'identity',
|
|
||||||
'TemplateNotFound']
|
|
||||||
|
|
||||||
#: the name of the function that is used to convert something into
|
|
||||||
#: a string. 2to3 will adopt that automatically and the generated
|
|
||||||
#: code can take advantage of it.
|
|
||||||
to_string = unicode
|
|
||||||
|
|
||||||
#: the identity function. Useful for certain things in the environment
|
|
||||||
identity = lambda x: x
|
|
||||||
|
|
||||||
|
|
||||||
def markup_join(seq):
|
|
||||||
"""Concatenation that escapes if necessary and converts to unicode."""
|
|
||||||
buf = []
|
|
||||||
iterator = imap(soft_unicode, seq)
|
|
||||||
for arg in iterator:
|
|
||||||
buf.append(arg)
|
|
||||||
if hasattr(arg, '__html__'):
|
|
||||||
return Markup(u'').join(chain(buf, iterator))
|
|
||||||
return concat(buf)
|
|
||||||
|
|
||||||
|
|
||||||
def unicode_join(seq):
|
|
||||||
"""Simple args to unicode conversion and concatenation."""
|
|
||||||
return concat(imap(unicode, seq))
|
|
||||||
|
|
||||||
|
|
||||||
def new_context(environment, template_name, blocks, vars=None,
|
|
||||||
shared=None, globals=None, locals=None):
|
|
||||||
"""Internal helper to for context creation."""
|
|
||||||
if vars is None:
|
|
||||||
vars = {}
|
|
||||||
if shared:
|
|
||||||
parent = vars
|
|
||||||
else:
|
|
||||||
parent = dict(globals or (), **vars)
|
|
||||||
if locals:
|
|
||||||
# if the parent is shared a copy should be created because
|
|
||||||
# we don't want to modify the dict passed
|
|
||||||
if shared:
|
|
||||||
parent = dict(parent)
|
|
||||||
for key, value in locals.iteritems():
|
|
||||||
if key[:2] == 'l_' and value is not missing:
|
|
||||||
parent[key[2:]] = value
|
|
||||||
return Context(environment, parent, template_name, blocks)
|
|
||||||
|
|
||||||
|
|
||||||
class TemplateReference(object):
|
|
||||||
"""The `self` in templates."""
|
|
||||||
|
|
||||||
def __init__(self, context):
|
|
||||||
self.__context = context
|
|
||||||
|
|
||||||
def __getitem__(self, name):
|
|
||||||
blocks = self.__context.blocks[name]
|
|
||||||
return BlockReference(name, self.__context, blocks, 0)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<%s %r>' % (
|
|
||||||
self.__class__.__name__,
|
|
||||||
self.__context.name
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Context(object):
|
|
||||||
"""The template context holds the variables of a template. It stores the
|
|
||||||
values passed to the template and also the names the template exports.
|
|
||||||
Creating instances is neither supported nor useful as it's created
|
|
||||||
automatically at various stages of the template evaluation and should not
|
|
||||||
be created by hand.
|
|
||||||
|
|
||||||
The context is immutable. Modifications on :attr:`parent` **must not**
|
|
||||||
happen and modifications on :attr:`vars` are allowed from generated
|
|
||||||
template code only. Template filters and global functions marked as
|
|
||||||
:func:`contextfunction`\s get the active context passed as first argument
|
|
||||||
and are allowed to access the context read-only.
|
|
||||||
|
|
||||||
The template context supports read only dict operations (`get`,
|
|
||||||
`keys`, `values`, `items`, `iterkeys`, `itervalues`, `iteritems`,
|
|
||||||
`__getitem__`, `__contains__`). Additionally there is a :meth:`resolve`
|
|
||||||
method that doesn't fail with a `KeyError` but returns an
|
|
||||||
:class:`Undefined` object for missing variables.
|
|
||||||
"""
|
|
||||||
__slots__ = ('parent', 'vars', 'environment', 'eval_ctx', 'exported_vars',
|
|
||||||
'name', 'blocks', '__weakref__')
|
|
||||||
|
|
||||||
def __init__(self, environment, parent, name, blocks):
|
|
||||||
self.parent = parent
|
|
||||||
self.vars = {}
|
|
||||||
self.environment = environment
|
|
||||||
self.eval_ctx = EvalContext(self.environment, name)
|
|
||||||
self.exported_vars = set()
|
|
||||||
self.name = name
|
|
||||||
|
|
||||||
# create the initial mapping of blocks. Whenever template inheritance
|
|
||||||
# takes place the runtime will update this mapping with the new blocks
|
|
||||||
# from the template.
|
|
||||||
self.blocks = dict((k, [v]) for k, v in blocks.iteritems())
|
|
||||||
|
|
||||||
def super(self, name, current):
|
|
||||||
"""Render a parent block."""
|
|
||||||
try:
|
|
||||||
blocks = self.blocks[name]
|
|
||||||
index = blocks.index(current) + 1
|
|
||||||
blocks[index]
|
|
||||||
except LookupError:
|
|
||||||
return self.environment.undefined('there is no parent block '
|
|
||||||
'called %r.' % name,
|
|
||||||
name='super')
|
|
||||||
return BlockReference(name, self, blocks, index)
|
|
||||||
|
|
||||||
def get(self, key, default=None):
|
|
||||||
"""Returns an item from the template context, if it doesn't exist
|
|
||||||
`default` is returned.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return self[key]
|
|
||||||
except KeyError:
|
|
||||||
return default
|
|
||||||
|
|
||||||
def resolve(self, key):
|
|
||||||
"""Looks up a variable like `__getitem__` or `get` but returns an
|
|
||||||
:class:`Undefined` object with the name of the name looked up.
|
|
||||||
"""
|
|
||||||
if key in self.vars:
|
|
||||||
return self.vars[key]
|
|
||||||
if key in self.parent:
|
|
||||||
return self.parent[key]
|
|
||||||
return self.environment.undefined(name=key)
|
|
||||||
|
|
||||||
def get_exported(self):
|
|
||||||
"""Get a new dict with the exported variables."""
|
|
||||||
return dict((k, self.vars[k]) for k in self.exported_vars)
|
|
||||||
|
|
||||||
def get_all(self):
|
|
||||||
"""Return a copy of the complete context as dict including the
|
|
||||||
exported variables.
|
|
||||||
"""
|
|
||||||
return dict(self.parent, **self.vars)
|
|
||||||
|
|
||||||
@internalcode
|
|
||||||
def call(__self, __obj, *args, **kwargs):
|
|
||||||
"""Call the callable with the arguments and keyword arguments
|
|
||||||
provided but inject the active context or environment as first
|
|
||||||
argument if the callable is a :func:`contextfunction` or
|
|
||||||
:func:`environmentfunction`.
|
|
||||||
"""
|
|
||||||
if __debug__:
|
|
||||||
__traceback_hide__ = True
|
|
||||||
if isinstance(__obj, _context_function_types):
|
|
||||||
if getattr(__obj, 'contextfunction', 0):
|
|
||||||
args = (__self,) + args
|
|
||||||
elif getattr(__obj, 'evalcontextfunction', 0):
|
|
||||||
args = (__self.eval_ctx,) + args
|
|
||||||
elif getattr(__obj, 'environmentfunction', 0):
|
|
||||||
args = (__self.environment,) + args
|
|
||||||
try:
|
|
||||||
return __obj(*args, **kwargs)
|
|
||||||
except StopIteration:
|
|
||||||
return __self.environment.undefined('value was undefined because '
|
|
||||||
'a callable raised a '
|
|
||||||
'StopIteration exception')
|
|
||||||
|
|
||||||
def derived(self, locals=None):
|
|
||||||
"""Internal helper function to create a derived context."""
|
|
||||||
context = new_context(self.environment, self.name, {},
|
|
||||||
self.parent, True, None, locals)
|
|
||||||
context.vars.update(self.vars)
|
|
||||||
context.eval_ctx = self.eval_ctx
|
|
||||||
context.blocks.update((k, list(v)) for k, v in self.blocks.iteritems())
|
|
||||||
return context
|
|
||||||
|
|
||||||
def _all(meth):
|
|
||||||
proxy = lambda self: getattr(self.get_all(), meth)()
|
|
||||||
proxy.__doc__ = getattr(dict, meth).__doc__
|
|
||||||
proxy.__name__ = meth
|
|
||||||
return proxy
|
|
||||||
|
|
||||||
keys = _all('keys')
|
|
||||||
values = _all('values')
|
|
||||||
items = _all('items')
|
|
||||||
|
|
||||||
# not available on python 3
|
|
||||||
if hasattr(dict, 'iterkeys'):
|
|
||||||
iterkeys = _all('iterkeys')
|
|
||||||
itervalues = _all('itervalues')
|
|
||||||
iteritems = _all('iteritems')
|
|
||||||
del _all
|
|
||||||
|
|
||||||
def __contains__(self, name):
|
|
||||||
return name in self.vars or name in self.parent
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
"""Lookup a variable or raise `KeyError` if the variable is
|
|
||||||
undefined.
|
|
||||||
"""
|
|
||||||
item = self.resolve(key)
|
|
||||||
if isinstance(item, Undefined):
|
|
||||||
raise KeyError(key)
|
|
||||||
return item
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<%s %s of %r>' % (
|
|
||||||
self.__class__.__name__,
|
|
||||||
repr(self.get_all()),
|
|
||||||
self.name
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# register the context as mapping if possible
|
|
||||||
try:
|
|
||||||
from collections import Mapping
|
|
||||||
Mapping.register(Context)
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class BlockReference(object):
|
|
||||||
"""One block on a template reference."""
|
|
||||||
|
|
||||||
def __init__(self, name, context, stack, depth):
|
|
||||||
self.name = name
|
|
||||||
self._context = context
|
|
||||||
self._stack = stack
|
|
||||||
self._depth = depth
|
|
||||||
|
|
||||||
@property
|
|
||||||
def super(self):
|
|
||||||
"""Super the block."""
|
|
||||||
if self._depth + 1 >= len(self._stack):
|
|
||||||
return self._context.environment. \
|
|
||||||
undefined('there is no parent block called %r.' %
|
|
||||||
self.name, name='super')
|
|
||||||
return BlockReference(self.name, self._context, self._stack,
|
|
||||||
self._depth + 1)
|
|
||||||
|
|
||||||
@internalcode
|
|
||||||
def __call__(self):
|
|
||||||
rv = concat(self._stack[self._depth](self._context))
|
|
||||||
if self._context.eval_ctx.autoescape:
|
|
||||||
rv = Markup(rv)
|
|
||||||
return rv
|
|
||||||
|
|
||||||
|
|
||||||
class LoopContext(object):
|
|
||||||
"""A loop context for dynamic iteration."""
|
|
||||||
|
|
||||||
def __init__(self, iterable, recurse=None):
|
|
||||||
self._iterator = iter(iterable)
|
|
||||||
self._recurse = recurse
|
|
||||||
self.index0 = -1
|
|
||||||
|
|
||||||
# try to get the length of the iterable early. This must be done
|
|
||||||
# here because there are some broken iterators around where there
|
|
||||||
# __len__ is the number of iterations left (i'm looking at your
|
|
||||||
# listreverseiterator!).
|
|
||||||
try:
|
|
||||||
self._length = len(iterable)
|
|
||||||
except (TypeError, AttributeError):
|
|
||||||
self._length = None
|
|
||||||
|
|
||||||
def cycle(self, *args):
|
|
||||||
"""Cycles among the arguments with the current loop index."""
|
|
||||||
if not args:
|
|
||||||
raise TypeError('no items for cycling given')
|
|
||||||
return args[self.index0 % len(args)]
|
|
||||||
|
|
||||||
first = property(lambda x: x.index0 == 0)
|
|
||||||
last = property(lambda x: x.index0 + 1 == x.length)
|
|
||||||
index = property(lambda x: x.index0 + 1)
|
|
||||||
revindex = property(lambda x: x.length - x.index0)
|
|
||||||
revindex0 = property(lambda x: x.length - x.index)
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return self.length
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return LoopContextIterator(self)
|
|
||||||
|
|
||||||
@internalcode
|
|
||||||
def loop(self, iterable):
|
|
||||||
if self._recurse is None:
|
|
||||||
raise TypeError('Tried to call non recursive loop. Maybe you '
|
|
||||||
"forgot the 'recursive' modifier.")
|
|
||||||
return self._recurse(iterable, self._recurse)
|
|
||||||
|
|
||||||
# a nifty trick to enhance the error message if someone tried to call
|
|
||||||
# the the loop without or with too many arguments.
|
|
||||||
__call__ = loop
|
|
||||||
del loop
|
|
||||||
|
|
||||||
@property
|
|
||||||
def length(self):
|
|
||||||
if self._length is None:
|
|
||||||
# if was not possible to get the length of the iterator when
|
|
||||||
# the loop context was created (ie: iterating over a generator)
|
|
||||||
# we have to convert the iterable into a sequence and use the
|
|
||||||
# length of that.
|
|
||||||
iterable = tuple(self._iterator)
|
|
||||||
self._iterator = iter(iterable)
|
|
||||||
self._length = len(iterable) + self.index0 + 1
|
|
||||||
return self._length
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<%s %r/%r>' % (
|
|
||||||
self.__class__.__name__,
|
|
||||||
self.index,
|
|
||||||
self.length
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class LoopContextIterator(object):
|
|
||||||
"""The iterator for a loop context."""
|
|
||||||
__slots__ = ('context',)
|
|
||||||
|
|
||||||
def __init__(self, context):
|
|
||||||
self.context = context
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def next(self):
|
|
||||||
ctx = self.context
|
|
||||||
ctx.index0 += 1
|
|
||||||
return next(ctx._iterator), ctx
|
|
||||||
|
|
||||||
|
|
||||||
class Macro(object):
|
|
||||||
"""Wraps a macro function."""
|
|
||||||
|
|
||||||
def __init__(self, environment, func, name, arguments, defaults,
|
|
||||||
catch_kwargs, catch_varargs, caller):
|
|
||||||
self._environment = environment
|
|
||||||
self._func = func
|
|
||||||
self._argument_count = len(arguments)
|
|
||||||
self.name = name
|
|
||||||
self.arguments = arguments
|
|
||||||
self.defaults = defaults
|
|
||||||
self.catch_kwargs = catch_kwargs
|
|
||||||
self.catch_varargs = catch_varargs
|
|
||||||
self.caller = caller
|
|
||||||
|
|
||||||
@internalcode
|
|
||||||
def __call__(self, *args, **kwargs):
|
|
||||||
# try to consume the positional arguments
|
|
||||||
arguments = list(args[:self._argument_count])
|
|
||||||
off = len(arguments)
|
|
||||||
|
|
||||||
# if the number of arguments consumed is not the number of
|
|
||||||
# arguments expected we start filling in keyword arguments
|
|
||||||
# and defaults.
|
|
||||||
if off != self._argument_count:
|
|
||||||
for idx, name in enumerate(self.arguments[len(arguments):]):
|
|
||||||
try:
|
|
||||||
value = kwargs.pop(name)
|
|
||||||
except KeyError:
|
|
||||||
try:
|
|
||||||
value = self.defaults[idx - self._argument_count + off]
|
|
||||||
except IndexError:
|
|
||||||
value = self._environment.undefined(
|
|
||||||
'parameter %r was not provided' % name, name=name)
|
|
||||||
arguments.append(value)
|
|
||||||
|
|
||||||
# it's important that the order of these arguments does not change
|
|
||||||
# if not also changed in the compiler's `function_scoping` method.
|
|
||||||
# the order is caller, keyword arguments, positional arguments!
|
|
||||||
if self.caller:
|
|
||||||
caller = kwargs.pop('caller', None)
|
|
||||||
if caller is None:
|
|
||||||
caller = self._environment.undefined('No caller defined',
|
|
||||||
name='caller')
|
|
||||||
arguments.append(caller)
|
|
||||||
if self.catch_kwargs:
|
|
||||||
arguments.append(kwargs)
|
|
||||||
elif kwargs:
|
|
||||||
raise TypeError('macro %r takes no keyword argument %r' %
|
|
||||||
(self.name, next(iter(kwargs))))
|
|
||||||
if self.catch_varargs:
|
|
||||||
arguments.append(args[self._argument_count:])
|
|
||||||
elif len(args) > self._argument_count:
|
|
||||||
raise TypeError('macro %r takes not more than %d argument(s)' %
|
|
||||||
(self.name, len(self.arguments)))
|
|
||||||
return self._func(*arguments)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<%s %s>' % (
|
|
||||||
self.__class__.__name__,
|
|
||||||
self.name is None and 'anonymous' or repr(self.name)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Undefined(object):
|
|
||||||
"""The default undefined type. This undefined type can be printed and
|
|
||||||
iterated over, but every other access will raise an :exc:`UndefinedError`:
|
|
||||||
|
|
||||||
>>> foo = Undefined(name='foo')
|
|
||||||
>>> str(foo)
|
|
||||||
''
|
|
||||||
>>> not foo
|
|
||||||
True
|
|
||||||
>>> foo + 42
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
UndefinedError: 'foo' is undefined
|
|
||||||
"""
|
|
||||||
__slots__ = ('_undefined_hint', '_undefined_obj', '_undefined_name',
|
|
||||||
'_undefined_exception')
|
|
||||||
|
|
||||||
def __init__(self, hint=None, obj=missing, name=None, exc=UndefinedError):
|
|
||||||
self._undefined_hint = hint
|
|
||||||
self._undefined_obj = obj
|
|
||||||
self._undefined_name = name
|
|
||||||
self._undefined_exception = exc
|
|
||||||
|
|
||||||
@internalcode
|
|
||||||
def _fail_with_undefined_error(self, *args, **kwargs):
|
|
||||||
"""Regular callback function for undefined objects that raises an
|
|
||||||
`UndefinedError` on call.
|
|
||||||
"""
|
|
||||||
if self._undefined_hint is None:
|
|
||||||
if self._undefined_obj is missing:
|
|
||||||
hint = '%r is undefined' % self._undefined_name
|
|
||||||
elif not isinstance(self._undefined_name, basestring):
|
|
||||||
hint = '%s has no element %r' % (
|
|
||||||
object_type_repr(self._undefined_obj),
|
|
||||||
self._undefined_name
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
hint = '%r has no attribute %r' % (
|
|
||||||
object_type_repr(self._undefined_obj),
|
|
||||||
self._undefined_name
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
hint = self._undefined_hint
|
|
||||||
raise self._undefined_exception(hint)
|
|
||||||
|
|
||||||
@internalcode
|
|
||||||
def __getattr__(self, name):
|
|
||||||
if name[:2] == '__':
|
|
||||||
raise AttributeError(name)
|
|
||||||
return self._fail_with_undefined_error()
|
|
||||||
|
|
||||||
__add__ = __radd__ = __mul__ = __rmul__ = __div__ = __rdiv__ = \
|
|
||||||
__truediv__ = __rtruediv__ = __floordiv__ = __rfloordiv__ = \
|
|
||||||
__mod__ = __rmod__ = __pos__ = __neg__ = __call__ = \
|
|
||||||
__getitem__ = __lt__ = __le__ = __gt__ = __ge__ = __int__ = \
|
|
||||||
__float__ = __complex__ = __pow__ = __rpow__ = \
|
|
||||||
_fail_with_undefined_error
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return unicode(self).encode('utf-8')
|
|
||||||
|
|
||||||
# unicode goes after __str__ because we configured 2to3 to rename
|
|
||||||
# __unicode__ to __str__. because the 2to3 tree is not designed to
|
|
||||||
# remove nodes from it, we leave the above __str__ around and let
|
|
||||||
# it override at runtime.
|
|
||||||
def __unicode__(self):
|
|
||||||
return u''
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
if 0:
|
|
||||||
yield None
|
|
||||||
|
|
||||||
def __nonzero__(self):
|
|
||||||
return False
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'Undefined'
|
|
||||||
|
|
||||||
|
|
||||||
class DebugUndefined(Undefined):
|
|
||||||
"""An undefined that returns the debug info when printed.
|
|
||||||
|
|
||||||
>>> foo = DebugUndefined(name='foo')
|
|
||||||
>>> str(foo)
|
|
||||||
'{{ foo }}'
|
|
||||||
>>> not foo
|
|
||||||
True
|
|
||||||
>>> foo + 42
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
UndefinedError: 'foo' is undefined
|
|
||||||
"""
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
if self._undefined_hint is None:
|
|
||||||
if self._undefined_obj is missing:
|
|
||||||
return u'{{ %s }}' % self._undefined_name
|
|
||||||
return '{{ no such element: %s[%r] }}' % (
|
|
||||||
object_type_repr(self._undefined_obj),
|
|
||||||
self._undefined_name
|
|
||||||
)
|
|
||||||
return u'{{ undefined value printed: %s }}' % self._undefined_hint
|
|
||||||
|
|
||||||
|
|
||||||
class StrictUndefined(Undefined):
|
|
||||||
"""An undefined that barks on print and iteration as well as boolean
|
|
||||||
tests and all kinds of comparisons. In other words: you can do nothing
|
|
||||||
with it except checking if it's defined using the `defined` test.
|
|
||||||
|
|
||||||
>>> foo = StrictUndefined(name='foo')
|
|
||||||
>>> str(foo)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
UndefinedError: 'foo' is undefined
|
|
||||||
>>> not foo
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
UndefinedError: 'foo' is undefined
|
|
||||||
>>> foo + 42
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
UndefinedError: 'foo' is undefined
|
|
||||||
"""
|
|
||||||
__slots__ = ()
|
|
||||||
__iter__ = __unicode__ = __str__ = __len__ = __nonzero__ = __eq__ = \
|
|
||||||
__ne__ = __bool__ = Undefined._fail_with_undefined_error
|
|
||||||
|
|
||||||
|
|
||||||
# remove remaining slots attributes, after the metaclass did the magic they
|
|
||||||
# are unneeded and irritating as they contain wrong data for the subclasses.
|
|
||||||
del Undefined.__slots__, DebugUndefined.__slots__, StrictUndefined.__slots__
|
|
||||||
@@ -1,361 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
jinja2.sandbox
|
|
||||||
~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Adds a sandbox layer to Jinja as it was the default behavior in the old
|
|
||||||
Jinja 1 releases. This sandbox is slightly different from Jinja 1 as the
|
|
||||||
default behavior is easier to use.
|
|
||||||
|
|
||||||
The behavior can be changed by subclassing the environment.
|
|
||||||
|
|
||||||
:copyright: (c) 2010 by the Jinja Team.
|
|
||||||
:license: BSD.
|
|
||||||
"""
|
|
||||||
import operator
|
|
||||||
from jinja2.environment import Environment
|
|
||||||
from jinja2.exceptions import SecurityError
|
|
||||||
from jinja2.utils import FunctionType, MethodType, TracebackType, CodeType, \
|
|
||||||
FrameType, GeneratorType
|
|
||||||
|
|
||||||
|
|
||||||
#: maximum number of items a range may produce
|
|
||||||
MAX_RANGE = 100000
|
|
||||||
|
|
||||||
#: attributes of function objects that are considered unsafe.
|
|
||||||
UNSAFE_FUNCTION_ATTRIBUTES = set(['func_closure', 'func_code', 'func_dict',
|
|
||||||
'func_defaults', 'func_globals'])
|
|
||||||
|
|
||||||
#: unsafe method attributes. function attributes are unsafe for methods too
|
|
||||||
UNSAFE_METHOD_ATTRIBUTES = set(['im_class', 'im_func', 'im_self'])
|
|
||||||
|
|
||||||
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
# make sure we don't warn in python 2.6 about stuff we don't care about
|
|
||||||
warnings.filterwarnings('ignore', 'the sets module', DeprecationWarning,
|
|
||||||
module='jinja2.sandbox')
|
|
||||||
|
|
||||||
from collections import deque
|
|
||||||
|
|
||||||
_mutable_set_types = (set,)
|
|
||||||
_mutable_mapping_types = (dict,)
|
|
||||||
_mutable_sequence_types = (list,)
|
|
||||||
|
|
||||||
|
|
||||||
# on python 2.x we can register the user collection types
|
|
||||||
try:
|
|
||||||
from UserDict import UserDict, DictMixin
|
|
||||||
from UserList import UserList
|
|
||||||
_mutable_mapping_types += (UserDict, DictMixin)
|
|
||||||
_mutable_set_types += (UserList,)
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# if sets is still available, register the mutable set from there as well
|
|
||||||
try:
|
|
||||||
from sets import Set
|
|
||||||
_mutable_set_types += (Set,)
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
#: register Python 2.6 abstract base classes
|
|
||||||
try:
|
|
||||||
from collections import MutableSet, MutableMapping, MutableSequence
|
|
||||||
_mutable_set_types += (MutableSet,)
|
|
||||||
_mutable_mapping_types += (MutableMapping,)
|
|
||||||
_mutable_sequence_types += (MutableSequence,)
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
_mutable_spec = (
|
|
||||||
(_mutable_set_types, frozenset([
|
|
||||||
'add', 'clear', 'difference_update', 'discard', 'pop', 'remove',
|
|
||||||
'symmetric_difference_update', 'update'
|
|
||||||
])),
|
|
||||||
(_mutable_mapping_types, frozenset([
|
|
||||||
'clear', 'pop', 'popitem', 'setdefault', 'update'
|
|
||||||
])),
|
|
||||||
(_mutable_sequence_types, frozenset([
|
|
||||||
'append', 'reverse', 'insert', 'sort', 'extend', 'remove'
|
|
||||||
])),
|
|
||||||
(deque, frozenset([
|
|
||||||
'append', 'appendleft', 'clear', 'extend', 'extendleft', 'pop',
|
|
||||||
'popleft', 'remove', 'rotate'
|
|
||||||
]))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def safe_range(*args):
|
|
||||||
"""A range that can't generate ranges with a length of more than
|
|
||||||
MAX_RANGE items.
|
|
||||||
"""
|
|
||||||
rng = xrange(*args)
|
|
||||||
if len(rng) > MAX_RANGE:
|
|
||||||
raise OverflowError('range too big, maximum size for range is %d' %
|
|
||||||
MAX_RANGE)
|
|
||||||
return rng
|
|
||||||
|
|
||||||
|
|
||||||
def unsafe(f):
|
|
||||||
"""Marks a function or method as unsafe.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
@unsafe
|
|
||||||
def delete(self):
|
|
||||||
pass
|
|
||||||
"""
|
|
||||||
f.unsafe_callable = True
|
|
||||||
return f
|
|
||||||
|
|
||||||
|
|
||||||
def is_internal_attribute(obj, attr):
|
|
||||||
"""Test if the attribute given is an internal python attribute. For
|
|
||||||
example this function returns `True` for the `func_code` attribute of
|
|
||||||
python objects. This is useful if the environment method
|
|
||||||
:meth:`~SandboxedEnvironment.is_safe_attribute` is overriden.
|
|
||||||
|
|
||||||
>>> from jinja2.sandbox import is_internal_attribute
|
|
||||||
>>> is_internal_attribute(lambda: None, "func_code")
|
|
||||||
True
|
|
||||||
>>> is_internal_attribute((lambda x:x).func_code, 'co_code')
|
|
||||||
True
|
|
||||||
>>> is_internal_attribute(str, "upper")
|
|
||||||
False
|
|
||||||
"""
|
|
||||||
if isinstance(obj, FunctionType):
|
|
||||||
if attr in UNSAFE_FUNCTION_ATTRIBUTES:
|
|
||||||
return True
|
|
||||||
elif isinstance(obj, MethodType):
|
|
||||||
if attr in UNSAFE_FUNCTION_ATTRIBUTES or \
|
|
||||||
attr in UNSAFE_METHOD_ATTRIBUTES:
|
|
||||||
return True
|
|
||||||
elif isinstance(obj, type):
|
|
||||||
if attr == 'mro':
|
|
||||||
return True
|
|
||||||
elif isinstance(obj, (CodeType, TracebackType, FrameType)):
|
|
||||||
return True
|
|
||||||
elif isinstance(obj, GeneratorType):
|
|
||||||
if attr == 'gi_frame':
|
|
||||||
return True
|
|
||||||
return attr.startswith('__')
|
|
||||||
|
|
||||||
|
|
||||||
def modifies_known_mutable(obj, attr):
|
|
||||||
"""This function checks if an attribute on a builtin mutable object
|
|
||||||
(list, dict, set or deque) would modify it if called. It also supports
|
|
||||||
the "user"-versions of the objects (`sets.Set`, `UserDict.*` etc.) and
|
|
||||||
with Python 2.6 onwards the abstract base classes `MutableSet`,
|
|
||||||
`MutableMapping`, and `MutableSequence`.
|
|
||||||
|
|
||||||
>>> modifies_known_mutable({}, "clear")
|
|
||||||
True
|
|
||||||
>>> modifies_known_mutable({}, "keys")
|
|
||||||
False
|
|
||||||
>>> modifies_known_mutable([], "append")
|
|
||||||
True
|
|
||||||
>>> modifies_known_mutable([], "index")
|
|
||||||
False
|
|
||||||
|
|
||||||
If called with an unsupported object (such as unicode) `False` is
|
|
||||||
returned.
|
|
||||||
|
|
||||||
>>> modifies_known_mutable("foo", "upper")
|
|
||||||
False
|
|
||||||
"""
|
|
||||||
for typespec, unsafe in _mutable_spec:
|
|
||||||
if isinstance(obj, typespec):
|
|
||||||
return attr in unsafe
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class SandboxedEnvironment(Environment):
|
|
||||||
"""The sandboxed environment. It works like the regular environment but
|
|
||||||
tells the compiler to generate sandboxed code. Additionally subclasses of
|
|
||||||
this environment may override the methods that tell the runtime what
|
|
||||||
attributes or functions are safe to access.
|
|
||||||
|
|
||||||
If the template tries to access insecure code a :exc:`SecurityError` is
|
|
||||||
raised. However also other exceptions may occour during the rendering so
|
|
||||||
the caller has to ensure that all exceptions are catched.
|
|
||||||
"""
|
|
||||||
sandboxed = True
|
|
||||||
|
|
||||||
#: default callback table for the binary operators. A copy of this is
|
|
||||||
#: available on each instance of a sandboxed environment as
|
|
||||||
#: :attr:`binop_table`
|
|
||||||
default_binop_table = {
|
|
||||||
'+': operator.add,
|
|
||||||
'-': operator.sub,
|
|
||||||
'*': operator.mul,
|
|
||||||
'/': operator.truediv,
|
|
||||||
'//': operator.floordiv,
|
|
||||||
'**': operator.pow,
|
|
||||||
'%': operator.mod
|
|
||||||
}
|
|
||||||
|
|
||||||
#: default callback table for the unary operators. A copy of this is
|
|
||||||
#: available on each instance of a sandboxed environment as
|
|
||||||
#: :attr:`unop_table`
|
|
||||||
default_unop_table = {
|
|
||||||
'+': operator.pos,
|
|
||||||
'-': operator.neg
|
|
||||||
}
|
|
||||||
|
|
||||||
#: a set of binary operators that should be intercepted. Each operator
|
|
||||||
#: that is added to this set (empty by default) is delegated to the
|
|
||||||
#: :meth:`call_binop` method that will perform the operator. The default
|
|
||||||
#: operator callback is specified by :attr:`binop_table`.
|
|
||||||
#:
|
|
||||||
#: The following binary operators are interceptable:
|
|
||||||
#: ``//``, ``%``, ``+``, ``*``, ``-``, ``/``, and ``**``
|
|
||||||
#:
|
|
||||||
#: The default operation form the operator table corresponds to the
|
|
||||||
#: builtin function. Intercepted calls are always slower than the native
|
|
||||||
#: operator call, so make sure only to intercept the ones you are
|
|
||||||
#: interested in.
|
|
||||||
#:
|
|
||||||
#: .. versionadded:: 2.6
|
|
||||||
intercepted_binops = frozenset()
|
|
||||||
|
|
||||||
#: a set of unary operators that should be intercepted. Each operator
|
|
||||||
#: that is added to this set (empty by default) is delegated to the
|
|
||||||
#: :meth:`call_unop` method that will perform the operator. The default
|
|
||||||
#: operator callback is specified by :attr:`unop_table`.
|
|
||||||
#:
|
|
||||||
#: The following unary operators are interceptable: ``+``, ``-``
|
|
||||||
#:
|
|
||||||
#: The default operation form the operator table corresponds to the
|
|
||||||
#: builtin function. Intercepted calls are always slower than the native
|
|
||||||
#: operator call, so make sure only to intercept the ones you are
|
|
||||||
#: interested in.
|
|
||||||
#:
|
|
||||||
#: .. versionadded:: 2.6
|
|
||||||
intercepted_unops = frozenset()
|
|
||||||
|
|
||||||
def intercept_unop(self, operator):
|
|
||||||
"""Called during template compilation with the name of a unary
|
|
||||||
operator to check if it should be intercepted at runtime. If this
|
|
||||||
method returns `True`, :meth:`call_unop` is excuted for this unary
|
|
||||||
operator. The default implementation of :meth:`call_unop` will use
|
|
||||||
the :attr:`unop_table` dictionary to perform the operator with the
|
|
||||||
same logic as the builtin one.
|
|
||||||
|
|
||||||
The following unary operators are interceptable: ``+`` and ``-``
|
|
||||||
|
|
||||||
Intercepted calls are always slower than the native operator call,
|
|
||||||
so make sure only to intercept the ones you are interested in.
|
|
||||||
|
|
||||||
.. versionadded:: 2.6
|
|
||||||
"""
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
Environment.__init__(self, *args, **kwargs)
|
|
||||||
self.globals['range'] = safe_range
|
|
||||||
self.binop_table = self.default_binop_table.copy()
|
|
||||||
self.unop_table = self.default_unop_table.copy()
|
|
||||||
|
|
||||||
def is_safe_attribute(self, obj, attr, value):
|
|
||||||
"""The sandboxed environment will call this method to check if the
|
|
||||||
attribute of an object is safe to access. Per default all attributes
|
|
||||||
starting with an underscore are considered private as well as the
|
|
||||||
special attributes of internal python objects as returned by the
|
|
||||||
:func:`is_internal_attribute` function.
|
|
||||||
"""
|
|
||||||
return not (attr.startswith('_') or is_internal_attribute(obj, attr))
|
|
||||||
|
|
||||||
def is_safe_callable(self, obj):
|
|
||||||
"""Check if an object is safely callable. Per default a function is
|
|
||||||
considered safe unless the `unsafe_callable` attribute exists and is
|
|
||||||
True. Override this method to alter the behavior, but this won't
|
|
||||||
affect the `unsafe` decorator from this module.
|
|
||||||
"""
|
|
||||||
return not (getattr(obj, 'unsafe_callable', False) or
|
|
||||||
getattr(obj, 'alters_data', False))
|
|
||||||
|
|
||||||
def call_binop(self, context, operator, left, right):
|
|
||||||
"""For intercepted binary operator calls (:meth:`intercepted_binops`)
|
|
||||||
this function is executed instead of the builtin operator. This can
|
|
||||||
be used to fine tune the behavior of certain operators.
|
|
||||||
|
|
||||||
.. versionadded:: 2.6
|
|
||||||
"""
|
|
||||||
return self.binop_table[operator](left, right)
|
|
||||||
|
|
||||||
def call_unop(self, context, operator, arg):
|
|
||||||
"""For intercepted unary operator calls (:meth:`intercepted_unops`)
|
|
||||||
this function is executed instead of the builtin operator. This can
|
|
||||||
be used to fine tune the behavior of certain operators.
|
|
||||||
|
|
||||||
.. versionadded:: 2.6
|
|
||||||
"""
|
|
||||||
return self.unop_table[operator](arg)
|
|
||||||
|
|
||||||
def getitem(self, obj, argument):
|
|
||||||
"""Subscribe an object from sandboxed code."""
|
|
||||||
try:
|
|
||||||
return obj[argument]
|
|
||||||
except (TypeError, LookupError):
|
|
||||||
if isinstance(argument, basestring):
|
|
||||||
try:
|
|
||||||
attr = str(argument)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
value = getattr(obj, attr)
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
if self.is_safe_attribute(obj, argument, value):
|
|
||||||
return value
|
|
||||||
return self.unsafe_undefined(obj, argument)
|
|
||||||
return self.undefined(obj=obj, name=argument)
|
|
||||||
|
|
||||||
def getattr(self, obj, attribute):
|
|
||||||
"""Subscribe an object from sandboxed code and prefer the
|
|
||||||
attribute. The attribute passed *must* be a bytestring.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
value = getattr(obj, attribute)
|
|
||||||
except AttributeError:
|
|
||||||
try:
|
|
||||||
return obj[attribute]
|
|
||||||
except (TypeError, LookupError):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
if self.is_safe_attribute(obj, attribute, value):
|
|
||||||
return value
|
|
||||||
return self.unsafe_undefined(obj, attribute)
|
|
||||||
return self.undefined(obj=obj, name=attribute)
|
|
||||||
|
|
||||||
def unsafe_undefined(self, obj, attribute):
|
|
||||||
"""Return an undefined object for unsafe attributes."""
|
|
||||||
return self.undefined('access to attribute %r of %r '
|
|
||||||
'object is unsafe.' % (
|
|
||||||
attribute,
|
|
||||||
obj.__class__.__name__
|
|
||||||
), name=attribute, obj=obj, exc=SecurityError)
|
|
||||||
|
|
||||||
def call(__self, __context, __obj, *args, **kwargs):
|
|
||||||
"""Call an object from sandboxed code."""
|
|
||||||
# the double prefixes are to avoid double keyword argument
|
|
||||||
# errors when proxying the call.
|
|
||||||
if not __self.is_safe_callable(__obj):
|
|
||||||
raise SecurityError('%r is not safely callable' % (__obj,))
|
|
||||||
return __context.call(__obj, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class ImmutableSandboxedEnvironment(SandboxedEnvironment):
|
|
||||||
"""Works exactly like the regular `SandboxedEnvironment` but does not
|
|
||||||
permit modifications on the builtin mutable objects `list`, `set`, and
|
|
||||||
`dict` by using the :func:`modifies_known_mutable` function.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def is_safe_attribute(self, obj, attr, value):
|
|
||||||
if not SandboxedEnvironment.is_safe_attribute(self, obj, attr, value):
|
|
||||||
return False
|
|
||||||
return not modifies_known_mutable(obj, attr)
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
jinja2.tests
|
|
||||||
~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Jinja test functions. Used with the "is" operator.
|
|
||||||
|
|
||||||
:copyright: (c) 2010 by the Jinja Team.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
import re
|
|
||||||
from jinja2.runtime import Undefined
|
|
||||||
|
|
||||||
try:
|
|
||||||
from collections import Mapping as MappingType
|
|
||||||
except ImportError:
|
|
||||||
import UserDict
|
|
||||||
MappingType = (UserDict.UserDict, UserDict.DictMixin, dict)
|
|
||||||
|
|
||||||
# nose, nothing here to test
|
|
||||||
__test__ = False
|
|
||||||
|
|
||||||
|
|
||||||
number_re = re.compile(r'^-?\d+(\.\d+)?$')
|
|
||||||
regex_type = type(number_re)
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
test_callable = callable
|
|
||||||
except NameError:
|
|
||||||
def test_callable(x):
|
|
||||||
return hasattr(x, '__call__')
|
|
||||||
|
|
||||||
|
|
||||||
def test_odd(value):
|
|
||||||
"""Return true if the variable is odd."""
|
|
||||||
return value % 2 == 1
|
|
||||||
|
|
||||||
|
|
||||||
def test_even(value):
|
|
||||||
"""Return true if the variable is even."""
|
|
||||||
return value % 2 == 0
|
|
||||||
|
|
||||||
|
|
||||||
def test_divisibleby(value, num):
|
|
||||||
"""Check if a variable is divisible by a number."""
|
|
||||||
return value % num == 0
|
|
||||||
|
|
||||||
|
|
||||||
def test_defined(value):
|
|
||||||
"""Return true if the variable is defined:
|
|
||||||
|
|
||||||
.. sourcecode:: jinja
|
|
||||||
|
|
||||||
{% if variable is defined %}
|
|
||||||
value of variable: {{ variable }}
|
|
||||||
{% else %}
|
|
||||||
variable is not defined
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
See the :func:`default` filter for a simple way to set undefined
|
|
||||||
variables.
|
|
||||||
"""
|
|
||||||
return not isinstance(value, Undefined)
|
|
||||||
|
|
||||||
|
|
||||||
def test_undefined(value):
|
|
||||||
"""Like :func:`defined` but the other way round."""
|
|
||||||
return isinstance(value, Undefined)
|
|
||||||
|
|
||||||
|
|
||||||
def test_none(value):
|
|
||||||
"""Return true if the variable is none."""
|
|
||||||
return value is None
|
|
||||||
|
|
||||||
|
|
||||||
def test_lower(value):
|
|
||||||
"""Return true if the variable is lowercased."""
|
|
||||||
return unicode(value).islower()
|
|
||||||
|
|
||||||
|
|
||||||
def test_upper(value):
|
|
||||||
"""Return true if the variable is uppercased."""
|
|
||||||
return unicode(value).isupper()
|
|
||||||
|
|
||||||
|
|
||||||
def test_string(value):
|
|
||||||
"""Return true if the object is a string."""
|
|
||||||
return isinstance(value, basestring)
|
|
||||||
|
|
||||||
|
|
||||||
def test_mapping(value):
|
|
||||||
"""Return true if the object is a mapping (dict etc.).
|
|
||||||
|
|
||||||
.. versionadded:: 2.6
|
|
||||||
"""
|
|
||||||
return isinstance(value, MappingType)
|
|
||||||
|
|
||||||
|
|
||||||
def test_number(value):
|
|
||||||
"""Return true if the variable is a number."""
|
|
||||||
return isinstance(value, (int, long, float, complex))
|
|
||||||
|
|
||||||
|
|
||||||
def test_sequence(value):
|
|
||||||
"""Return true if the variable is a sequence. Sequences are variables
|
|
||||||
that are iterable.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
len(value)
|
|
||||||
value.__getitem__
|
|
||||||
except:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def test_sameas(value, other):
|
|
||||||
"""Check if an object points to the same memory address than another
|
|
||||||
object:
|
|
||||||
|
|
||||||
.. sourcecode:: jinja
|
|
||||||
|
|
||||||
{% if foo.attribute is sameas false %}
|
|
||||||
the foo attribute really is the `False` singleton
|
|
||||||
{% endif %}
|
|
||||||
"""
|
|
||||||
return value is other
|
|
||||||
|
|
||||||
|
|
||||||
def test_iterable(value):
|
|
||||||
"""Check if it's possible to iterate over an object."""
|
|
||||||
try:
|
|
||||||
iter(value)
|
|
||||||
except TypeError:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def test_escaped(value):
|
|
||||||
"""Check if the value is escaped."""
|
|
||||||
return hasattr(value, '__html__')
|
|
||||||
|
|
||||||
|
|
||||||
TESTS = {
|
|
||||||
'odd': test_odd,
|
|
||||||
'even': test_even,
|
|
||||||
'divisibleby': test_divisibleby,
|
|
||||||
'defined': test_defined,
|
|
||||||
'undefined': test_undefined,
|
|
||||||
'none': test_none,
|
|
||||||
'lower': test_lower,
|
|
||||||
'upper': test_upper,
|
|
||||||
'string': test_string,
|
|
||||||
'mapping': test_mapping,
|
|
||||||
'number': test_number,
|
|
||||||
'sequence': test_sequence,
|
|
||||||
'iterable': test_iterable,
|
|
||||||
'callable': test_callable,
|
|
||||||
'sameas': test_sameas,
|
|
||||||
'escaped': test_escaped
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
# -*- 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
|
|
||||||
@@ -1,245 +0,0 @@
|
|||||||
# -*- 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
|
|
||||||
@@ -1,285 +0,0 @@
|
|||||||
# -*- 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
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
# -*- 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
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# -*- 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
|
|
||||||
@@ -1,455 +0,0 @@
|
|||||||
# -*- 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
|
|
||||||
@@ -1,356 +0,0 @@
|
|||||||
# -*- 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()
|
|
||||||
assert out == (
|
|
||||||
'100 Bytes|0.0 kB|0.0 MB|0.0 GB|0.0 TB|100 Bytes|'
|
|
||||||
'1000 Bytes|1.0 KiB|0.9 MiB|0.9 GiB'
|
|
||||||
)
|
|
||||||
|
|
||||||
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"
|
|
||||||
|
|
||||||
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 suite():
|
|
||||||
suite = unittest.TestSuite()
|
|
||||||
suite.addTest(unittest.makeSuite(FilterTestCase))
|
|
||||||
return suite
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
# -*- 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
|
|
||||||
@@ -1,227 +0,0 @@
|
|||||||
# -*- 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
|
|
||||||
@@ -1,387 +0,0 @@
|
|||||||
# -*- 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 suposed 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 hypens, 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_contant_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 }}')
|
|
||||||
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
|
|
||||||
@@ -1,190 +0,0 @@
|
|||||||
# -*- 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 suite():
|
|
||||||
suite = unittest.TestSuite()
|
|
||||||
suite.addTest(unittest.makeSuite(LoaderTestCase))
|
|
||||||
suite.addTest(unittest.makeSuite(ModuleLoaderTestCase))
|
|
||||||
return suite
|
|
||||||
Loaded 100 of 157 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user