django-js-reverse-0.9.1/0000775000175000017500000000000013464761047017052 5ustar graingertgraingert00000000000000django-js-reverse-0.9.1/PKG-INFO0000664000175000017500000002565313464761047020162 0ustar graingertgraingert00000000000000Metadata-Version: 1.1 Name: django-js-reverse Version: 0.9.1 Summary: Javascript url handling for Django that doesn't hurt. Home-page: https://github.com/ierror/django-js-reverse Author: Bernhard Janetzki Author-email: boerni@gmail.com License: MIT Download-URL: http://pypi.python.org/pypi/django-js-reverse/ Description: ================= Django JS Reverse ================= .. image:: https://img.shields.io/pypi/v/django-js-reverse.svg :target: https://pypi.python.org/pypi/django-js-reverse/ .. image:: https://img.shields.io/travis/ierror/django-js-reverse/master.svg :target: https://travis-ci.org/ierror/django-js-reverse .. image:: https://img.shields.io/coveralls/ierror/django-js-reverse/master.svg :alt: Coverage Status :target: https://coveralls.io/r/ierror/django-js-reverse?branch=master .. image:: https://img.shields.io/github/license/ierror/django-js-reverse.svg :target: https://raw.githubusercontent.com/ierror/django-js-reverse/develop/LICENSE .. image:: https://img.shields.io/pypi/wheel/django-js-reverse.svg **Javascript url handling for Django that doesn’t hurt.** Overview -------- Django JS Reverse is a small django app that makes url handling of `named urls `__ in javascript easy and non-annoying.. For example you can retrieve a named url: urls.py: :: url(r'^/betterliving/(?P[-\w]+)/(?P\d+)/$', 'get_house', name='betterliving_get_house'), in javascript like: :: Urls.betterliving_get_house('house', 12) Result: :: /betterliving/house/12/ Changelog _________ 0.9.0 New: Support for Python 3.7 New: Support for Django 2.2 New: Unit Tests Script prefix with no slash, changed URL Conf`#72 `__ Thank you `graingert `__ Fix: "ROOT_URLCONF not taken into account" `#73 `__ `#74 `__ Thank you `LuukOost `__ and `graingert `__ Refactoring: "move template logic to view" `#64 `__ Thank you `graingert `__ Fix: "Now using LooseVersion instead of StrictVersion to avoid issues with rc releases" `#67 `__ Thank you `kavdev `__ 0.8.2 Fix: A bug fix in Django 2.0.6 has broken django-js-reverse `#65 `__ Thank you `kavdev `__ 0.8.1 Fix: The tests folder of the `#53 `__ was still present in the build. => Added cleanup to the release make command. 0.8.0 New: Support for Django 2.0: `#58 `__ Thank you `wlonk `__ Fix: `#53 `__ - Don't install the tests folder as a separate folder. Moved inside the django_js_reverse namespace. `Full changelog `__ Requirements ------------ +----------------+------------------------------------------+ | Python version | Django versions | +================+==========================================+ | 3.7 | 2.2, 2.1, 2.0, 1.11, 1.10, 1.9, 1.8 | +----------------+------------------------------------------+ | 3.6 | 2.2, 2.1, 2.0, 1.11, 1.10, 1.9, 1.8 | +----------------+------------------------------------------+ | 3.5 | 2.2, 2.1, 2.0, 1.11, 1.10, 1.9, 1.8 | +----------------+------------------------------------------+ | 3.4 | 2.0, 1.11, 1.10, 1.9, 1.8, 1.7, 1.6, 1.5 | +----------------+------------------------------------------+ | 2.7 | 1.11, 1.10, 1.9, 1.8, 1.7, 1.6, 1.5 | +----------------+------------------------------------------+ Installation ------------ Install using ``pip`` … :: pip install django-js-reverse … or clone the project from github. :: git clone https://github.com/ierror/django-js-reverse.git Add ``'django_js_reverse'`` to your ``INSTALLED_APPS`` setting. :: INSTALLED_APPS = ( ... 'django_js_reverse', ) Usage as static file -------------------- First generate static file by :: ./manage.py collectstatic_js_reverse If you change some urls or add an app and want to update the reverse.js file, run the command again. After this add the file to your template :: Usage with views ---------------- Include none-cached view … :: urlpatterns = patterns('', url(r'^jsreverse/$', 'django_js_reverse.views.urls_js', name='js_reverse'), ) … or a cached one that delivers the urls javascript :: from django_js_reverse.views import urls_js urlpatterns = patterns('', url(r'^jsreverse/$', cache_page(3600)(urls_js), name='js_reverse'), ) Include javascript in your template :: or, if you are using Django > 1.5 :: Usage as template tag _____________________ You can place the js_reverse JavaScript inline into your templates, however use of inline JavaScript is not recommended, because it will make it impossible to deploy a secure Content Security Policy. See `django-csp `__ :: {% load js_reverse %} Use the urls in javascript -------------------------- If your url names are valid javascript identifiers ([$A-Z\_][-Z\_$]\*)i you can access them by the Dot notation: :: Urls.betterliving_get_house('house', 12) If the named url contains invalid identifiers use the Square bracket notation instead: :: Urls['betterliving-get-house']('house', 12) Urls['namespace:betterliving-get-house']('house', 12) You can also pass javascript objects to match keyword aguments like the examples bellow: :: Urls['betterliving-get-house']({ category_slug: 'house', entry_pk: 12 }) Urls['namespace:betterliving-get-house']({ category_slug: 'house', entry_pk: 12 }) Options ------- Optionally, you can overwrite the default javascript variable ‘Urls’ used to access the named urls by django setting :: JS_REVERSE_JS_VAR_NAME = 'Urls' Optionally, you can change the name of the global object the javascript variable used to access the named urls is attached to. Default is :code:`this` :: JS_REVERSE_JS_GLOBAL_OBJECT_NAME = 'window' Optionally, you can disable the minfication of the generated javascript file by django setting :: JS_REVERSE_JS_MINIFY = False By default all namespaces are included :: JS_REVERSE_EXCLUDE_NAMESPACES = [] To exclude any namespaces from the generated javascript file, add them to the `JS_REVERSE_EXCLUDE_NAMESPACES` setting :: JS_REVERSE_EXCLUDE_NAMESPACES = ['admin', 'djdt', ...] If you want to include only specific namespaces add them to the `JS_REVERSE_INCLUDE_ONLY_NAMESPACES` setting tips: * Use "" (empty string) for urls without namespace * Use "foo\0" to include urls just from "foo" namaspace and not from any subnamespaces (e.g. "foo:bar") :: JS_REVERSE_INCLUDE_ONLY_NAMESPACES = ['poll', 'calendar', ...] If you run your application under a subpath, the collectstatic_js_reverse needs to take care of this. Define the prefix in your django settings: :: JS_REVERSE_SCRIPT_PREFIX = '/myprefix/' By default collectstatic_js_reverse writes its output (reverse.js) to your project's STATIC_ROOT. You can change the output path: :: JS_REVERSE_OUTPUT_PATH = 'some_path' Running the test suite ---------------------- :: make test License ------- `MIT `__ Contact ------- `@i_error `__ -------------- Enjoy! Platform: UNKNOWN Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Framework :: Django Classifier: Development Status :: 5 - Production/Stable Classifier: License :: OSI Approved :: MIT License django-js-reverse-0.9.1/README.rst0000664000175000017500000001760213464760452020546 0ustar graingertgraingert00000000000000================= Django JS Reverse ================= .. image:: https://img.shields.io/pypi/v/django-js-reverse.svg :target: https://pypi.python.org/pypi/django-js-reverse/ .. image:: https://img.shields.io/travis/ierror/django-js-reverse/master.svg :target: https://travis-ci.org/ierror/django-js-reverse .. image:: https://img.shields.io/coveralls/ierror/django-js-reverse/master.svg :alt: Coverage Status :target: https://coveralls.io/r/ierror/django-js-reverse?branch=master .. image:: https://img.shields.io/github/license/ierror/django-js-reverse.svg :target: https://raw.githubusercontent.com/ierror/django-js-reverse/develop/LICENSE .. image:: https://img.shields.io/pypi/wheel/django-js-reverse.svg **Javascript url handling for Django that doesn’t hurt.** Overview -------- Django JS Reverse is a small django app that makes url handling of `named urls `__ in javascript easy and non-annoying.. For example you can retrieve a named url: urls.py: :: url(r'^/betterliving/(?P[-\w]+)/(?P\d+)/$', 'get_house', name='betterliving_get_house'), in javascript like: :: Urls.betterliving_get_house('house', 12) Result: :: /betterliving/house/12/ Changelog _________ 0.9.0 New: Support for Python 3.7 New: Support for Django 2.2 New: Unit Tests Script prefix with no slash, changed URL Conf`#72 `__ Thank you `graingert `__ Fix: "ROOT_URLCONF not taken into account" `#73 `__ `#74 `__ Thank you `LuukOost `__ and `graingert `__ Refactoring: "move template logic to view" `#64 `__ Thank you `graingert `__ Fix: "Now using LooseVersion instead of StrictVersion to avoid issues with rc releases" `#67 `__ Thank you `kavdev `__ 0.8.2 Fix: A bug fix in Django 2.0.6 has broken django-js-reverse `#65 `__ Thank you `kavdev `__ 0.8.1 Fix: The tests folder of the `#53 `__ was still present in the build. => Added cleanup to the release make command. 0.8.0 New: Support for Django 2.0: `#58 `__ Thank you `wlonk `__ Fix: `#53 `__ - Don't install the tests folder as a separate folder. Moved inside the django_js_reverse namespace. `Full changelog `__ Requirements ------------ +----------------+------------------------------------------+ | Python version | Django versions | +================+==========================================+ | 3.7 | 2.2, 2.1, 2.0, 1.11, 1.10, 1.9, 1.8 | +----------------+------------------------------------------+ | 3.6 | 2.2, 2.1, 2.0, 1.11, 1.10, 1.9, 1.8 | +----------------+------------------------------------------+ | 3.5 | 2.2, 2.1, 2.0, 1.11, 1.10, 1.9, 1.8 | +----------------+------------------------------------------+ | 3.4 | 2.0, 1.11, 1.10, 1.9, 1.8, 1.7, 1.6, 1.5 | +----------------+------------------------------------------+ | 2.7 | 1.11, 1.10, 1.9, 1.8, 1.7, 1.6, 1.5 | +----------------+------------------------------------------+ Installation ------------ Install using ``pip`` … :: pip install django-js-reverse … or clone the project from github. :: git clone https://github.com/ierror/django-js-reverse.git Add ``'django_js_reverse'`` to your ``INSTALLED_APPS`` setting. :: INSTALLED_APPS = ( ... 'django_js_reverse', ) Usage as static file -------------------- First generate static file by :: ./manage.py collectstatic_js_reverse If you change some urls or add an app and want to update the reverse.js file, run the command again. After this add the file to your template :: Usage with views ---------------- Include none-cached view … :: urlpatterns = patterns('', url(r'^jsreverse/$', 'django_js_reverse.views.urls_js', name='js_reverse'), ) … or a cached one that delivers the urls javascript :: from django_js_reverse.views import urls_js urlpatterns = patterns('', url(r'^jsreverse/$', cache_page(3600)(urls_js), name='js_reverse'), ) Include javascript in your template :: or, if you are using Django > 1.5 :: Usage as template tag _____________________ You can place the js_reverse JavaScript inline into your templates, however use of inline JavaScript is not recommended, because it will make it impossible to deploy a secure Content Security Policy. See `django-csp `__ :: {% load js_reverse %} Use the urls in javascript -------------------------- If your url names are valid javascript identifiers ([$A-Z\_][-Z\_$]\*)i you can access them by the Dot notation: :: Urls.betterliving_get_house('house', 12) If the named url contains invalid identifiers use the Square bracket notation instead: :: Urls['betterliving-get-house']('house', 12) Urls['namespace:betterliving-get-house']('house', 12) You can also pass javascript objects to match keyword aguments like the examples bellow: :: Urls['betterliving-get-house']({ category_slug: 'house', entry_pk: 12 }) Urls['namespace:betterliving-get-house']({ category_slug: 'house', entry_pk: 12 }) Options ------- Optionally, you can overwrite the default javascript variable ‘Urls’ used to access the named urls by django setting :: JS_REVERSE_JS_VAR_NAME = 'Urls' Optionally, you can change the name of the global object the javascript variable used to access the named urls is attached to. Default is :code:`this` :: JS_REVERSE_JS_GLOBAL_OBJECT_NAME = 'window' Optionally, you can disable the minfication of the generated javascript file by django setting :: JS_REVERSE_JS_MINIFY = False By default all namespaces are included :: JS_REVERSE_EXCLUDE_NAMESPACES = [] To exclude any namespaces from the generated javascript file, add them to the `JS_REVERSE_EXCLUDE_NAMESPACES` setting :: JS_REVERSE_EXCLUDE_NAMESPACES = ['admin', 'djdt', ...] If you want to include only specific namespaces add them to the `JS_REVERSE_INCLUDE_ONLY_NAMESPACES` setting tips: * Use "" (empty string) for urls without namespace * Use "foo\0" to include urls just from "foo" namaspace and not from any subnamespaces (e.g. "foo:bar") :: JS_REVERSE_INCLUDE_ONLY_NAMESPACES = ['poll', 'calendar', ...] If you run your application under a subpath, the collectstatic_js_reverse needs to take care of this. Define the prefix in your django settings: :: JS_REVERSE_SCRIPT_PREFIX = '/myprefix/' By default collectstatic_js_reverse writes its output (reverse.js) to your project's STATIC_ROOT. You can change the output path: :: JS_REVERSE_OUTPUT_PATH = 'some_path' Running the test suite ---------------------- :: make test License ------- `MIT `__ Contact ------- `@i_error `__ -------------- Enjoy! django-js-reverse-0.9.1/django_js_reverse/0000775000175000017500000000000013464761047022543 5ustar graingertgraingert00000000000000django-js-reverse-0.9.1/django_js_reverse/__init__.py0000775000175000017500000000005413464760572024660 0ustar graingertgraingert00000000000000# -*- coding: utf-8 -*- VERSION = (0, 9, 1) django-js-reverse-0.9.1/django_js_reverse/core.py0000775000175000017500000001364313464760452024056 0ustar graingertgraingert00000000000000# -*- coding: utf-8 -*- import json import re import sys from distutils.version import LooseVersion import django from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.template import loader from django.utils.safestring import mark_safe from django.utils.encoding import force_text from . import rjsmin from .js_reverse_settings import (JS_EXCLUDE_NAMESPACES, JS_GLOBAL_OBJECT_NAME, JS_INCLUDE_ONLY_NAMESPACES, JS_MINIFY, JS_VAR_NAME) try: from django import urls as urlresolvers except ImportError: from django.core import urlresolvers if sys.version < '3': text_type = unicode # NOQA else: text_type = str JS_IDENTIFIER_RE = re.compile(r'^[$A-Z_][\dA-Z_$]*$') def prepare_url_list(urlresolver, namespace_path='', namespace=''): """ returns list of tuples [(, ), ...] """ exclude_ns = getattr(settings, 'JS_REVERSE_EXCLUDE_NAMESPACES', JS_EXCLUDE_NAMESPACES) include_only_ns = getattr(settings, 'JS_REVERSE_INCLUDE_ONLY_NAMESPACES', JS_INCLUDE_ONLY_NAMESPACES) if exclude_ns and include_only_ns: raise ImproperlyConfigured( 'Neither use JS_REVERSE_EXCLUDE_NAMESPACES nor JS_REVERSE_INCLUDE_ONLY_NAMESPACES setting') if namespace[:-1] in exclude_ns: return include_only_allow = True # include_only state varible if include_only_ns != []: # True mean that ns passed the test in_on_empty_ns = False in_on_is_in_list = False in_on_null = False # Test urls without ns if namespace == '' and '' in include_only_ns: in_on_empty_ns = True # check if nestead ns isn't subns of include_only ns # e.g. ns = "foo:bar" include_only = ["foo"] -> this ns will be used # works for ns = "lorem:ipsum:dolor" include_only = ["lorem:ipsum"] # ns "lorem" will be ignored but "lorem:ipsum" & "lorem:ipsum:.." won't for ns in include_only_ns: if ns != "" and namespace[:-1].startswith(ns): in_on_is_in_list = True break # Test if isn't used "\0" flag # use "foo\0" to add urls just from "foo" not from subns "foo:bar" if namespace[:-1] + '\0' in include_only_ns: in_on_null = True include_only_allow = in_on_empty_ns or in_on_is_in_list or in_on_null if include_only_allow: for url_name in urlresolver.reverse_dict.keys(): if isinstance(url_name, (text_type, str)): url_patterns = [] for url_pattern in urlresolver.reverse_dict.getlist(url_name): url_patterns += [ [namespace_path + pat[0], pat[1]] for pat in url_pattern[0]] yield [namespace + url_name, url_patterns] for inner_ns, (inner_ns_path, inner_urlresolver) in \ urlresolver.namespace_dict.items(): inner_ns_path = namespace_path + inner_ns_path inner_ns = namespace + inner_ns + ':' # if we have inner_ns_path, reconstruct a new resolver so that we can # handle regex substitutions within the regex of a namespace. if inner_ns_path: args = [inner_ns_path, inner_urlresolver] # https://github.com/ierror/django-js-reverse/issues/65 if LooseVersion(django.get_version()) >= LooseVersion("2.0.6"): args.append(tuple(urlresolver.pattern.converters.items())) inner_urlresolver = urlresolvers.get_ns_resolver(*args) inner_ns_path = '' for x in prepare_url_list(inner_urlresolver, inner_ns_path, inner_ns): yield x def generate_json(default_urlresolver, script_prefix=None): if script_prefix is None: script_prefix = urlresolvers.get_script_prefix() urls = sorted(list(prepare_url_list(default_urlresolver))) return { 'urls': [ [ force_text(name), [ [force_text(path), [force_text(arg) for arg in args]] for path, args in patterns ], ] for name, patterns in urls ], 'prefix': script_prefix, } def _safe_json(obj): return mark_safe( json .dumps(obj) .replace('>', '\\u003E') .replace('<', '\\u003C') .replace('&', '\\u0026') ) def generate_js(default_urlresolver): js_var_name = getattr(settings, 'JS_REVERSE_JS_VAR_NAME', JS_VAR_NAME) if not JS_IDENTIFIER_RE.match(js_var_name.upper()): raise ImproperlyConfigured( 'JS_REVERSE_JS_VAR_NAME setting "%s" is not a valid javascript identifier.' % (js_var_name)) js_global_object_name = getattr(settings, 'JS_REVERSE_JS_GLOBAL_OBJECT_NAME', JS_GLOBAL_OBJECT_NAME) if not JS_IDENTIFIER_RE.match(js_global_object_name.upper()): raise ImproperlyConfigured( 'JS_REVERSE_JS_GLOBAL_OBJECT_NAME setting "%s" is not a valid javascript identifier.' % ( js_global_object_name)) minfiy = getattr(settings, 'JS_REVERSE_JS_MINIFY', JS_MINIFY) if not isinstance(minfiy, bool): raise ImproperlyConfigured( 'JS_REVERSE_JS_MINIFY setting "%s" is not a valid. Needs to be set to True or False.' % (minfiy)) script_prefix_via_config = getattr(settings, 'JS_REVERSE_SCRIPT_PREFIX', None) if script_prefix_via_config: script_prefix = script_prefix_via_config if not script_prefix.endswith('/'): script_prefix = '{0}/'.format(script_prefix) else: script_prefix = urlresolvers.get_script_prefix() data = generate_json(default_urlresolver, script_prefix) js_content = loader.render_to_string('django_js_reverse/urls_js.tpl', { 'data': _safe_json(data), 'js_name': '.'.join([js_global_object_name, js_var_name]), }) if minfiy: js_content = rjsmin.jsmin(js_content) return js_content django-js-reverse-0.9.1/django_js_reverse/js_reverse_settings.py0000775000175000017500000000030613464264542027204 0ustar graingertgraingert00000000000000# -*- coding: utf-8 -*- JS_VAR_NAME = 'Urls' JS_MINIFY = True JS_EXCLUDE_NAMESPACES = [] JS_INCLUDE_ONLY_NAMESPACES = [] JS_SCRIPT_PREFIX = None JS_GLOBAL_OBJECT_NAME = 'this' JS_OUTPUT_PATH = None django-js-reverse-0.9.1/django_js_reverse/management/0000775000175000017500000000000013464761047024657 5ustar graingertgraingert00000000000000django-js-reverse-0.9.1/django_js_reverse/management/__init__.py0000775000175000017500000000002613464264542026767 0ustar graingertgraingert00000000000000__author__ = 'boerni' django-js-reverse-0.9.1/django_js_reverse/management/commands/0000775000175000017500000000000013464761047026460 5ustar graingertgraingert00000000000000django-js-reverse-0.9.1/django_js_reverse/management/commands/__init__.py0000775000175000017500000000002613464264542030570 0ustar graingertgraingert00000000000000__author__ = 'boerni' django-js-reverse-0.9.1/django_js_reverse/management/commands/collectstatic_js_reverse.py0000775000175000017500000000324713464313137034120 0ustar graingertgraingert00000000000000# -*- coding: utf-8 -*- import os import sys from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.core.files.base import ContentFile from django.core.files.storage import FileSystemStorage from django.core.management.base import BaseCommand from django_js_reverse.core import generate_js from django_js_reverse.js_reverse_settings import JS_OUTPUT_PATH try: from django.urls import get_resolver except ImportError: from django.core.urlresolvers import get_resolver class Command(BaseCommand): help = 'Creates a static urls-js file for django-js-reverse' def get_location(self): output_path = getattr(settings, 'JS_REVERSE_OUTPUT_PATH', JS_OUTPUT_PATH) if output_path: return output_path if not hasattr(settings, 'STATIC_ROOT') or not settings.STATIC_ROOT: raise ImproperlyConfigured( 'The collectstatic_js_reverse command needs settings.JS_REVERSE_OUTPUT_PATH or settings.STATIC_ROOT to be set.') return os.path.join(settings.STATIC_ROOT, 'django_js_reverse', 'js') def handle(self, *args, **options): location = self.get_location() file = 'reverse.js' fs = FileSystemStorage(location=location) if fs.exists(file): fs.delete(file) urlconf = getattr(settings, 'ROOT_URLCONF', None) default_urlresolver = get_resolver(urlconf) content = generate_js(default_urlresolver) fs.save(file, ContentFile(content)) if len(sys.argv) > 1 and sys.argv[1] in ['collectstatic_js_reverse']: self.stdout.write('js-reverse file written to %s' % (location)) # pragma: no cover django-js-reverse-0.9.1/django_js_reverse/models.py0000775000175000017500000000000013464264542024367 0ustar graingertgraingert00000000000000django-js-reverse-0.9.1/django_js_reverse/rjsmin.py0000664000175000017500000004071713464264542024426 0ustar graingertgraingert00000000000000#!/usr/bin/env python # -*- coding: ascii -*- r""" ===================== Javascript Minifier ===================== rJSmin is a javascript minifier written in python. The minifier is based on the semantics of `jsmin.c by Douglas Crockford`_\\. :Copyright: Copyright 2011 - 2014 Andr\xe9 Malo or his licensors, as applicable :License: Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. The module is a re-implementation aiming for speed, so it can be used at runtime (rather than during a preprocessing step). Usually it produces the same results as the original ``jsmin.c``. It differs in the following ways: - there is no error detection: unterminated string, regex and comment literals are treated as regular javascript code and minified as such. - Control characters inside string and regex literals are left untouched; they are not converted to spaces (nor to \\n) - Newline characters are not allowed inside string and regex literals, except for line continuations in string literals (ECMA-5). - "return /regex/" is recognized correctly. - "+ +" and "- -" sequences are not collapsed to '++' or '--' - Newlines before ! operators are removed more sensibly - Comments starting with an exclamation mark (``!``) can be kept optionally - rJSmin does not handle streams, but only complete strings. (However, the module provides a "streamy" interface). Since most parts of the logic are handled by the regex engine it's way faster than the original python port of ``jsmin.c`` by Baruch Even. The speed factor varies between about 6 and 55 depending on input and python version (it gets faster the more compressed the input already is). Compared to the speed-refactored python port by Dave St.Germain the performance gain is less dramatic but still between 3 and 50 (for huge inputs). See the docs/BENCHMARKS file for details. rjsmin.c is a reimplementation of rjsmin.py in C and speeds it up even more. Both python 2 and python 3 are supported. .. _jsmin.c by Douglas Crockford: http://www.crockford.com/javascript/jsmin.c """ if __doc__: # pylint: disable = W0622 __doc__ = __doc__.encode('ascii').decode('unicode_escape') __author__ = r"Andr\xe9 Malo".encode('ascii').decode('unicode_escape') __docformat__ = "restructuredtext en" __license__ = "Apache License, Version 2.0" __version__ = '1.0.10' __all__ = ['jsmin'] import re as _re def _make_jsmin(python_only=False): """ Generate JS minifier based on `jsmin.c by Douglas Crockford`_ .. _jsmin.c by Douglas Crockford: http://www.crockford.com/javascript/jsmin.c :Parameters: `python_only` : ``bool`` Use only the python variant. If true, the c extension is not even tried to be loaded. :Return: Minifier :Rtype: ``callable`` """ # pylint: disable = R0912, R0914, W0612 if not python_only: try: import _rjsmin # pylint: disable = F0401 except ImportError: pass else: return _rjsmin.jsmin try: xrange except NameError: xrange = range # pylint: disable = W0622 space_chars = r'[\000-\011\013\014\016-\040]' line_comment = r'(?://[^\r\n]*)' space_comment = r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)' space_comment_nobang = r'(?:/\*(?!!)[^*]*\*+(?:[^/*][^*]*\*+)*/)' bang_comment = r'(?:/\*![^*]*\*+(?:[^/*][^*]*\*+)*/)' string1 = \ r'(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|\r)[^\047\\\r\n]*)*\047)' string2 = r'(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|\r)[^"\\\r\n]*)*")' strings = r'(?:%s|%s)' % (string1, string2) charclass = r'(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\])' nospecial = r'[^/\\\[\r\n]' regex = r'(?:/(?![\r\n/*])%s*(?:(?:\\[^\r\n]|%s)%s*)*/)' % ( nospecial, charclass, nospecial ) space = r'(?:%s|%s)' % (space_chars, space_comment) space_nobang = r'(?:%s|%s)' % (space_chars, space_comment_nobang) newline = r'(?:%s?[\r\n])' % line_comment def fix_charclass(result): """ Fixup string of chars to fit into a regex char class """ pos = result.find('-') if pos >= 0: result = r'%s%s-' % (result[:pos], result[pos + 1:]) def sequentize(string): """ Notate consecutive characters as sequence (1-4 instead of 1234) """ first, last, result = None, None, [] for char in map(ord, string): if last is None: first = last = char elif last + 1 == char: last = char else: result.append((first, last)) first = last = char if last is not None: result.append((first, last)) return ''.join(['%s%s%s' % ( chr(first), last > first + 1 and '-' or '', last != first and chr(last) or '' ) for first, last in result]) return _re.sub( r'([\000-\040\047])', # \047 for better portability lambda m: '\\%03o' % ord(m.group(1)), ( sequentize(result) .replace('\\', '\\\\') .replace('[', '\\[') .replace(']', '\\]') ) ) def id_literal_(what): """ Make id_literal like char class """ match = _re.compile(what).match result = ''.join([ chr(c) for c in xrange(127) if not match(chr(c)) ]) return '[^%s]' % fix_charclass(result) def not_id_literal_(keep): """ Make negated id_literal like char class """ match = _re.compile(id_literal_(keep)).match result = ''.join([ chr(c) for c in xrange(127) if not match(chr(c)) ]) return r'[%s]' % fix_charclass(result) not_id_literal = not_id_literal_(r'[a-zA-Z0-9_$]') preregex1 = r'[(,=:\[!&|?{};\r\n]' preregex2 = r'%(not_id_literal)sreturn' % locals() id_literal = id_literal_(r'[a-zA-Z0-9_$]') id_literal_open = id_literal_(r'[a-zA-Z0-9_${\[(!+-]') id_literal_close = id_literal_(r'[a-zA-Z0-9_$}\])"\047+-]') dull = r'[^\047"/\000-\040]' space_sub_simple = _re.compile(( # noqa pylint: disable = C0330 r'(%(dull)s+)' r'|(%(strings)s%(dull)s*)' r'|(?<=%(preregex1)s)' r'%(space)s*(?:%(newline)s%(space)s*)*' r'(%(regex)s%(dull)s*)' r'|(?<=%(preregex2)s)' r'%(space)s*(?:%(newline)s%(space)s)*' r'(%(regex)s%(dull)s*)' r'|(?<=%(id_literal_close)s)' r'%(space)s*(?:(%(newline)s)%(space)s*)+' r'(?=%(id_literal_open)s)' r'|(?<=%(id_literal)s)(%(space)s)+(?=%(id_literal)s)' r'|(?<=\+)(%(space)s)+(?=\+)' r'|(?<=-)(%(space)s)+(?=-)' r'|%(space)s+' r'|(?:%(newline)s%(space)s*)+' ) % locals()).sub #print space_sub_simple.__self__.pattern def space_subber_simple(match): """ Substitution callback """ # pylint: disable = R0911 groups = match.groups() if groups[0]: return groups[0] elif groups[1]: return groups[1] elif groups[2]: return groups[2] elif groups[3]: return groups[3] elif groups[4]: return '\n' elif groups[5] or groups[6] or groups[7]: return ' ' else: return '' space_sub_banged = _re.compile(( # noqa pylint: disable = C0330 r'(%(dull)s+)' r'|(%(strings)s%(dull)s*)' r'|(%(bang_comment)s%(dull)s*)' r'|(?<=%(preregex1)s)' r'%(space)s*(?:%(newline)s%(space)s*)*' r'(%(regex)s%(dull)s*)' r'|(?<=%(preregex2)s)' r'%(space)s*(?:%(newline)s%(space)s)*' r'(%(regex)s%(dull)s*)' r'|(?<=%(id_literal_close)s)' r'%(space)s*(?:(%(newline)s)%(space)s*)+' r'(?=%(id_literal_open)s)' r'|(?<=%(id_literal)s)(%(space)s)+(?=%(id_literal)s)' r'|(?<=\+)(%(space)s)+(?=\+)' r'|(?<=-)(%(space)s)+(?=-)' r'|%(space)s+' r'|(?:%(newline)s%(space)s*)+' ) % dict(locals(), space=space_nobang)).sub #print space_sub_banged.__self__.pattern def space_subber_banged(match): """ Substitution callback """ # pylint: disable = R0911 groups = match.groups() if groups[0]: return groups[0] elif groups[1]: return groups[1] elif groups[2]: return groups[2] elif groups[3]: return groups[3] elif groups[4]: return groups[4] elif groups[5]: return '\n' elif groups[6] or groups[7] or groups[8]: return ' ' else: return '' def jsmin(script, keep_bang_comments=False): # pylint: disable = W0621 r""" Minify javascript based on `jsmin.c by Douglas Crockford`_\. Instead of parsing the stream char by char, it uses a regular expression approach which minifies the whole script with one big substitution regex. .. _jsmin.c by Douglas Crockford: http://www.crockford.com/javascript/jsmin.c :Parameters: `script` : ``str`` Script to minify `keep_bang_comments` : ``bool`` Keep comments starting with an exclamation mark? (``/*!...*/``) :Return: Minified script :Rtype: ``str`` """ if keep_bang_comments: return space_sub_banged( space_subber_banged, '\n%s\n' % script ).strip() else: return space_sub_simple( space_subber_simple, '\n%s\n' % script ).strip() return jsmin jsmin = _make_jsmin(python_only=True) def jsmin_for_posers(script, keep_bang_comments=False): r""" Minify javascript based on `jsmin.c by Douglas Crockford`_\. Instead of parsing the stream char by char, it uses a regular expression approach which minifies the whole script with one big substitution regex. .. _jsmin.c by Douglas Crockford: http://www.crockford.com/javascript/jsmin.c :Warning: This function is the digest of a _make_jsmin() call. It just utilizes the resulting regexes. It's here for fun and may vanish any time. Use the `jsmin` function instead. :Parameters: `script` : ``str`` Script to minify `keep_bang_comments` : ``bool`` Keep comments starting with an exclamation mark? (``/*!...*/``) :Return: Minified script :Rtype: ``str`` """ if not keep_bang_comments: rex = ( r'([^\047"/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]' r'|\r?\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]' r'|\r?\n|\r)[^"\\\r\n]*)*"))[^\047"/\000-\040]*)|(?<=[(,=:\[!&|?' r'{};\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*' r'][^*]*\*+)*/))*(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\0' r'14\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)*((?:/(?![\r' r'\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r' r'\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*/)[^\047"/\000-\040]*)|(?<' r'=[\000-#%-,./:-@\[-^`{-~-]return)(?:[\000-\011\013\014\016-\04' r'0]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*(?:(?:(?://[^\r\n]*)?[' r'\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^' r'*]*\*+)*/)))*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|(?:' r'\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*/)[' r'^\047"/\000-\040]*)|(?<=[^\000-!#%&(*,./:-@\[\\^`{|~])(?:[\000' r'-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*(?' r':((?:(?://[^\r\n]*)?[\r\n]))(?:[\000-\011\013\014\016-\040]|(?' r':/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-\040"#%-\047)*,.' r'/:-@\\-^`|-~])|(?<=[^\000-#%-,./:-@\[-^`{-~-])((?:[\000-\011\0' r'13\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=[^\00' r'0-#%-,./:-@\[-^`{-~-])|(?<=\+)((?:[\000-\011\013\014\016-\040]' r'|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=\+)|(?<=-)((?:[\000-' r'\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?' r'=-)|(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]' r'*\*+)*/))+|(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\0' r'16-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+' ) def subber(match): """ Substitution callback """ groups = match.groups() return ( groups[0] or groups[1] or groups[2] or groups[3] or (groups[4] and '\n') or (groups[5] and ' ') or (groups[6] and ' ') or (groups[7] and ' ') or '' ) else: rex = ( r'([^\047"/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]' r'|\r?\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]' r'|\r?\n|\r)[^"\\\r\n]*)*"))[^\047"/\000-\040]*)|((?:/\*![^*]*\*' r'+(?:[^/*][^*]*\*+)*/)[^\047"/\000-\040]*)|(?<=[(,=:\[!&|?{};\r' r'\n])(?:[\000-\011\013\014\016-\040]|(?:/\*(?!!)[^*]*\*+(?:[^/*' r'][^*]*\*+)*/))*(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\0' r'14\016-\040]|(?:/\*(?!!)[^*]*\*+(?:[^/*][^*]*\*+)*/))*)*((?:/(' r'?![\r\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:' r'\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*/)[^\047"/\000-\040]' r'*)|(?<=[\000-#%-,./:-@\[-^`{-~-]return)(?:[\000-\011\013\014\0' r'16-\040]|(?:/\*(?!!)[^*]*\*+(?:[^/*][^*]*\*+)*/))*(?:(?:(?://[' r'^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*(?!!)[^*' r']*\*+(?:[^/*][^*]*\*+)*/)))*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?:(' r'?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/' r'\\\[\r\n]*)*/)[^\047"/\000-\040]*)|(?<=[^\000-!#%&(*,./:-@\[\\' r'^`{|~])(?:[\000-\011\013\014\016-\040]|(?:/\*(?!!)[^*]*\*+(?:[' r'^/*][^*]*\*+)*/))*(?:((?:(?://[^\r\n]*)?[\r\n]))(?:[\000-\011' r'\013\014\016-\040]|(?:/\*(?!!)[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+' r'(?=[^\000-\040"#%-\047)*,./:-@\\-^`|-~])|(?<=[^\000-#%-,./:-@' r'\[-^`{-~-])((?:[\000-\011\013\014\016-\040]|(?:/\*(?!!)[^*]*\*' r'+(?:[^/*][^*]*\*+)*/)))+(?=[^\000-#%-,./:-@\[-^`{-~-])|(?<=\+)' r'((?:[\000-\011\013\014\016-\040]|(?:/\*(?!!)[^*]*\*+(?:[^/*][^' r'*]*\*+)*/)))+(?=\+)|(?<=-)((?:[\000-\011\013\014\016-\040]|(?:' r'/\*(?!!)[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=-)|(?:[\000-\011\013' r'\014\016-\040]|(?:/\*(?!!)[^*]*\*+(?:[^/*][^*]*\*+)*/))+|(?:(?' r':(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*(' r'?!!)[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+' ) def subber(match): """ Substitution callback """ groups = match.groups() return ( groups[0] or groups[1] or groups[2] or groups[3] or groups[4] or (groups[5] and '\n') or (groups[6] and ' ') or (groups[7] and ' ') or (groups[8] and ' ') or '' ) return _re.sub(rex, subber, '\n%s\n' % script).strip() if __name__ == '__main__': def main(): """ Main """ import sys as _sys keep_bang_comments = ( '-b' in _sys.argv[1:] or '-bp' in _sys.argv[1:] or '-pb' in _sys.argv[1:] ) if '-p' in _sys.argv[1:] or '-bp' in _sys.argv[1:] \ or '-pb' in _sys.argv[1:]: global jsmin # pylint: disable = W0603 jsmin = _make_jsmin(python_only=True) _sys.stdout.write(jsmin( _sys.stdin.read(), keep_bang_comments=keep_bang_comments )) main() django-js-reverse-0.9.1/django_js_reverse/templates/0000775000175000017500000000000013464761047024541 5ustar graingertgraingert00000000000000django-js-reverse-0.9.1/django_js_reverse/templates/django_js_reverse/0000775000175000017500000000000013464761047030232 5ustar graingertgraingert00000000000000django-js-reverse-0.9.1/django_js_reverse/templates/django_js_reverse/urls_js.tpl0000775000175000017500000000604113464760452032437 0ustar graingertgraingert00000000000000{{ js_name }} = (function () { "use strict"; var data = {{ data }}; function factory(d) { var url_patterns = d.urls; var url_prefix = d.prefix; var Urls = {}; var self_url_patterns = {}; var _get_url = function (url_pattern) { return function () { var _arguments, index, url, url_arg, url_args, _i, _len, _ref, _ref_list, match_ref, provided_keys, build_kwargs; _arguments = arguments; _ref_list = self_url_patterns[url_pattern]; if (arguments.length == 1 && typeof (arguments[0]) == "object") { // kwargs mode var provided_keys_list = Object.keys (arguments[0]); provided_keys = {}; for (_i = 0; _i < provided_keys_list.length; _i++) provided_keys[provided_keys_list[_i]] = 1; match_ref = function (ref) { var _i; // Verify that they have the same number of arguments if (ref[1].length != provided_keys_list.length) return false; for (_i = 0; _i < ref[1].length && ref[1][_i] in provided_keys; _i++); // If for loop completed, we have all keys return _i == ref[1].length; } build_kwargs = function (keys) {return _arguments[0];} } else { // args mode match_ref = function (ref) { return ref[1].length == _arguments.length; } build_kwargs = function (keys) { var kwargs = {}; for (var i = 0; i < keys.length; i++) { kwargs[keys[i]] = _arguments[i]; } return kwargs; } } for (_i = 0; _i < _ref_list.length && !match_ref(_ref_list[_i]); _i++); // can't find a match if (_i == _ref_list.length) return null; _ref = _ref_list[_i]; url = _ref[0], url_args = build_kwargs(_ref[1]); for (url_arg in url_args) { var url_arg_value = url_args[url_arg]; if (url_arg_value === undefined || url_arg_value === null) { url_arg_value = ''; } else { url_arg_value = url_arg_value.toString(); } url = url.replace("%(" + url_arg + ")s", url_arg_value); } return url_prefix + url; }; }; var name, pattern, url, _i, _len, _ref; for (_i = 0, _len = url_patterns.length; _i < _len; _i++) { _ref = url_patterns[_i], name = _ref[0], pattern = _ref[1]; self_url_patterns[name] = pattern; url = _get_url(name); Urls[name.replace(/[-_]+(.)/g, function (_m, p1) { return p1.toUpperCase(); })] = url; Urls[name.replace(/-/g, '_')] = url; Urls[name] = url; } return Urls; } return data ? factory(data) : factory; })(); django-js-reverse-0.9.1/django_js_reverse/templatetags/0000775000175000017500000000000013464761047025235 5ustar graingertgraingert00000000000000django-js-reverse-0.9.1/django_js_reverse/templatetags/__init__.py0000775000175000017500000000000013464264542027335 0ustar graingertgraingert00000000000000django-js-reverse-0.9.1/django_js_reverse/templatetags/js_reverse.py0000775000175000017500000000150613464313137027754 0ustar graingertgraingert00000000000000# -*- coding: utf-8 -*- from django import template from django.utils.safestring import mark_safe from django_js_reverse.core import generate_js try: from django.urls import get_resolver except ImportError: from django.core.urlresolvers import get_resolver register = template.Library() urlconf = template.Variable('request.urlconf') def _get_urlconf(context): try: return context.request.urlconf except AttributeError: pass try: return urlconf.resolve(context) except template.VariableDoesNotExist: pass @register.simple_tag(takes_context=True) def js_reverse_inline(context): """ Outputs a string of javascript that can generate URLs via the use of the names given to those URLs. """ return mark_safe(generate_js(get_resolver(_get_urlconf(context)))) django-js-reverse-0.9.1/django_js_reverse/tests/0000775000175000017500000000000013464761047023705 5ustar graingertgraingert00000000000000django-js-reverse-0.9.1/django_js_reverse/tests/__init__.py0000664000175000017500000000000013464264542026002 0ustar graingertgraingert00000000000000django-js-reverse-0.9.1/django_js_reverse/tests/helper.py0000664000175000017500000000015313464264542025533 0ustar graingertgraingert00000000000000# -*- coding: utf-8 -*- from django import VERSION def is_django_ver_gte_2(): return VERSION[0] >= 2 django-js-reverse-0.9.1/django_js_reverse/tests/settings.py0000664000175000017500000000114213464264542026113 0ustar graingertgraingert00000000000000import os DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:', }, } SECRET_KEY = 'wtf' ROOT_URLCONF = None USE_TZ = True INSTALLED_APPS = ( 'django_js_reverse', ) ALLOWED_HOSTS = ['testserver'] MIDDLEWARE_CLASSES = () MIDDLEWARE = () TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'APP_DIRS': True, }, ] STATIC_ROOT = os.path.join(os.path.dirname(__file__), 'tmp', 'static_root') CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', } } django-js-reverse-0.9.1/django_js_reverse/tests/test_urlconf_urls.py0000664000175000017500000000025413464313137030025 0ustar graingertgraingert00000000000000from django.conf.urls import url from django.views.generic import View urlpatterns = [ url(r'^test_changed_urlconf/$', View.as_view(), name='test_changed_urlconf'), ] django-js-reverse-0.9.1/django_js_reverse/tests/test_urls.py0000664000175000017500000000612613464760452026307 0ustar graingertgraingert00000000000000# -*- coding: utf-8 -*- from copy import copy from django.conf.urls import include as django_include from django.conf.urls import url from django.views.generic import View from django_js_reverse.tests.helper import is_django_ver_gte_2 from django_js_reverse.views import urls_js try: from django.urls import path except ImportError: pass dummy_view = View.as_view() basic_patterns = [ url(r'^jsreverse/$', urls_js, name='js_reverse'), # test urls url(r'^test_no_url_args/$', dummy_view, name='test_no_url_args'), url(r'^test_script/$', dummy_view, name='