Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/websdk/werkzeug/contrib/jsrouting.py
diff options
context:
space:
mode:
Diffstat (limited to 'websdk/werkzeug/contrib/jsrouting.py')
-rw-r--r--websdk/werkzeug/contrib/jsrouting.py259
1 files changed, 259 insertions, 0 deletions
diff --git a/websdk/werkzeug/contrib/jsrouting.py b/websdk/werkzeug/contrib/jsrouting.py
new file mode 100644
index 0000000..9b7d0c0
--- /dev/null
+++ b/websdk/werkzeug/contrib/jsrouting.py
@@ -0,0 +1,259 @@
+# -*- coding: utf-8 -*-
+"""
+ werkzeug.contrib.jsrouting
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Addon module that allows to create a JavaScript function from a map
+ that generates rules.
+
+ :copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+try:
+ from simplejson import dumps
+except ImportError:
+ try:
+ from json import dumps
+ except ImportError:
+ def dumps(*args):
+ raise RuntimeError('simplejson required for jsrouting')
+
+from inspect import getmro
+from werkzeug.routing import NumberConverter
+
+
+def render_template(name_parts, rules, converters):
+ result = u''
+ if name_parts:
+ for idx in xrange(0, len(name_parts) - 1):
+ name = u'.'.join(name_parts[:idx + 1])
+ result += u"if (typeof %s === 'undefined') %s = {}\n" % (name, name)
+ result += '%s = ' % '.'.join(name_parts)
+ result += """(function (server_name, script_name, subdomain, url_scheme) {
+ var converters = %(converters)s;
+ var rules = $rules;
+ function in_array(array, value) {
+ if (array.indexOf != undefined) {
+ return array.indexOf(value) != -1;
+ }
+ for (var i = 0; i < array.length; i++) {
+ if (array[i] == value) {
+ return true;
+ }
+ }
+ return false;
+ }
+ function array_diff(array1, array2) {
+ array1 = array1.slice();
+ for (var i = array1.length-1; i >= 0; i--) {
+ if (in_array(array2, array1[i])) {
+ array1.splice(i, 1);
+ }
+ }
+ return array1;
+ }
+ function split_obj(obj) {
+ var names = [];
+ var values = [];
+ for (var name in obj) {
+ if (typeof(obj[name]) != 'function') {
+ names.push(name);
+ values.push(obj[name]);
+ }
+ }
+ return {names: names, values: values, original: obj};
+ }
+ function suitable(rule, args) {
+ var default_args = split_obj(rule.defaults || {});
+ var diff_arg_names = array_diff(rule.arguments, default_args.names);
+
+ for (var i = 0; i < diff_arg_names.length; i++) {
+ if (!in_array(args.names, diff_arg_names[i])) {
+ return false;
+ }
+ }
+
+ if (array_diff(rule.arguments, args.names).length == 0) {
+ if (rule.defaults == null) {
+ return true;
+ }
+ for (var i = 0; i < default_args.names.length; i++) {
+ var key = default_args.names[i];
+ var value = default_args.values[i];
+ if (value != args.original[key]) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+ function build(rule, args) {
+ var tmp = [];
+ var processed = rule.arguments.slice();
+ for (var i = 0; i < rule.trace.length; i++) {
+ var part = rule.trace[i];
+ if (part.is_dynamic) {
+ var converter = converters[rule.converters[part.data]];
+ var data = converter(args.original[part.data]);
+ if (data == null) {
+ return null;
+ }
+ tmp.push(data);
+ processed.push(part.name);
+ } else {
+ tmp.push(part.data);
+ }
+ }
+ tmp = tmp.join('');
+ var pipe = tmp.indexOf('|');
+ var subdomain = tmp.substring(0, pipe);
+ var url = tmp.substring(pipe+1);
+
+ var unprocessed = array_diff(args.names, processed);
+ var first_query_var = true;
+ for (var i = 0; i < unprocessed.length; i++) {
+ if (first_query_var) {
+ url += '?';
+ } else {
+ url += '&';
+ }
+ first_query_var = false;
+ url += encodeURIComponent(unprocessed[i]);
+ url += '=';
+ url += encodeURIComponent(args.original[unprocessed[i]]);
+ }
+ return {subdomain: subdomain, path: url};
+ }
+ function lstrip(s, c) {
+ while (s && s.substring(0, 1) == c) {
+ s = s.substring(1);
+ }
+ return s;
+ }
+ function rstrip(s, c) {
+ while (s && s.substring(s.length-1, s.length) == c) {
+ s = s.substring(0, s.length-1);
+ }
+ return s;
+ }
+ return function(endpoint, args, force_external) {
+ args = split_obj(args);
+ var rv = null;
+ for (var i = 0; i < rules.length; i++) {
+ var rule = rules[i];
+ if (rule.endpoint != endpoint) continue;
+ if (suitable(rule, args)) {
+ rv = build(rule, args);
+ if (rv != null) {
+ break;
+ }
+ }
+ }
+ if (rv == null) {
+ return null;
+ }
+ if (!force_external && rv.subdomain == subdomain) {
+ return rstrip(script_name, '/') + '/' + lstrip(rv.path, '/');
+ } else {
+ return url_scheme + '://'
+ + (rv.subdomain ? rv.subdomain + '.' : '')
+ + server_name + rstrip(script_name, '/')
+ + '/' + lstrip(rv.path, '/');
+ }
+ };
+})""" % {'converters': u', '.join(converters)}
+ return result
+
+
+def generate_map(map, name='url_map'):
+ """
+ Generates a JavaScript function containing the rules defined in
+ this map, to be used with a MapAdapter's generate_javascript
+ method. If you don't pass a name the returned JavaScript code is
+ an expression that returns a function. Otherwise it's a standalone
+ script that assigns the function with that name. Dotted names are
+ resolved (so you an use a name like 'obj.url_for')
+
+ In order to use JavaScript generation, simplejson must be installed.
+
+ Note that using this feature will expose the rules
+ defined in your map to users. If your rules contain sensitive
+ information, don't use JavaScript generation!
+ """
+ map.update()
+ rules = []
+ converters = []
+ for rule in map.iter_rules():
+ trace = [{
+ 'is_dynamic': is_dynamic,
+ 'data': data
+ } for is_dynamic, data in rule._trace]
+ rule_converters = {}
+ for key, converter in rule._converters.iteritems():
+ js_func = js_to_url_function(converter)
+ try:
+ index = converters.index(js_func)
+ except ValueError:
+ converters.append(js_func)
+ index = len(converters) - 1
+ rule_converters[key] = index
+ rules.append({
+ u'endpoint': rule.endpoint,
+ u'arguments': list(rule.arguments),
+ u'converters': rule_converters,
+ u'trace': trace,
+ u'defaults': rule.defaults
+ })
+
+ return render_template(name_parts=name and name.split('.') or [],
+ rules=dumps(rules),
+ converters=converters)
+
+
+def generate_adapter(adapter, name='url_for', map_name='url_map'):
+ """Generates the url building function for a map."""
+ values = {
+ u'server_name': dumps(adapter.server_name),
+ u'script_name': dumps(adapter.script_name),
+ u'subdomain': dumps(adapter.subdomain),
+ u'url_scheme': dumps(adapter.url_scheme),
+ u'name': name,
+ u'map_name': map_name
+ }
+ return u'''\
+var %(name)s = %(map_name)s(
+ %(server_name)s,
+ %(script_name)s,
+ %(subdomain)s,
+ %(url_scheme)s
+);''' % values
+
+
+def js_to_url_function(converter):
+ """Get the JavaScript converter function from a rule."""
+ if hasattr(converter, 'js_to_url_function'):
+ data = converter.js_to_url_function()
+ else:
+ for cls in getmro(type(converter)):
+ if cls in js_to_url_functions:
+ data = js_to_url_functions[cls](converter)
+ break
+ else:
+ return 'encodeURIComponent'
+ return '(function(value) { %s })' % data
+
+
+def NumberConverter_js_to_url(conv):
+ if conv.fixed_digits:
+ return u'''\
+var result = value.toString();
+while (result.length < %s)
+ result = '0' + result;
+return result;''' % conv.fixed_digits
+ return u'return value.toString();'
+
+
+js_to_url_functions = {
+ NumberConverter: NumberConverter_js_to_url
+}