From de258ae39ca26151fe484bc26029e8582d71f96d Mon Sep 17 00:00:00 2001 From: Toby Date: Tue, 8 May 2012 13:27:07 +0100 Subject: [PATCH] fanstatic branch initial release --- LICENSE.txt | 24 +++ ckan/html_resources/__init__.py | 99 +++++++++ ckan/include/README.txt | 37 ++++ ckan/include/__init__.py | 0 ckan/include/rcssmin.py | 360 ++++++++++++++++++++++++++++++++ ckan/include/rjsmin.py | 290 +++++++++++++++++++++++++ ckan/lib/helpers.py | 7 + setup.py | 4 + 8 files changed, 821 insertions(+) create mode 100644 ckan/html_resources/__init__.py create mode 100644 ckan/include/README.txt create mode 100644 ckan/include/__init__.py create mode 100755 ckan/include/rcssmin.py create mode 100644 ckan/include/rjsmin.py diff --git a/LICENSE.txt b/LICENSE.txt index 0e58e4e3401..8b85a1b6390 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -114,4 +114,28 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +rjsmin / rcssmin +---------------- + +Parts of these packages are include in the include directory of ckan. +Full packages can be found at. +http://opensource.perlig.de/rjsmin/ +http://opensource.perlig.de/rcssmin/ + +They both are licensed under Approved License, Version 2 + +Copyright 2011, 2012 +Andr\xe9 Malo or his licensors, as applicable + +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. diff --git a/ckan/html_resources/__init__.py b/ckan/html_resources/__init__.py new file mode 100644 index 00000000000..0d719b6cc10 --- /dev/null +++ b/ckan/html_resources/__init__.py @@ -0,0 +1,99 @@ +import os.path +import sys +import ConfigParser + +from fanstatic import Library, Resource + +from ckan.include.rjsmin import jsmin +from ckan.include.rcssmin import cssmin + +# TODO +# loop through dirs to setup +# warn on no entry point provided for fanstatic + +def setup(name, path): + + def min_path(path): + ''' return the .min filename eg moo.js -> moo.min.js ''' + if f.endswith('.js'): + return path[:-3] + '.min.js' + if f.endswith('.css'): + return path[:-4] + '.min.css' + + def minify(filename, min_function): + ''' Minify file path using min_function. ''' + # if the minified file was modified after the source file we can + # assume that it is up-to-date + path = os.path.join(resource_path, filename) + path_min = min_path(path) + op = os.path + if op.exists(path_min) and op.getmtime(path) < op.getmtime(path_min): + return + source = open(path, 'r').read() + f = open(path_min, 'w') + f.write(min_function(source)) + f.close() + print 'minified %s' % path + + def create_resource(filename): + ''' create the fanstatic Resource ''' + # resource_name is name of the file without the .js/.css + resource_name = '.'.join(filename.split('.')[:-1]) + kw = {} + path_min = min_path(os.path.join(resource_path, filename)) + if os.path.exists(path_min): + kw['minified'] = min_path(filename) + if filename.endswith('.js'): + kw['bottom'] = True + if resource_name in depends: + dependencies = [] + for dependency in depends[resource_name]: + dependencies.append(getattr(module, dependency)) + kw['depends'] = dependencies + if resource_name in dont_bundle: + kw['dont_bundle'] = True + + resource = Resource(library, filename, **kw) + # add the resource to this module + setattr(module, resource_name, resource) + + order = [] + dont_bundle = [] + depends = {} + + # parse the config.ini file if it exists + resource_path = os.path.dirname(__file__) + resource_path = os.path.join(resource_path, path) + config_path = os.path.join(resource_path, 'config.ini') + if os.path.exists(config_path): + config = ConfigParser.RawConfigParser() + config.read(config_path) + if config.has_option('main', 'order'): + order = config.get('main', 'order').split() + if config.has_option('main', 'dont_bundle'): + dont_bundle = config.get('main', 'dont_bundle').split() + if config.has_section('depends'): + items = config.items('depends') + depends = dict((n, v.split()) for (n, v) in items) + + library = Library(name, path) + module = sys.modules[__name__] + + # process each .js/.css file found + for dirname, dirnames, filenames in os.walk(resource_path): + for x in reversed(order): + if x in filenames: + filenames.remove(x) + filenames.insert(0, x) + for f in filenames: + if f.endswith('.js') and not f.endswith('.min.js'): + minify(f, jsmin) + create_resource(f) + if f.endswith('.css') and not f.endswith('.min.css'): + minify(f, cssmin) + create_resource(f) + # finally add the library to this module + setattr(module, name, library) + + +setup('resources', 'resources') diff --git a/ckan/include/README.txt b/ckan/include/README.txt new file mode 100644 index 00000000000..207800aa9e6 --- /dev/null +++ b/ckan/include/README.txt @@ -0,0 +1,37 @@ +rjsmin.py +is taken from the rjsmin project and licensed under Apache License, Version 2 +http://opensource.perlig.de/rjsmin/ + +# Copyright 2011, 2012 +# Andr\xe9 Malo or his licensors, as applicable +# +# 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. + +rcssmin.py +is taken from the rcssmin project and licensed under Apache License, Version 2 +http://opensource.perlig.de/rcssmin/ + +# Copyright 2011, 2012 +# Andr\xe9 Malo or his licensors, as applicable +# +# 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. diff --git a/ckan/include/__init__.py b/ckan/include/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ckan/include/rcssmin.py b/ckan/include/rcssmin.py new file mode 100755 index 00000000000..3f0435f0074 --- /dev/null +++ b/ckan/include/rcssmin.py @@ -0,0 +1,360 @@ +#!/usr/bin/env python +# -*- coding: ascii -*- +# +# Copyright 2011, 2012 +# Andr\xe9 Malo or his licensors, as applicable +# +# 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. +r""" +============== + CSS Minifier +============== + +CSS Minifier. + +The minifier is based on the semantics of the `YUI compressor`_\, which itself +is based on `the rule list by Isaac Schlueter`_\. + +This module is a re-implementation aiming for speed instead of maximum +compression, so it can be used at runtime (rather than during a preprocessing +step). RCSSmin does syntactical compression only (removing spaces, comments +and possibly semicolons). It does not provide semantic compression (like +removing empty blocks, collapsing redundant properties etc). It does, however, +support various CSS hacks (by keeping them working as intended). + +Here's a feature list: + +- Strings are kept, except that escaped newlines are stripped +- Space/Comments before the very end or before various characters are + stripped: ``:{});=>+],!`` (The colon (``:``) is a special case, a single + space is kept if it's outside a ruleset.) +- Space/Comments at the very beginning or after various characters are + stripped: ``{}(=:>+[,!`` +- Optional space after unicode escapes is kept, resp. replaced by a simple + space +- whitespaces inside ``url()`` definitions are stripped +- Comments starting with an exclamation mark (``!``) can be kept optionally. +- All other comments and/or whitespace characters are replaced by a single + space. +- Multiple consecutive semicolons are reduced to one +- The last semicolon within a ruleset is stripped +- CSS Hacks supported: + + - IE7 hack (``>/**/``) + - Mac-IE5 hack (``/*\*/.../**/``) + - The boxmodelhack is supported naturally because it relies on valid CSS2 + strings + - Between ``:first-line`` and the following comma or curly brace a space is + inserted. (apparently it's needed for IE6) + - Same for ``:first-letter`` + +rcssmin.c is a reimplementation of rcssmin.py in C and improves runtime up to +factor 50 or so (depending on the input). + +Both python 2 (>= 2.4) and python 3 are supported. + +.. _YUI compressor: https://github.com/yui/yuicompressor/ + +.. _the rule list by Isaac Schlueter: https://github.com/isaacs/cssmin/tree/ +""" +__author__ = "Andr\xe9 Malo" +__author__ = getattr(__author__, 'decode', lambda x: __author__)('latin-1') +__docformat__ = "restructuredtext en" +__license__ = "Apache License, Version 2.0" +__version__ = '1.0.0' +__all__ = ['cssmin'] + +import re as _re + + +def _make_cssmin(python_only=False): + """ + Generate CSS minifier. + + :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 = W0612 + # ("unused" variables) + + # pylint: disable = R0911, R0912, R0914, R0915 + # (too many anything) + + if not python_only: + try: + import _rcssmin + except ImportError: + pass + else: + return _rcssmin.cssmin + + nl = r'(?:[\n\f]|\r\n?)' # pylint: disable = C0103 + spacechar = r'[\r\n\f\040\t]' + + unicoded = r'[0-9a-fA-F]{1,6}(?:[\040\n\t\f]|\r\n?)?' + escaped = r'[^\n\r\f0-9a-fA-F]' + escape = r'(?:\\(?:%(unicoded)s|%(escaped)s))' % locals() + + nmchar = r'[^\000-\054\056\057\072-\100\133-\136\140\173-\177]' + #nmstart = r'[^\000-\100\133-\136\140\173-\177]' + #ident = (r'(?:' + # r'-?(?:%(nmstart)s|%(escape)s)%(nmchar)s*(?:%(escape)s%(nmchar)s*)*' + #r')') % locals() + + comment = r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)' + + # only for specific purposes. The bang is grouped: + _bang_comment = r'(?:/\*(!?)[^*]*\*+(?:[^/*][^*]*\*+)*/)' + + string1 = \ + r'(?:\047[^\047\\\r\n\f]*(?:\\[^\r\n\f][^\047\\\r\n\f]*)*\047)' + string2 = r'(?:"[^"\\\r\n\f]*(?:\\[^\r\n\f][^"\\\r\n\f]*)*")' + strings = r'(?:%s|%s)' % (string1, string2) + + nl_string1 = \ + r'(?:\047[^\047\\\r\n\f]*(?:\\(?:[^\r]|\r\n?)[^\047\\\r\n\f]*)*\047)' + nl_string2 = r'(?:"[^"\\\r\n\f]*(?:\\(?:[^\r]|\r\n?)[^"\\\r\n\f]*)*")' + nl_strings = r'(?:%s|%s)' % (nl_string1, nl_string2) + + uri_nl_string1 = r'(?:\047[^\047\\]*(?:\\(?:[^\r]|\r\n?)[^\047\\]*)*\047)' + uri_nl_string2 = r'(?:"[^"\\]*(?:\\(?:[^\r]|\r\n?)[^"\\]*)*")' + uri_nl_strings = r'(?:%s|%s)' % (uri_nl_string1, uri_nl_string2) + + nl_escaped = r'(?:\\%(nl)s)' % locals() + + space = r'(?:%(spacechar)s|%(comment)s)' % locals() + + ie7hack = r'(?:>/\*\*/)' + + uri = (r'(?:' + r'(?:[^\000-\040"\047()\\\177]*' + r'(?:%(escape)s[^\000-\040"\047()\\\177]*)*)' + r'(?:' + r'(?:%(spacechar)s+|%(nl_escaped)s+)' + r'(?:' + r'(?:[^\000-\040"\047()\\\177]|%(escape)s|%(nl_escaped)s)' + r'[^\000-\040"\047()\\\177]*' + r'(?:%(escape)s[^\000-\040"\047()\\\177]*)*' + r')+' + r')*' + r')') % locals() + + nl_unesc_sub = _re.compile(nl_escaped).sub + + uri_space_sub = _re.compile(( + r'(%(escape)s+)|%(spacechar)s+|%(nl_escaped)s+' + ) % locals()).sub + uri_space_subber = lambda m: m.groups()[0] or '' + + space_sub_simple = _re.compile(( + r'[\r\n\f\040\t;]+|(%(comment)s+)' + ) % locals()).sub + space_sub_banged = _re.compile(( + r'[\r\n\f\040\t;]+|(%(_bang_comment)s+)' + ) % locals()).sub + + post_esc_sub = _re.compile(r'[\r\n\f\t]+').sub + + main_sub = _re.compile(( + r'([^\\"\047u>@\r\n\f\040\t/;:{}]+)' + r'|(?<=[{}(=:>+[,!])(%(space)s+)' + r'|^(%(space)s+)' + r'|(%(space)s+)(?=(([:{});=>+\],!])|$)?)' + r'|;(%(space)s*(?:;%(space)s*)*)(?=(\})?)' + r'|(\{)' + r'|(\})' + r'|(%(strings)s)' + r'|(?@\r\n\f\040\t/;:{}]*)' + ) % locals()).sub + + #print main_sub.__self__.pattern + + def main_subber(keep_bang_comments): + """ Make main subber """ + in_macie5, in_rule, at_media = [0], [0], [0] + + if keep_bang_comments: + space_sub = space_sub_banged + def space_subber(match): + """ Space|Comment subber """ + if match.lastindex: + group1, group2 = match.group(1, 2) + if group2: + if group1.endswith(r'\*/'): + in_macie5[0] = 1 + else: + in_macie5[0] = 0 + return group1 + elif group1: + if group1.endswith(r'\*/'): + if in_macie5[0]: + return '' + in_macie5[0] = 1 + return r'/*\*/' + elif in_macie5[0]: + in_macie5[0] = 0 + return '/**/' + return '' + else: + space_sub = space_sub_simple + def space_subber(match): + """ Space|Comment subber """ + if match.lastindex: + if match.group(1).endswith(r'\*/'): + if in_macie5[0]: + return '' + in_macie5[0] = 1 + return r'/*\*/' + elif in_macie5[0]: + in_macie5[0] = 0 + return '/**/' + return '' + + def fn_space_post(group): + """ space with token after """ + if group(5) is None or ( + group(6) == ':' and not in_rule[0] and not at_media[0]): + return ' ' + space_sub(space_subber, group(4)) + return space_sub(space_subber, group(4)) + + def fn_semicolon(group): + """ ; handler """ + return ';' + space_sub(space_subber, group(7)) + + def fn_semicolon2(group): + """ ; handler """ + if in_rule[0]: + return space_sub(space_subber, group(7)) + return ';' + space_sub(space_subber, group(7)) + + def fn_open(group): + """ { handler """ + # pylint: disable = W0613 + if at_media[0]: + at_media[0] -= 1 + else: + in_rule[0] = 1 + return '{' + + def fn_close(group): + """ } handler """ + # pylint: disable = W0613 + in_rule[0] = 0 + return '}' + + def fn_media(group): + """ @media handler """ + at_media[0] += 1 + return group(13) + + def fn_ie7hack(group): + """ IE7 Hack handler """ + if not in_rule[0] and not at_media[0]: + in_macie5[0] = 0 + return group(14) + space_sub(space_subber, group(15)) + return '>' + space_sub(space_subber, group(15)) + + table = ( + None, + None, + None, + None, + fn_space_post, # space with token after + fn_space_post, # space with token after + fn_space_post, # space with token after + fn_semicolon, # semicolon + fn_semicolon2, # semicolon + fn_open, # { + fn_close, # } + lambda g: g(11), # string + lambda g: 'url(%s)' % uri_space_sub(uri_space_subber, g(12)), + # url(...) + fn_media, # @media + None, + fn_ie7hack, # ie7hack + None, + lambda g: g(16) + ' ' + space_sub(space_subber, g(17)), + # :first-line|letter followed + # by [{,] (apparently space + # needed for IE6) + lambda g: nl_unesc_sub('', g(18)), # nl_string + lambda g: post_esc_sub(' ', g(19)), # escape + ) + + def func(match): + """ Main subber """ + idx, group = match.lastindex, match.group + if idx > 3: + return table[idx](group) + + # shortcuts for frequent operations below: + elif idx == 1: # not interesting + return group(1) + #else: # space with token before or at the beginning + return space_sub(space_subber, group(idx)) + + return func + + def cssmin(style, keep_bang_comments=False): # pylint: disable = W0621 + """ + Minify CSS. + + :Parameters: + `style` : ``str`` + CSS to minify + + `keep_bang_comments` : ``bool`` + Keep comments starting with an exclamation mark? (``/*!...*/``) + + :Return: Minified style + :Rtype: ``str`` + """ + return main_sub(main_subber(keep_bang_comments), style) + + return cssmin + +cssmin = _make_cssmin() + + +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 cssmin # pylint: disable = W0603 + cssmin = _make_cssmin(python_only=True) + _sys.stdout.write(cssmin( + _sys.stdin.read(), keep_bang_comments=keep_bang_comments + )) + main() diff --git a/ckan/include/rjsmin.py b/ckan/include/rjsmin.py new file mode 100644 index 00000000000..13e66e0e421 --- /dev/null +++ b/ckan/include/rjsmin.py @@ -0,0 +1,290 @@ +#!/usr/bin/env python +# -*- coding: ascii -*- +# +# Copyright 2011, 2012 +# Andr\xe9 Malo or his licensors, as applicable +# +# 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. +r""" +===================== + Javascript Minifier +===================== + +rJSmin is a javascript minifier written in python. + +The minifier is based on the semantics of `jsmin.c by Douglas Crockford`_\. + +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 '---' +- 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 1.2 and 7. 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 +""" +__author__ = "Andr\xe9 Malo" +__author__ = getattr(__author__, 'decode', lambda x: __author__)('latin-1') +__docformat__ = "restructuredtext en" +__license__ = "Apache License, Version 2.0" +__version__ = '1.0.3' +__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 + 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'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)' + 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) + 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])', # 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+-]') + + space_sub = _re.compile(( + r'([^\047"/\000-\040]+)' + r'|(%(strings)s[^\047"/\000-\040]*)' + r'|(?:(?<=%(preregex1)s)%(space)s*(%(regex)s[^\047"/\000-\040]*))' + r'|(?:(?<=%(preregex2)s)%(space)s*(%(regex)s[^\047"/\000-\040]*))' + 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.__self__.pattern + + def space_subber(match): + """ Substitution callback """ + # pylint: disable = C0321, 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 '' + + def jsmin(script): # 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 + + :Return: Minified script + :Rtype: ``str`` + """ + return space_sub(space_subber, '\n%s\n' % script).strip() + + return jsmin + +jsmin = _make_jsmin() + + +def jsmin_for_posers(script): + 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 regex. It's just for fun here and may + vanish any time. Use the `jsmin` function instead. + + :Parameters: + `script` : ``str`` + Script to minify + + :Return: Minified script + :Rtype: ``str`` + """ + 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 + '' + ) + + return _re.sub( + r'([^\047"/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?' + r'\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|' + r'\r)[^"\\\r\n]*)*"))[^\047"/\000-\040]*)|(?:(?<=[(,=:\[!&|?{};\r\n]' + r')(?:[\000-\011\013\014\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\01' + r'6-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*((?:/(?![\r\n/*])[^/' + r'\\\[\r\n]*(?:(?:\\[^\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\013\01' + r'4\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-#%-\04' + r'7)*,./:-@\\-^`|-~])|(?<=[^\000-#%-,./:-@\[-^`{-~-])((?:[\000-\011' + r'\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=[^\000-' + r'#%-,./:-@\[-^`{-~-])|(?<=\+)((?:[\000-\011\013\014\016-\040]|(?:/' + r'\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=\+\+)|(?<=-)((?:[\000-\011\013' + r'\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=--)|(?:[\00' + r'0-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))+|(?:(' + r'?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]' + r'*\*+(?:[^/*][^*]*\*+)*/))*)+', subber, '\n%s\n' % script + ).strip() + + +if __name__ == '__main__': + import sys as _sys + _sys.stdout.write(jsmin(_sys.stdin.read())) diff --git a/ckan/lib/helpers.py b/ckan/lib/helpers.py index b21d5a5b7bd..9955ce58c8e 100644 --- a/ckan/lib/helpers.py +++ b/ckan/lib/helpers.py @@ -33,6 +33,8 @@ from pylons import c from pylons.i18n import _ +import html_resources + get_available_locales = i18n.get_available_locales get_locales_dict = i18n.get_locales_dict @@ -800,6 +802,10 @@ def process_names(items): return items +def include_resource(resource): + r = getattr(html_resources, resource) + r.need() + # these are the functions that will end up in `h` template helpers # if config option restrict_template_vars is true __allowed_functions__ = [ @@ -853,6 +859,7 @@ def process_names(items): 'activity_div', 'lang_native_name', 'unselected_facet_items', + 'include_resource', # imported into ckan.lib.helpers 'literal', 'link_to', diff --git a/setup.py b/setup.py index d1e857d9802..2eb548fc4e8 100644 --- a/setup.py +++ b/setup.py @@ -104,6 +104,10 @@ [ckan.system_plugins] domain_object_mods = ckan.model.modification:DomainObjectModificationExtension + + [fanstatic.libraries] + resource = html_resources:resources + """, # setup.py test command needs a TestSuite so does not work with py.test # test_suite = 'nose.collector',