Commit ee0facf5 by Steven Cummings

Merge remote-tracking branch 'upstream/master'

parents 23a4ad06 b498f48f
......@@ -15,4 +15,6 @@ tests/assets/js/dummy.js
.tox/
.DS_Store
.idea
.venv
\ No newline at end of file
.venv
.project
.pydevproject
......@@ -3,3 +3,5 @@ python:
- 2.7
install: pip install tox --use-mirrors
script: tox -e py26-1.2.X,py27-1.2.X,py26-1.3.X,py27-1.3.X,py26,py27,docs
notifications:
irc: "irc.freenode.org#django-pipeline"
\ No newline at end of file
......@@ -7,6 +7,7 @@ or just made Pipeline more awesome.
* Adam Charnock <adam@omniwiki.co.uk>
* Alexander Artemenko <svetlyak40wt>
* Alexander Pugachev <alexander.pugachev@gmail.com>
* Alexis Svinartchouk <zvin@free.fr>
* Andreas Cederström <andreas@klydd.se>
* Ara Anjargolian <ara818@gmail.com>
* Balazs Kossovics <balazs.kossovics@e-loue.com>
......@@ -27,5 +28,7 @@ or just made Pipeline more awesome.
* Sam Thomson <sammthomson@gmail.com>
* Sander Smits <jhmsmits@gmail.com>
* Sander Smits <jsmits@imac.lan>
* Sirex <sirexas@gmail.com>
* Steven Cummings <estebistec@gmail.com>
* Timothée Peignier <timothee.peignier@tryphon.org>
* Trey Smith <trey.smith@nasa.gov>
.. :changelog:
History
=======
1.2.8
-----
* Fix bugs in our glob implementation.
1.2.7
-----
* Many documentation improvements. Thanks to Alexis Svinartchouk.
* Improve python packaging.
* Don't write silently to STATIC_ROOT when we shouldn't.
* Accept new .sass extension in SASSCompiler. Thanks to Jonas Geiregat for the report.
1.2.6
-----
* New lines in templates are now escaper rather than deleted. Thanks to Trey Smith for the report and the patch.
* Improve how we find where to write compiled file. Thanks to sirex for the patch.
1.2.5
-----
* Fix import error for cssmin and jsmin compressors. Thanks to Berker Peksag for the report.
* Fix error with default template function. Thanks to David Charbonnier for the patch and report.
1.2.4
-----
* Fix encoding problem.
* Improve storage documentation
* Add mention of the IRC channel #django-pipeline in documentation
1.2.3
-----
* Fix javascript mime type bug. Thanks to Chase Seibert for the report.
1.2.2.1
-------
* License clarification. Thanks to Dmitry Nezhevenko for the report.
1.2.2
-----
* Allow to disable javascript closure wrapper with ``PIPELINE_DISABLE_WRAPPER``.
* Various improvements to documentation.
* Slightly improve how we find where to write compiled file.
* Simplify module hierarchy.
* Allow templatetag to output mimetype to be able to use less.js and other javascript compilers.
1.2.1
-----
* Fixing a bug in ``FinderStorage`` when using prefix in staticfiles. Thanks to Christian Hammond for the report and testing.
* Make ``PIPELINE_ROOT`` defaults more sane. Thanks to Konstantinos Pachnis for the report.
1.2.0
-----
* Dropped ``synccompress`` command in favor of staticfiles ``collecstatic`` command.
* Added file versionning via staticfiles ``CachedStaticFilesStorage``.
* Added a default js template language.
* Dropped ``PIPELINE_AUTO`` settings in favor of simple ``PIPELINE``.
* Renamed ``absolute_asset_paths`` to ``absolute_paths`` for brevity.
* Made packages lazy to avoid doing unnecessary I/O.
* Dropped ``external_urls`` support for now.
* Add cssmin compressor. Thanks to Steven Cummings.
* Jsmin is no more bundle with pipeline.
Pipeline
--------
Copyright (©) 2008 Andreas Pelme <andreas@pelme.se>
Copyright (©) 2011-2012 Timothée Peignier <timothee.peignier@tryphon.org>
......@@ -19,32 +17,4 @@ 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.
jsmin.py (License-information from the file)
--------------------------------------------
This code is original from jsmin by Douglas Crockford, it was translated to
Python by Baruch Even. The original code had the following copyright and
license.
Copyright (©) 2002 Douglas Crockford (www.crockford.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The Software shall be used for Good, not Evil.
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.
\ No newline at end of file
THE SOFTWARE.
\ No newline at end of file
recursive-include pipeline/templates *.html
include docs/*
include AUTHORS
include LICENSE
include AUTHORS LICENSE README.rst HISTORY.rst
......@@ -3,13 +3,13 @@ Pipeline
Pipeline is an asset packaging library for Django, providing both CSS and JavaScript concatenation and compression, built-in JavaScript template support, and optional data-URI image and font embedding.
To install it :
To install it : ::
pip install django-pipeline
For documentation, usage, and examples, see :
http://django-pipeline.readthedocs.org/
http://django-pipeline.readthedocs.org
To suggest a feature or report a bug :
https://github.com/cyberdelia/django-pipeline/issues
Changelog
=========
1.2.0
-----
* Dropped ``synccompress`` command in favor of staticfiles ``collecstatic`` command.
* Added file versionning via staticfiles ``CachedStaticFilesStorage``.
* Added a default js template language.
* Dropped ``PIPELINE_AUTO`` settings in favor of simple ``PIPELINE``.
* Renamed ``absolute_asset_paths`` to ``absolute_paths`` for brevity.
* Made packages lazy to avoid doing unnecessary I/O.
* Dropped ``external_urls`` support for now.
* Add cssmin compressor.
* Jsmin is not more bundle with pipeline.
......@@ -8,7 +8,7 @@ Compilers
Coffee Script compiler
======================
The Coffee Script compiler use `Coffee Script <http://jashkenas.github.com/coffee-script/>`_
The Coffee Script compiler uses `Coffee Script <http://jashkenas.github.com/coffee-script/>`_
to compile your javascripts.
To use it add this to your ``PIPELINE_COMPILERS`` ::
......@@ -35,7 +35,7 @@ To use it add this to your ``PIPELINE_COMPILERS`` ::
LESS compiler
=============
The LESS compiler use `LESS <http://lesscss.org/>`_
The LESS compiler uses `LESS <http://lesscss.org/>`_
to compile your stylesheets.
To use it add this to your ``PIPELINE_COMPILERS`` ::
......@@ -62,7 +62,7 @@ To use it add this to your ``PIPELINE_COMPILERS`` ::
SASS compiler
=============
The SASS compiler use `SASS <http://sass-lang.com/>`_
The SASS compiler uses `SASS <http://sass-lang.com/>`_
to compile your stylesheets.
To use it add this to your ``PIPELINE_COMPILERS`` ::
......@@ -91,7 +91,7 @@ To use it add this to your ``PIPELINE_COMPILERS`` ::
Stylus compiler
===============
The Stylus compiler use `Stylus <http://learnboost.github.com/stylus/>`
The Stylus compiler uses `Stylus <http://learnboost.github.com/stylus/>`
to compile your stylesheets.
To use it add this to your ``PIPELINE_COMPILERS`` ::
......@@ -121,10 +121,10 @@ To use it add this to your ``PIPELINE_COMPILERS`` ::
Write your own compiler class
=============================
To write your own compiler class, for example want to implement other types
You can write your own compiler class, for example if you want to implement other types
of compilers.
All you need to do is to create a class that inherits from ``pipeline.compilers.CompilerBase``
To do so, you just have to create a class that inherits from ``pipeline.compilers.CompilerBase``
and implements ``match_file`` and ``compile_file`` when needed.
Finally, specify it in the tuple of compilers ``PIPELINE_COMPILERS`` in the settings.
......@@ -132,7 +132,7 @@ Finally, specify it in the tuple of compilers ``PIPELINE_COMPILERS`` in the sett
Example
-------
A custom compiler for a imaginary compiler called jam ::
A custom compiler for an imaginary compiler called jam ::
from pipeline.compilers import CompilerBase
......
......@@ -8,7 +8,7 @@ Compressors
YUI Compressor compressor
=========================
The YUI compressor use `yui-compressor <http://developer.yahoo.com/yui/compressor/>`_
The YUI compressor uses `yui-compressor <http://developer.yahoo.com/yui/compressor/>`_
for compressing javascript and stylesheets.
To use it for your stylesheets add this to your ``PIPELINE_CSS_COMPRESSOR`` ::
......@@ -50,7 +50,7 @@ To use it for your javascripts add this to your ``PIPELINE_JS_COMPRESSOR`` ::
Closure Compiler compressor
===========================
The Closure compressor use `Google Closure Compiler <http://code.google.com/closure/compiler/>`_
The Closure compressor uses `Google Closure Compiler <http://code.google.com/closure/compiler/>`_
to compress javascripts.
To use it add this to your ``PIPELINE_JS_COMPRESSOR`` ::
......@@ -83,7 +83,7 @@ To use it add this to your ``PIPELINE_JS_COMPRESSOR`` ::
UglifyJS compressor
===================
The UglifyJS compressor use `UglifyJS <https://github.com/mishoo/UglifyJS/>`_ to
The UglifyJS compressor uses `UglifyJS <https://github.com/mishoo/UglifyJS/>`_ to
compress javascripts.
To use it add this to your ``PIPELINE_JS_COMPRESSOR`` ::
......@@ -110,7 +110,7 @@ To use it add this to your ``PIPELINE_JS_COMPRESSOR`` ::
JSMin compressor
================
The jsmin compressor use Douglas Crockford jsmin tool to
The jsmin compressor uses Douglas Crockford jsmin tool to
compress javascripts.
To use it add this to your ``PIPELINE_JS_COMPRESSOR`` ::
......@@ -125,7 +125,7 @@ Install the jsmin library with your favorite Python package manager ::
CSSTidy compressor
==================
The CSStidy compressor use `CSStidy <http://csstidy.sourceforge.net/>`_ to compress
The CSStidy compressor uses `CSStidy <http://csstidy.sourceforge.net/>`_ to compress
stylesheets.
To us it for your stylesheets add this to your ``PIPELINE_CSS_COMPRESSOR`` ::
......@@ -163,10 +163,10 @@ Install the cssmin library with your favorite Python package manager. E.g. ::
Write your own compressor class
===============================
To write your own compressor class, for example want to implement other types
You can write your own compressor class, for example if you want to implement other types
of compressors.
All you need to do is to create a class that inherits from ``pipeline.compressors.CompressorBase``
To do so, you just have to create a class that inherits from ``pipeline.compressors.CompressorBase``
and implements ``compress_css`` and/or a ``compress_js`` when needed.
Finally, add it to ``PIPELINE_CSS_COMPRESSOR`` or
......@@ -175,7 +175,7 @@ Finally, add it to ``PIPELINE_CSS_COMPRESSOR`` or
Example
-------
A custom compressor for a imaginary compressor called jam ::
A custom compressor for an imaginary compressor called jam ::
from pipeline.compressors import CompressorBase
......
......@@ -11,7 +11,8 @@
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys, os
import sys
import os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
......@@ -50,7 +51,7 @@ copyright = u'2011-2012, Timothée Peignier'
# The short X.Y version.
version = '1.2'
# The full version, including alpha/beta/rc tags.
release = '1.2c1'
release = '1.2.9'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
......
......@@ -51,7 +51,7 @@ Group options
**Required**
Is a tuple with the source files to be compressed.
The files are concatenated in the order it is specified in the tuple.
The files are concatenated in the order specified in the tuple.
``output_filename``
......@@ -147,7 +147,7 @@ Other settings
``PIPELINE_TEMPLATE_EXT``
.........................
The extension for which Pipeline will consider the file as a Javascript templates.
The extension for which Pipeline will consider the file as a Javascript template.
To use a different extension, like ``.mustache``, set this settings to ``.mustache``.
Defaults to ``".jst"``
......@@ -157,7 +157,7 @@ Other settings
JavaScript function that compiles your JavaScript templates.
Pipeline doesn't bundle a javascript template library, but the default
settings is to use the
setting is to use the
`underscore <http://documentcloud.github.com/underscore/>`_ template function.
Defaults to ``"_.template"``
......@@ -167,7 +167,7 @@ Embedding fonts and images
==========================
You can embed fonts and images directly in your compiled css, using Data-URI in
modern browser.
modern browsers.
To do so, setup variant group options to the method you wish to use : ::
......@@ -192,5 +192,17 @@ Images and fonts are embedded following these rules :
Rewriting CSS urls
==================
If source CSS contain a relative URL (i.e. relative to current file),
those URL will be converted to full relative path.
If the source CSS contains relative URLs (i.e. relative to current file),
those URLs will be converted to full relative path.
Wrapped javascript output
=========================
All javascript output is wrapped in an anonymous function : ::
(function(){ ... })();
This safety wrapper, make it difficult to pollute the global namespace by accident and improve performance.
You can override this behavior by setting ``PIPELINE_DISABLE_WRAPPER`` to ``True``.
......@@ -7,6 +7,9 @@ template support, and optional data-URI image and font embedding.
You can report bugs and discuss features on the `issues page <https://github.com/cyberdelia/django-pipeline/issues>`_.
You can discuss features or ask questions on the IRC channel on freenode : `#django-pipeline <irc://irc.freenode.net/django-pipeline>`_
Table Of Contents
=================
......@@ -22,7 +25,7 @@ Table Of Contents
storages
signals
using
changelog
Indices and tables
==================
......
......@@ -9,12 +9,19 @@ Installation
pip install django-pipeline
2. Add 'compress' to your ``INSTALLED_APPS`` ::
2. Add 'pipeline' to your ``INSTALLED_APPS`` ::
INSTALLED_APPS = (
'pipeline',
)
3. Use a pipeline storage for ``STATICFILES_STORAGE`` ::
STATICFILES_STORAGE = 'pipeline.storage.PipelineCachedStorage'
.. note::
You need to use ``Django>=1.4`` or ``django-staticfiles>=1.2.1`` to be able to use this version of pipeline.
.. _GitHub: http://github.com/cyberdelia/django-pipeline
.. _PyPI: http://pypi.python.org/pypi/django-pipeline
......
......@@ -4,7 +4,7 @@
Signals
=======
A list of all signals send by pipeline.
List of all signals sent by pipeline.
css_compressed
--------------
......
......@@ -7,7 +7,7 @@ Storages
Using with a custom storage
===========================
Pipeline use `Django Storage <https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#storages>`_
Pipeline uses `Django Storage <https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#storages>`_
to read, save and delete files, by default it use an improved ``StaticFilesStorage``.
You can provide your own via ``PIPELINE_STORAGE`` : ::
......@@ -31,3 +31,23 @@ Pipeline is also providing a storage that play nicely with staticfiles app
particularly for development : ::
PIPELINE_STORAGE = 'pipeline.storage.PipelineFinderStorage'
Using with other storages
=========================
You can also use your own custom storage, for example, if you want to use S3 for your assets : ::
STATICFILES_STORAGE = 'your.app.S3PipelineStorage'
Your storage only need to inherit from ``PipelineMixin`` and/or ``CachedFilesMixin`` : ::
from staticfiles.storage import CachedFilesMixin
from pipeline.storage import PipelineMixin
from storages.backends.s3boto import S3BotoStorage
class S3PipelineStorage(PipelineMixin, CachedFilesMixin, S3BotoStorage):
pass
......@@ -26,7 +26,7 @@ For example, if you have the following template ``js/templates/photo/detail.jst`
</div>
</div>
They will be available from your javascript code via window.JST ::
It will be available from your javascript code via window.JST ::
JST.photo_detail({ src:"images/baby-panda.jpg", caption:"A baby panda is born" });
......
......@@ -52,7 +52,7 @@ Middleware
To enable HTML compression add ``pipeline.middleware.MinifyHTMLMiddleware``,
to your ``MIDDLEWARE_CLASSES`` settings.
Ensure that it comes after any middleware which modify your HTML, like ``GZipMiddleware`` ::
Ensure that it comes after any middleware which modifies your HTML, like ``GZipMiddleware`` ::
MIDDLEWARE_CLASSES = (
'django.middleware.gzip.GZipMiddleware',
......
......@@ -23,20 +23,22 @@ class Compiler(object):
return [to_class(compiler) for compiler in settings.PIPELINE_COMPILERS]
compilers = property(compilers)
def compile(self, paths):
def compile(self, paths, force=False):
for index, path in enumerate(paths):
for compiler in self.compilers:
compiler = compiler(self.verbose)
if compiler.match_file(path):
new_path = self.output_path(path, compiler.output_extension)
content = self.read_file(path)
paths[index] = new_path
if not force and not self.is_outdated(path, new_path):
continue
try:
content = self.read_file(path)
compiled_content = compiler.compile_file(content, finders.find(path))
self.save_file(new_path, compiled_content)
except CompilerError:
if not self.storage.exists(new_path) or not settings.PIPELINE:
raise
paths[index] = new_path
return paths
def output_path(self, path, extension):
......@@ -49,6 +51,12 @@ class Compiler(object):
file.close()
return content
def is_outdated(self, path, new_path):
try:
return self.storage.modified_time(path) > self.storage.modified_time(new_path)
except (OSError, NotImplementedError):
return True
def save_file(self, path, content):
return self.storage.save(path, ContentFile(smart_str(content)))
......
......@@ -8,7 +8,7 @@ class SASSCompiler(SubProcessCompiler):
output_extension = 'css'
def match_file(self, filename):
return filename.endswith('.scss')
return filename.endswith(('.scss', '.sass'))
def compile_file(self, content, path):
command = "%s --scss %s %s" % (
......
......@@ -5,6 +5,8 @@ import subprocess
from itertools import takewhile
from django.utils.encoding import smart_str
try:
from staticfiles import finders
except ImportError:
......@@ -21,7 +23,7 @@ URL_DETECTOR = r'url\([\'"]?([^\s)]+\.[a-z]+[\?\#\d\w]*)[\'"]?\)'
URL_REPLACER = r'url\(__EMBED__(.+?)(\?\d+)?\)'
DEFAULT_TEMPLATE_FUNC = "template"
TEMPLATE_FUNC = """var template = function(str){var fn = new Function('obj', 'var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push(\''+str.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/<%=([\s\S]+?)%>/g,function(match,code){return "',"+code.replace(/\\'/g, "'")+",'";}).replace(/<%([\s\S]+?)%>/g,function(match,code){return "');"+code.replace(/\\'/g, "'").replace(/[\r\n\t]/g,' ')+"__p.push('";}).replace(/\r/g,'\\r').replace(/\n/g,'\\n').replace(/\t/g,'\\t')+"');}return __p.join('');");return fn;};"""
TEMPLATE_FUNC = r"""var template = function(str){var fn = new Function('obj', 'var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push(\''+str.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/<%=([\s\S]+?)%>/g,function(match,code){return "',"+code.replace(/\\'/g, "'")+",'";}).replace(/<%([\s\S]+?)%>/g,function(match,code){return "');"+code.replace(/\\'/g, "'").replace(/[\r\n\t]/g,' ')+"__p.push('";}).replace(/\r/g,'\\r').replace(/\n/g,'\\n').replace(/\t/g,'\\t')+"');}return __p.join('');");return fn;};"""
MIME_TYPES = {
'.png': 'image/png',
......@@ -58,7 +60,9 @@ class Compressor(object):
js = self.concatenate(paths)
if templates:
js = js + self.compile_templates(templates)
js = "(function() { %s }).call(this);" % js
if not settings.PIPELINE_DISABLE_WRAPPER:
js = "(function() { %s }).call(this);" % js
compressor = self.js_compressor
if compressor:
......@@ -87,7 +91,7 @@ class Compressor(object):
base_path = self.base_path(paths)
for path in paths:
contents = self.read_file(path)
contents = re.sub(r"\r?\n", "", contents)
contents = re.sub(r"\r?\n", "\\\\n", contents)
contents = re.sub(r"'", "\\'", contents)
name = self.template_name(path, base_path)
compiled += "%s['%s'] = %s('%s');\n" % (
......@@ -132,7 +136,7 @@ class Compressor(object):
output_filename, variant)
return "url(%s)" % asset_url
content = self.read_file(path)
content = re.sub(URL_DETECTOR, reconstruct, content)
content = re.sub(URL_DETECTOR, reconstruct, smart_str(content))
stylesheets.append(content)
return '\n'.join(stylesheets)
......@@ -229,7 +233,7 @@ class SubProcessCompressor(CompressorBase):
def execute_command(self, command, content):
pipe = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE,
stdin=subprocess.PIPE, stderr=subprocess.PIPE)
pipe.stdin.write(content)
pipe.stdin.write(smart_str(content))
pipe.stdin.close()
compressed_content = pipe.stdout.read()
......
from __future__ import absolute_import
from pipeline.compressors import CompressorBase
......
from __future__ import absolute_import
from pipeline.compressors import CompressorBase
......
......@@ -2,7 +2,7 @@ from django.conf import settings
PIPELINE = getattr(settings, 'PIPELINE', not settings.DEBUG)
PIPELINE_ROOT = getattr(settings, 'PIPELINE_ROOT', settings.STATIC_URL)
PIPELINE_ROOT = getattr(settings, 'PIPELINE_ROOT', settings.STATIC_ROOT)
PIPELINE_URL = getattr(settings, 'PIPELINE_URL', settings.STATIC_URL)
PIPELINE_STORAGE = getattr(settings, 'PIPELINE_STORAGE',
......@@ -23,6 +23,8 @@ PIPELINE_TEMPLATE_NAMESPACE = getattr(settings, 'PIPELINE_TEMPLATE_NAMESPACE', "
PIPELINE_TEMPLATE_EXT = getattr(settings, 'PIPELINE_TEMPLATE_EXT', ".jst")
PIPELINE_TEMPLATE_FUNC = getattr(settings, 'PIPELINE_TEMPLATE_FUNC', "template")
PIPELINE_DISABLE_WRAPPER = getattr(settings, 'PIPELINE_DISABLE_WRAPPER', False)
PIPELINE_CSSTIDY_BINARY = getattr(settings, 'PIPELINE_CSSTIDY_BINARY', '/usr/local/bin/csstidy')
PIPELINE_CSSTIDY_ARGUMENTS = getattr(settings, 'PIPELINE_CSSTIDY_ARGUMENTS', '--template=highest')
......@@ -48,5 +50,11 @@ PIPELINE_STYLUS_ARGUMENTS = getattr(settings, 'PIPELINE_STYLUS_ARGUMENTS', '')
PIPELINE_LESS_BINARY = getattr(settings, 'PIPELINE_LESS_BINARY', '/usr/local/bin/lessc')
PIPELINE_LESS_ARGUMENTS = getattr(settings, 'PIPELINE_LESS_ARGUMENTS', '')
PIPELINE_MIMETYPES = getattr(settings, 'PIPELINE_MIMETYPES', (
('text/coffeescript', '.coffee'),
('text/less', '.less'),
('text/javascript', '.js')
))
if PIPELINE_COMPILERS is None:
PIPELINE_COMPILERS = []
......@@ -23,7 +23,12 @@ def iglob(pathname):
"""
if not has_magic(pathname):
yield pathname
try:
if default_storage.exists(pathname):
yield pathname
except NotImplementedError:
# Being optimistic
yield pathname
return
dirname, basename = os.path.split(pathname)
if not dirname:
......@@ -51,7 +56,9 @@ def glob1(dirname, pattern):
try:
directories, files = default_storage.listdir(dirname)
names = directories + files
except NotImplementedError:
except Exception:
# We are not sure that dirname is a real directory
# and storage implementations are really exotic.
return []
if pattern[0] != '.':
names = filter(lambda x: x[0] != '.', names)
......
......@@ -89,14 +89,14 @@ class Packager(object):
output_filename=package.output_filename,
variant=package.variant, **kwargs)
def compile(self, paths):
return self.compiler.compile(paths)
def compile(self, paths, force=False):
return self.compiler.compile(paths, force=force)
def pack(self, package, compress, signal, **kwargs):
output_filename = package.output_filename
if self.verbose:
print "Saving: %s" % output_filename
paths = self.compile(package.paths)
paths = self.compile(package.paths, force=True)
content = compress(paths, **kwargs)
self.save_file(output_filename, content)
signal.send(sender=self, package=package, **kwargs)
......
......@@ -15,6 +15,8 @@ from pipeline.conf import settings
class PipelineMixin(object):
packing = True
def post_process(self, paths, dry_run=False, **options):
if dry_run:
return []
......@@ -23,11 +25,15 @@ class PipelineMixin(object):
packager = Packager(storage=self)
for package_name in packager.packages['css']:
package = packager.package_for('css', package_name)
output_file = packager.pack_stylesheets(package)
output_file = package.output_filename
if self.packing:
packager.pack_stylesheets(package)
paths[output_file] = (self, output_file)
for package_name in packager.packages['js']:
package = packager.package_for('js', package_name)
output_file = packager.pack_javascripts(package)
output_file = package.output_filename
if self.packing:
packager.pack_javascripts(package)
paths[output_file] = (self, output_file)
super_class = super(PipelineMixin, self)
......@@ -45,14 +51,26 @@ class PipelineMixin(object):
return name
class NonPackagingMixin(object):
packing = False
class PipelineStorage(PipelineMixin, StaticFilesStorage):
pass
class NonPackagingPipelineStorage(NonPackagingMixin, PipelineStorage):
pass
class PipelineCachedStorage(PipelineMixin, CachedFilesMixin, StaticFilesStorage):
pass
class NonPackagingPipelineCachedStorage(NonPackagingMixin, PipelineCachedStorage):
pass
class BaseFinderStorage(PipelineStorage):
finders = None
......@@ -72,7 +90,7 @@ class BaseFinderStorage(PipelineStorage):
def exists(self, name):
exists = self.finders.find(name) != None
if not exists:
exists = super(BaseFinderStorage, self).exists(name)
return super(BaseFinderStorage, self).exists(name)
return exists
def listdir(self, path):
......@@ -83,11 +101,25 @@ class BaseFinderStorage(PipelineStorage):
except OSError:
pass
def _save(self, name, content):
def find_storage(self, name):
for finder in finders.get_finders():
for path, storage in finder.list([]):
if os.path.dirname(name) in path:
return storage._save(name, content)
if path == name:
return storage
if os.path.splitext(path)[0] == os.path.splitext(name)[0]:
return storage
raise ValueError("The file '%s' could not be found with %r." % (name, self))
def _open(self, name, mode="rb"):
storage = self.find_storage(name)
return storage._open(name, mode)
def _save(self, name, content):
storage = self.find_storage(name)
# Ensure we overwrite file, since we have no control on external storage
if storage.exists(name):
storage.delete(name)
return storage._save(name, content)
class PipelineFinderStorage(BaseFinderStorage):
......
<link href="{{ url }}" rel="stylesheet" type="text/css"{% if media %} media="{{ media }}"{% endif %}{% if title %} title="{{ title|default:"all" }}"{% endif %}{% if charset %} charset="{{ charset }}"{% endif %} />
\ No newline at end of file
<link href="{{ url }}" rel="stylesheet" type="{{ type }}"{% if media %} media="{{ media }}"{% endif %}{% if title %} title="{{ title|default:"all" }}"{% endif %}{% if charset %} charset="{{ charset }}"{% endif %} />
\ No newline at end of file
<script {% if async %}async{% endif %} {% if defer %}defer{% endif %} type="text/javascript" src="{{ url }}" charset="utf-8"></script>
\ No newline at end of file
<script {% if async %}async{% endif %} {% if defer %}defer{% endif %} type="{{ type }}" src="{{ url }}" charset="utf-8"></script>
\ No newline at end of file
......@@ -8,6 +8,7 @@ from django.template.loader import render_to_string
from pipeline.conf import settings
from pipeline.packager import Packager, PackageNotFound
from pipeline.utils import guess_type
register = template.Library()
......@@ -38,6 +39,7 @@ class CompressedCSSNode(template.Node):
template_name = package.template_name or "pipeline/css.html"
context = package.extra_context
context.update({
'type': guess_type(path, 'text/css'),
'url': staticfiles_storage.url(path)
})
return render_to_string(template_name, context)
......@@ -74,6 +76,7 @@ class CompressedJSNode(template.Node):
template_name = package.template_name or "pipeline/js.html"
context = package.extra_context
context.update({
'type': guess_type(path, 'text/javascript'),
'url': staticfiles_storage.url(path)
})
return render_to_string(template_name, context)
......
import mimetypes
import os
import sys
import urllib
......@@ -5,6 +6,8 @@ import urllib
from django.utils import importlib
from django.utils.encoding import smart_str
from pipeline.conf import settings
def to_class(class_str):
if not class_str:
......@@ -22,6 +25,15 @@ def filepath_to_uri(path):
return urllib.quote(smart_str(path).replace("\\", "/"), safe="/~!*()'#?")
def guess_type(path, default=None):
for type, ext in settings.PIPELINE_MIMETYPES:
mimetypes.add_type(type, ext)
mimetype, _ = mimetypes.guess_type(path)
if not mimetype:
return default
return mimetype
def _relpath_nt(path, start=os.path.curdir):
"""Return a relative version of a path"""
if not path:
......
......@@ -4,14 +4,14 @@ from setuptools import setup, find_packages
setup(
name='django-pipeline',
version='1.2c1',
version='1.2.9',
description='Pipeline is an asset packaging library for Django.',
long_description="""Pipeline is an asset packaging library for Django, providing
both CSS and JavaScript concatenation and compression, built-in JavaScript
template support, and optional data-URI image and font embedding.""",
long_description=open('README.rst').read() + '\n\n' +
open('HISTORY.rst').read(),
author='Timothée Peignier',
author_email='timothee.peignier@tryphon.org',
url='https://github.com/cyberdelia/django-pipeline',
license=open('LICENSE').read(),
packages=find_packages(),
zip_safe=False,
include_package_data=True,
......
.third {
display: none;
}
......@@ -29,6 +29,7 @@ STATIC_ROOT = local_path('static/')
STATIC_URL = '/static/'
STATICFILES_DIRS = (
local_path('assets/'),
local_path('assets2/'),
)
STATICFILES_FINDERS = (
'staticfiles.finders.FileSystemFinder',
......
# -*- coding: utf-8 flake8: noqa -*-
from packager import *
from compressor import *
from compiler import *
from compressor import *
from glob import *
from packager import *
from storage import *
from utils import *
\ No newline at end of file
......@@ -71,12 +71,12 @@ class CompressorTest(TestCase):
def test_compile_templates(self):
templates = self.compressor.compile_templates(['templates/photo/list.jst'])
self.assertEquals(templates, """window.JST = window.JST || {};\n%s\nwindow.JST['list'] = template('<div class="photo"> <img src="<%%= src %%>" /> <div class="caption"> <%%= caption %%> </div></div>');\n""" % TEMPLATE_FUNC)
self.assertEquals(templates, """window.JST = window.JST || {};\n%s\nwindow.JST[\'list\'] = template(\'<div class="photo">\\n <img src="<%%= src %%>" />\\n <div class="caption">\\n <%%= caption %%>\\n </div>\\n</div>\');\n""" % TEMPLATE_FUNC)
templates = self.compressor.compile_templates([
'templates/video/detail.jst',
'templates/photo/detail.jst'
])
self.assertEqual(templates, """window.JST = window.JST || {};\n%s\nwindow.JST['video_detail'] = template('<div class="video"> <video src="<%%= src %%>" /> <div class="caption"> <%%= description %%> </div></div>');\nwindow.JST[\'photo_detail\'] = template(\'<div class="photo"> <img src="<%%= src %%>" /> <div class="caption"> <%%= caption %%> by <%%= author %%> </div></div>\');\n""" % TEMPLATE_FUNC)
self.assertEqual(templates, """window.JST = window.JST || {};\n%s\nwindow.JST[\'video_detail\'] = template(\'<div class="video">\\n <video src="<%%= src %%>" />\\n <div class="caption">\\n <%%= description %%>\\n </div>\\n</div>\');\nwindow.JST[\'photo_detail\'] = template(\'<div class="photo">\\n <img src="<%%= src %%>" />\\n <div class="caption">\\n <%%= caption %%> by <%%= author %%>\\n </div>\\n</div>\');\n""" % TEMPLATE_FUNC)
def test_embeddable(self):
self.assertFalse(self.compressor.embeddable('images/sprite.png', None))
......
import os
import shutil
from django.core.files.base import ContentFile
from django.core.files.storage import FileSystemStorage
from django.test import TestCase
from pipeline import glob
local_path = lambda path: os.path.join(os.path.dirname(__file__), path)
class GlobTest(TestCase):
def normpath(self, *parts):
return os.path.normpath(os.path.join(*parts))
def mktemp(self, *parts):
filename = self.normpath(*parts)
base, file = os.path.split(filename)
base = os.path.join(self.storage.location, base)
if not os.path.exists(base):
os.makedirs(base)
self.storage.save(filename, ContentFile(None))
def assertSequenceEqual(self, l1, l2):
self.assertEqual(set(l1), set(l2))
def setUp(self):
self.storage = FileSystemStorage(local_path('glob_dir'))
self.old_storage = glob.default_storage
glob.default_storage = self.storage
self.mktemp('a', 'D')
self.mktemp('aab', 'F')
self.mktemp('aaa', 'zzzF')
self.mktemp('ZZZ')
self.mktemp('a', 'bcd', 'EF')
self.mktemp('a', 'bcd', 'efg', 'ha')
def glob(self, *parts):
if len(parts) == 1:
pattern = parts[0]
else:
pattern = os.path.join(*parts)
return glob.glob(pattern)
def tearDown(self):
shutil.rmtree(self.storage.location)
glob.default_storage = self.old_storage
def test_glob_literal(self):
self.assertSequenceEqual(self.glob('a'),
[self.normpath('a')])
self.assertSequenceEqual(self.glob('a', 'D'),
[self.normpath('a', 'D')])
self.assertSequenceEqual(self.glob('aab'),
[self.normpath('aab')])
self.assertSequenceEqual(self.glob('zymurgy'), [])
def test_glob_one_directory(self):
self.assertSequenceEqual(self.glob('a*'),
map(self.normpath, ['a', 'aab', 'aaa']))
self.assertSequenceEqual(self.glob('*a'),
map(self.normpath, ['a', 'aaa']))
self.assertSequenceEqual(self.glob('aa?'),
map(self.normpath, ['aaa', 'aab']))
self.assertSequenceEqual(self.glob('aa[ab]'),
map(self.normpath, ['aaa', 'aab']))
self.assertSequenceEqual(self.glob('*q'), [])
def test_glob_nested_directory(self):
if os.path.normcase("abCD") == "abCD":
# case-sensitive filesystem
self.assertSequenceEqual(self.glob('a', 'bcd', 'E*'),
[self.normpath('a', 'bcd', 'EF')])
else:
# case insensitive filesystem
self.assertSequenceEqual(self.glob('a', 'bcd', 'E*'), [
self.normpath('a', 'bcd', 'EF'),
self.normpath('a', 'bcd', 'efg')
])
self.assertSequenceEqual(self.glob('a', 'bcd', '*g'),
[self.normpath('a', 'bcd', 'efg')])
def test_glob_directory_names(self):
self.assertSequenceEqual(self.glob('*', 'D'),
[self.normpath('a', 'D')])
self.assertSequenceEqual(self.glob('*', '*a'), [])
self.assertSequenceEqual(self.glob('a', '*', '*', '*a'),
[self.normpath('a', 'bcd', 'efg', 'ha')])
self.assertSequenceEqual(self.glob('?a?', '*F'),
map(self.normpath, [os.path.join('aaa', 'zzzF'),
os.path.join('aab', 'F')]))
def test_glob_directory_with_trailing_slash(self):
# We are verifying that when there is wildcard pattern which
# ends with os.sep doesn't blow up.
paths = glob.glob('*' + os.sep)
self.assertEqual(len(paths), 4)
self.assertTrue(all([os.sep in path for path in paths]))
......@@ -11,6 +11,7 @@ class StorageTest(TestCase):
'testing': {
'source_filenames': (
'css/first.css',
'css/third.css',
),
'manifest': False,
'output_filename': 'testing.css',
......
# -*- coding: utf-8 -*-
from django.test import TestCase
from pipeline.utils import guess_type
class UtilTest(TestCase):
def test_guess_type(self):
self.assertEqual('text/css', guess_type('stylesheet.css'))
self.assertEqual('text/coffeescript', guess_type('application.coffee'))
self.assertEqual('text/less', guess_type('stylesheet.less'))
......@@ -17,7 +17,7 @@ basepython = python2.5
deps =
Django==1.2.4
mock
https://github.com/jezdez/django-staticfiles/zipball/master
django-staticfiles==1.2.1
unittest2
[testenv:py26-1.2.X]
......@@ -25,7 +25,7 @@ basepython = python2.6
deps =
Django==1.2.4
mock
https://github.com/jezdez/django-staticfiles/zipball/master
django-staticfiles==1.2.1
unittest2
[testenv:py27-1.2.X]
......@@ -33,7 +33,7 @@ basepython = python2.7
deps =
Django==1.2.4
mock
https://github.com/jezdez/django-staticfiles/zipball/master
django-staticfiles==1.2.1
unittest2
[testenv:py25-1.3.X]
......@@ -41,7 +41,7 @@ basepython = python2.5
deps =
Django==1.3.1
mock
https://github.com/jezdez/django-staticfiles/zipball/master
django-staticfiles==1.2.1
unittest2
[testenv:py26-1.3.X]
......@@ -49,7 +49,7 @@ basepython = python2.6
deps =
Django==1.3.1
mock
https://github.com/jezdez/django-staticfiles/zipball/master
django-staticfiles==1.2.1
unittest2
[testenv:py27-1.3.X]
......@@ -57,39 +57,39 @@ basepython = python2.7
deps =
Django==1.3.1
mock
https://github.com/jezdez/django-staticfiles/zipball/master
django-staticfiles==1.2.1
unittest2
[testenv:py25]
basepython = python2.5
deps =
https://github.com/django/django/zipball/master
Django==1.4
mock
django-staticfiles
django-staticfiles==1.2.1
unittest2
[testenv:py26]
basepython = python2.6
deps =
https://github.com/django/django/zipball/master
Django==1.4
mock
https://github.com/jezdez/django-staticfiles/zipball/master
django-staticfiles==1.2.1
unittest2
[testenv:py27]
basepython = python2.7
deps =
https://github.com/django/django/zipball/master
Django==1.4
mock
https://github.com/jezdez/django-staticfiles/zipball/master
django-staticfiles==1.2.1
unittest2
[testenv:pypy]
basepython = pypy
deps =
https://github.com/django/django/zipball/master
Django==1.4
mock
https://github.com/jezdez/django-staticfiles/zipball/master
django-staticfiles==1.2.1
unittest2
[testenv:docs]
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment