diff options
Diffstat (limited to 'websdk/werkzeug/contrib/jsrouting.py')
-rw-r--r-- | websdk/werkzeug/contrib/jsrouting.py | 259 |
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 +} |