Permalink
Browse files

Merge branch 'release/0.6.3'

  • Loading branch information...
2 parents d4ce582 + 7036e51 commit 5ae7ef190de3a6ffd24f420983fd8fc31a976c5d @jezdez jezdez committed Apr 18, 2011
View
@@ -17,6 +17,7 @@ David Ziegler
Eugene Mirotin
Fenn Bailey
Gert Van Gool
+Jaap Roes
Jason Davies
Jeremy Dunck
Justin Lilly
View
@@ -76,7 +76,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
-utils.cached_property extracted from Celery
+utils.cache.cached_property extracted from Celery
-------------------------------------------
Copyright (c) 2009-2011, Ask Solem and contributors.
All rights reserved.
View
@@ -1,13 +1,65 @@
Django Compressor
=================
-Compresses linked and inline JavaCcript or CSS into a single cached file.
+Django Compressor combines and compresses linked and inline Javascript
+or CSS in a Django templates into cacheable static files by using the
+``compress`` template tag.
-The main website for django-compressor is
-`github.com/jezdez/django_compressor`_ where you can also file tickets.
+HTML in between ``{% compress js/css %}`` and ``{% endcompress %}`` is
+parsed and searched for CSS or JS. These styles and scripts are subsequently
+processed with optional, configurable compilers and filters.
-You can also install the `in-development version`_ of django-compressor with
+The default filter for CSS rewrites paths to static files to be absolute
+and adds a cache busting timestamp. For Javascript the default filter
+compresses it using ``jsmin``.
+
+As the final result the template tag outputs a ``<script>`` or ``<link>``
+tag pointing to the optimized file. These files are stored inside a folder
+and given an unique name based on their content. Alternatively it can also
+return the resulting content to the original template directly.
+
+Since the file name is dependend on the content these files can be given
+a far future expiration date without worrying about stale browser caches.
+
+The concatenation and compressing process can also be jump started outside
+of the request/response cycle by using the Django management command
+``manage.py compress``.
+
+Configurability & Extendibility
+-------------------------------
+
+Django Compressor is highly configurable and extendible. The HTML parsing
+is done using BeautifulSoup_ by default. As an alternative Django Compressor
+provides an lxml_ and a html5lib_ based parser, as well as an abstract base
+class that makes it easy to write a custom parser.
+
+Django Compressor also comes with built-in support for `CSS Tidy`_,
+`YUI CSS and JS`_ compressor, the Google's `Closure Compiler`_, a Python
+port of Douglas Crockford's JSmin_, a Python port of the YUI CSS Compressor
+cssmin_ and a filter to convert (some) images into `data URIs`_.
+
+If your setup requires a different compressor or other post-processing
+tool it will be fairly easy to implement a custom filter. Simply extend
+from one of the available base classes.
+
+More documentation about the usage and settings of Django Compressor can be
+found on `django_compressor.readthedocs.org`_.
+
+The source code for Django Compressor can be found and contributed to on
+`github.com/jezdez/django_compressor`_. There you can also file tickets.
+
+The `in-development version`_ of Django Compressor can be installed with
``pip install django_compressor==dev`` or ``easy_install django_compressor==dev``.
-.. _github.com/jezdez/django_compressor: http://github.com/jezdez/django_compressor
-.. _in-development version: http://github.com/jezdez/django_compressor/tarball/master#egg=django_compressor-dev
+.. _BeautifulSoup: http://www.crummy.com/software/BeautifulSoup/
+.. _lxml: http://lxml.de/
+.. _html5lib: http://code.google.com/p/html5lib/
+.. _CSS Tidy: http://csstidy.sourceforge.net/
+.. _YUI CSS and JS: http://developer.yahoo.com/yui/compressor/
+.. _Closure Compiler: http://code.google.com/closure/compiler/
+.. _JSMin: http://www.crockford.com/javascript/jsmin.html
+.. _cssmin: https://github.com/zacharyvoase/cssmin
+.. _data URIs: http://en.wikipedia.org/wiki/Data_URI_scheme
+.. _django_compressor.readthedocs.org: http://django_compressor.readthedocs.org/
+.. _github.com/jezdez/django_compressor: https://github.com/jezdez/django_compressor
+.. _in-development version: http://github.com/jezdez/django_compressor/tarball/develop#egg=django_compressor-dev
View
@@ -1,4 +1,4 @@
-VERSION = (0, 6, 2, "f", 0) # following PEP 386
+VERSION = (0, 6, 3, "f", 0) # following PEP 386
DEV_N = None
View
@@ -12,8 +12,8 @@
from compressor.exceptions import CompressorError, UncompressableFileError
from compressor.filters import CompilerFilter
from compressor.storage import default_storage
-from compressor.utils import get_class, cached_property, get_staticfiles_finders
-
+from compressor.utils import get_class, staticfiles
+from compressor.utils.cache import cached_property
class Compressor(object):
"""
@@ -30,7 +30,7 @@ def __init__(self, content=None, output_prefix="compressed"):
self.split_content = []
self.extra_context = {}
self.all_mimetypes = dict(settings.COMPRESS_PRECOMPILERS)
- self.finders = get_staticfiles_finders()
+ self.finders = staticfiles.finders
def split_contents(self):
"""
@@ -5,7 +5,7 @@
from compressor.conf import settings
from compressor.exceptions import FilterError
-from compressor.utils import cmd_split, FormattableString
+from compressor.utils import cmd_split, stringformat
logger = logging.getLogger("compressor.filters")
@@ -31,14 +31,15 @@ class CompilerFilter(FilterBase):
external commands.
"""
command = None
+ options = {}
- def __init__(self, content, filter_type=None, verbose=0, command=None):
+ def __init__(self, content, filter_type=None, verbose=0, command=None, **kwargs):
super(CompilerFilter, self).__init__(content, filter_type, verbose)
if command:
self.command = command
+ self.options.update(kwargs)
if self.command is None:
raise FilterError("Required command attribute not set")
- self.options = {}
self.stdout = subprocess.PIPE
self.stdin = subprocess.PIPE
self.stderr = subprocess.PIPE
@@ -56,7 +57,7 @@ def output(self, **kwargs):
ext = ".%s" % self.type and self.type or ""
outfile = tempfile.NamedTemporaryFile(mode='w', suffix=ext)
self.options["outfile"] = outfile.name
- cmd = FormattableString(self.command).format(**self.options)
+ cmd = stringformat.FormattableString(self.command).format(**self.options)
proc = subprocess.Popen(cmd_split(cmd),
stdout=self.stdout, stdin=self.stdin, stderr=self.stderr)
if infile is not None:
View
@@ -1,18 +1,7 @@
-from django.core.exceptions import ImproperlyConfigured
-
-from compressor.conf import settings
+from compressor.utils import staticfiles
from compressor.storage import CompressorFileStorage
-from compressor.utils import get_staticfiles_finders
-
-finders = get_staticfiles_finders()
-if finders is None:
- raise ImproperlyConfigured("When using the compressor staticfiles finder"
- "either django.contrib.staticfiles or the "
- "standalone version django-staticfiles needs "
- "to be installed.")
-
-class CompressorFinder(finders.BaseStorageFinder):
+class CompressorFinder(staticfiles.finders.BaseStorageFinder):
"""
A staticfiles finder that looks in COMPRESS_ROOT
for compressed files, to be used during development
@@ -11,12 +11,13 @@
from django.core.management.base import NoArgsCommand, CommandError
from django.template import Context, Template, TemplateDoesNotExist, TemplateSyntaxError
from django.utils.datastructures import SortedDict
+from django.utils.importlib import import_module
from compressor.cache import cache, get_offline_cachekey
from compressor.conf import settings
from compressor.exceptions import OfflineGenerationError
from compressor.templatetags.compress import CompressorNode
-from compressor.utils import walk, any, import_module
+from compressor.utils import walk, any
class Command(NoArgsCommand):
@@ -94,8 +95,8 @@ def compress(self, log=None, **options):
for root, dirs, files in walk(path,
followlinks=options.get('followlinks', False)):
templates.update(os.path.join(root, name)
- for name in files if any(fnmatch(name, "*%s" % glob)
- for glob in extensions))
+ for name in files if not name.startswith('.') and
+ any(fnmatch(name, "*%s" % glob) for glob in extensions))
if not templates:
raise OfflineGenerationError("No templates found. Make sure your "
"TEMPLATE_LOADERS and TEMPLATE_DIRS "
View
@@ -1,119 +0,0 @@
-from django.utils.encoding import smart_unicode
-
-from compressor.exceptions import ParserError
-
-
-class ParserBase(object):
- """
- Base parser to be subclassed when creating an own parser.
- """
- def __init__(self, content):
- self.content = content
-
- def css_elems(self):
- """
- Return an iterable containing the css elements to handle
- """
- raise NotImplementedError
-
- def js_elems(self):
- """
- Return an iterable containing the js elements to handle
- """
- raise NotImplementedError
-
- def elem_attribs(self, elem):
- """
- Return the dictionary like attribute store of the given element
- """
- raise NotImplementedError
-
- def elem_content(self, elem):
- """
- Return the content of the given element
- """
- raise NotImplementedError
-
- def elem_name(self, elem):
- """
- Return the name of the given element
- """
- raise NotImplementedError
-
- def elem_str(self, elem):
- """
- Return the string representation of the given elem
- """
- raise NotImplementedError
-
-
-class BeautifulSoupParser(ParserBase):
- _soup = None
-
- @property
- def soup(self):
- try:
- from BeautifulSoup import BeautifulSoup
- except ImportError, e:
- raise ParserError("Error while initializing Parser: %s" % e)
- if self._soup is None:
- self._soup = BeautifulSoup(self.content)
- return self._soup
-
- def css_elems(self):
- return self.soup.findAll({'link': True, 'style': True})
-
- def js_elems(self):
- return self.soup.findAll('script')
-
- def elem_attribs(self, elem):
- return dict(elem.attrs)
-
- def elem_content(self, elem):
- return elem.string
-
- def elem_name(self, elem):
- return elem.name
-
- def elem_str(self, elem):
- return smart_unicode(elem)
-
-
-class LxmlParser(ParserBase):
- _tree = None
-
- @property
- def tree(self):
- try:
- from lxml import html
- from lxml.etree import tostring
- except ImportError, e:
- raise ParserError("Error while initializing Parser: %s" % e)
- if self._tree is None:
- content = '<root>%s</root>' % self.content
- self._tree = html.fromstring(content)
- try:
- ignore = tostring(self._tree, encoding=unicode)
- except UnicodeDecodeError:
- self._tree = html.soupparser.fromstring(content)
- return self._tree
-
- def css_elems(self):
- return self.tree.xpath('link[@rel="stylesheet"]|style')
-
- def js_elems(self):
- return self.tree.findall('script')
-
- def elem_attribs(self, elem):
- return elem.attrib
-
- def elem_content(self, elem):
- return smart_unicode(elem.text)
-
- def elem_name(self, elem):
- return elem.tag
-
- def elem_str(self, elem):
- from lxml import etree
- return smart_unicode(
- etree.tostring(elem, method='html', encoding=unicode))
@@ -0,0 +1,5 @@
+# support legacy parser module usage
+from compressor.parser.base import ParserBase
+from compressor.parser.beautifulsoup import BeautifulSoupParser
+from compressor.parser.lxml import LxmlParser
+from compressor.parser.html5lib import Html5LibParser
View
@@ -0,0 +1,42 @@
+class ParserBase(object):
+ """
+ Base parser to be subclassed when creating an own parser.
+ """
+ def __init__(self, content):
+ self.content = content
+
+ def css_elems(self):
+ """
+ Return an iterable containing the css elements to handle
+ """
+ raise NotImplementedError
+
+ def js_elems(self):
+ """
+ Return an iterable containing the js elements to handle
+ """
+ raise NotImplementedError
+
+ def elem_attribs(self, elem):
+ """
+ Return the dictionary like attribute store of the given element
+ """
+ raise NotImplementedError
+
+ def elem_content(self, elem):
+ """
+ Return the content of the given element
+ """
+ raise NotImplementedError
+
+ def elem_name(self, elem):
+ """
+ Return the name of the given element
+ """
+ raise NotImplementedError
+
+ def elem_str(self, elem):
+ """
+ Return the string representation of the given elem
+ """
+ raise NotImplementedError
Oops, something went wrong.

0 comments on commit 5ae7ef1

Please sign in to comment.