Commit ee0facf5 by Steven Cummings

Merge remote-tracking branch 'upstream/master'

parents 23a4ad06 b498f48f
...@@ -16,3 +16,5 @@ tests/assets/js/dummy.js ...@@ -16,3 +16,5 @@ tests/assets/js/dummy.js
.DS_Store .DS_Store
.idea .idea
.venv .venv
.project
.pydevproject
...@@ -3,3 +3,5 @@ python: ...@@ -3,3 +3,5 @@ python:
- 2.7 - 2.7
install: pip install tox --use-mirrors 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 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. ...@@ -7,6 +7,7 @@ or just made Pipeline more awesome.
* Adam Charnock <adam@omniwiki.co.uk> * Adam Charnock <adam@omniwiki.co.uk>
* Alexander Artemenko <svetlyak40wt> * Alexander Artemenko <svetlyak40wt>
* Alexander Pugachev <alexander.pugachev@gmail.com> * Alexander Pugachev <alexander.pugachev@gmail.com>
* Alexis Svinartchouk <zvin@free.fr>
* Andreas Cederström <andreas@klydd.se> * Andreas Cederström <andreas@klydd.se>
* Ara Anjargolian <ara818@gmail.com> * Ara Anjargolian <ara818@gmail.com>
* Balazs Kossovics <balazs.kossovics@e-loue.com> * Balazs Kossovics <balazs.kossovics@e-loue.com>
...@@ -27,5 +28,7 @@ or just made Pipeline more awesome. ...@@ -27,5 +28,7 @@ or just made Pipeline more awesome.
* Sam Thomson <sammthomson@gmail.com> * Sam Thomson <sammthomson@gmail.com>
* Sander Smits <jhmsmits@gmail.com> * Sander Smits <jhmsmits@gmail.com>
* Sander Smits <jsmits@imac.lan> * Sander Smits <jsmits@imac.lan>
* Sirex <sirexas@gmail.com>
* Steven Cummings <estebistec@gmail.com> * Steven Cummings <estebistec@gmail.com>
* Timothée Peignier <timothee.peignier@tryphon.org> * 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 (©) 2008 Andreas Pelme <andreas@pelme.se>
Copyright (©) 2011-2012 Timothée Peignier <timothee.peignier@tryphon.org> Copyright (©) 2011-2012 Timothée Peignier <timothee.peignier@tryphon.org>
...@@ -20,31 +18,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ...@@ -20,31 +18,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
\ No newline at end of file
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
recursive-include pipeline/templates *.html recursive-include pipeline/templates *.html
include docs/* include AUTHORS LICENSE README.rst HISTORY.rst
include AUTHORS
include LICENSE
...@@ -3,13 +3,13 @@ Pipeline ...@@ -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. 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 pip install django-pipeline
For documentation, usage, and examples, see : For documentation, usage, and examples, see :
http://django-pipeline.readthedocs.org/ http://django-pipeline.readthedocs.org
To suggest a feature or report a bug : To suggest a feature or report a bug :
https://github.com/cyberdelia/django-pipeline/issues 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 ...@@ -8,7 +8,7 @@ Compilers
Coffee Script compiler 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 compile your javascripts.
To use it add this to your ``PIPELINE_COMPILERS`` :: To use it add this to your ``PIPELINE_COMPILERS`` ::
...@@ -35,7 +35,7 @@ To use it add this to your ``PIPELINE_COMPILERS`` :: ...@@ -35,7 +35,7 @@ To use it add this to your ``PIPELINE_COMPILERS`` ::
LESS compiler LESS compiler
============= =============
The LESS compiler use `LESS <http://lesscss.org/>`_ The LESS compiler uses `LESS <http://lesscss.org/>`_
to compile your stylesheets. to compile your stylesheets.
To use it add this to your ``PIPELINE_COMPILERS`` :: To use it add this to your ``PIPELINE_COMPILERS`` ::
...@@ -62,7 +62,7 @@ To use it add this to your ``PIPELINE_COMPILERS`` :: ...@@ -62,7 +62,7 @@ To use it add this to your ``PIPELINE_COMPILERS`` ::
SASS compiler 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 compile your stylesheets.
To use it add this to your ``PIPELINE_COMPILERS`` :: To use it add this to your ``PIPELINE_COMPILERS`` ::
...@@ -91,7 +91,7 @@ To use it add this to your ``PIPELINE_COMPILERS`` :: ...@@ -91,7 +91,7 @@ To use it add this to your ``PIPELINE_COMPILERS`` ::
Stylus compiler 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 compile your stylesheets.
To use it add this to your ``PIPELINE_COMPILERS`` :: To use it add this to your ``PIPELINE_COMPILERS`` ::
...@@ -121,10 +121,10 @@ 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 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. 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. and implements ``match_file`` and ``compile_file`` when needed.
Finally, specify it in the tuple of compilers ``PIPELINE_COMPILERS`` in the settings. 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 ...@@ -132,7 +132,7 @@ Finally, specify it in the tuple of compilers ``PIPELINE_COMPILERS`` in the sett
Example Example
------- -------
A custom compiler for a imaginary compiler called jam :: A custom compiler for an imaginary compiler called jam ::
from pipeline.compilers import CompilerBase from pipeline.compilers import CompilerBase
......
...@@ -8,7 +8,7 @@ Compressors ...@@ -8,7 +8,7 @@ Compressors
YUI Compressor compressor 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. for compressing javascript and stylesheets.
To use it for your stylesheets add this to your ``PIPELINE_CSS_COMPRESSOR`` :: 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`` :: ...@@ -50,7 +50,7 @@ To use it for your javascripts add this to your ``PIPELINE_JS_COMPRESSOR`` ::
Closure Compiler 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 compress javascripts.
To use it add this to your ``PIPELINE_JS_COMPRESSOR`` :: To use it add this to your ``PIPELINE_JS_COMPRESSOR`` ::
...@@ -83,7 +83,7 @@ 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 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. compress javascripts.
To use it add this to your ``PIPELINE_JS_COMPRESSOR`` :: To use it add this to your ``PIPELINE_JS_COMPRESSOR`` ::
...@@ -110,7 +110,7 @@ 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 JSMin compressor
================ ================
The jsmin compressor use Douglas Crockford jsmin tool to The jsmin compressor uses Douglas Crockford jsmin tool to
compress javascripts. compress javascripts.
To use it add this to your ``PIPELINE_JS_COMPRESSOR`` :: To use it add this to your ``PIPELINE_JS_COMPRESSOR`` ::
...@@ -125,7 +125,7 @@ Install the jsmin library with your favorite Python package manager :: ...@@ -125,7 +125,7 @@ Install the jsmin library with your favorite Python package manager ::
CSSTidy compressor 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. stylesheets.
To us it for your stylesheets add this to your ``PIPELINE_CSS_COMPRESSOR`` :: 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. :: ...@@ -163,10 +163,10 @@ Install the cssmin library with your favorite Python package manager. E.g. ::
Write your own compressor class 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. 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. and implements ``compress_css`` and/or a ``compress_js`` when needed.
Finally, add it to ``PIPELINE_CSS_COMPRESSOR`` or Finally, add it to ``PIPELINE_CSS_COMPRESSOR`` or
...@@ -175,7 +175,7 @@ Finally, add it to ``PIPELINE_CSS_COMPRESSOR`` or ...@@ -175,7 +175,7 @@ Finally, add it to ``PIPELINE_CSS_COMPRESSOR`` or
Example Example
------- -------
A custom compressor for a imaginary compressor called jam :: A custom compressor for an imaginary compressor called jam ::
from pipeline.compressors import CompressorBase from pipeline.compressors import CompressorBase
......
...@@ -11,7 +11,8 @@ ...@@ -11,7 +11,8 @@
# All configuration values have a default; values that are commented out # All configuration values have a default; values that are commented out
# serve to show the default. # serve to show the default.
import sys, os import sys
import os
# If extensions (or modules to document with autodoc) are in another directory, # 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 # 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' ...@@ -50,7 +51,7 @@ copyright = u'2011-2012, Timothée Peignier'
# The short X.Y version. # The short X.Y version.
version = '1.2' version = '1.2'
# The full version, including alpha/beta/rc tags. # 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 # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.
......
...@@ -51,7 +51,7 @@ Group options ...@@ -51,7 +51,7 @@ Group options
**Required** **Required**
Is a tuple with the source files to be compressed. 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`` ``output_filename``
...@@ -147,7 +147,7 @@ Other settings ...@@ -147,7 +147,7 @@ Other settings
``PIPELINE_TEMPLATE_EXT`` ``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``. To use a different extension, like ``.mustache``, set this settings to ``.mustache``.
Defaults to ``".jst"`` Defaults to ``".jst"``
...@@ -157,7 +157,7 @@ Other settings ...@@ -157,7 +157,7 @@ Other settings
JavaScript function that compiles your JavaScript templates. JavaScript function that compiles your JavaScript templates.
Pipeline doesn't bundle a javascript template library, but the default 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. `underscore <http://documentcloud.github.com/underscore/>`_ template function.
Defaults to ``"_.template"`` Defaults to ``"_.template"``
...@@ -167,7 +167,7 @@ Embedding fonts and images ...@@ -167,7 +167,7 @@ Embedding fonts and images
========================== ==========================
You can embed fonts and images directly in your compiled css, using Data-URI in 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 : :: 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 : ...@@ -192,5 +192,17 @@ Images and fonts are embedded following these rules :
Rewriting CSS urls Rewriting CSS urls
================== ==================
If source CSS contain a relative URL (i.e. relative to current file), If the source CSS contains relative URLs (i.e. relative to current file),
those URL will be converted to full relative path. 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. ...@@ -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 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 Table Of Contents
================= =================
...@@ -22,7 +25,7 @@ Table Of Contents ...@@ -22,7 +25,7 @@ Table Of Contents
storages storages
signals signals
using using
changelog
Indices and tables Indices and tables
================== ==================
......
...@@ -9,12 +9,19 @@ Installation ...@@ -9,12 +9,19 @@ Installation
pip install django-pipeline pip install django-pipeline
2. Add 'compress' to your ``INSTALLED_APPS`` :: 2. Add 'pipeline' to your ``INSTALLED_APPS`` ::
INSTALLED_APPS = ( INSTALLED_APPS = (
'pipeline', '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 .. _GitHub: http://github.com/cyberdelia/django-pipeline
.. _PyPI: http://pypi.python.org/pypi/django-pipeline .. _PyPI: http://pypi.python.org/pypi/django-pipeline
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
Signals Signals
======= =======
A list of all signals send by pipeline. List of all signals sent by pipeline.
css_compressed css_compressed
-------------- --------------
......
...@@ -7,7 +7,7 @@ Storages ...@@ -7,7 +7,7 @@ Storages
Using with a custom storage 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``. to read, save and delete files, by default it use an improved ``StaticFilesStorage``.
You can provide your own via ``PIPELINE_STORAGE`` : :: You can provide your own via ``PIPELINE_STORAGE`` : ::
...@@ -31,3 +31,23 @@ Pipeline is also providing a storage that play nicely with staticfiles app ...@@ -31,3 +31,23 @@ Pipeline is also providing a storage that play nicely with staticfiles app
particularly for development : :: particularly for development : ::
PIPELINE_STORAGE = 'pipeline.storage.PipelineFinderStorage' 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` ...@@ -26,7 +26,7 @@ For example, if you have the following template ``js/templates/photo/detail.jst`
</div> </div>
</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" }); JST.photo_detail({ src:"images/baby-panda.jpg", caption:"A baby panda is born" });
......
...@@ -52,7 +52,7 @@ Middleware ...@@ -52,7 +52,7 @@ Middleware
To enable HTML compression add ``pipeline.middleware.MinifyHTMLMiddleware``, To enable HTML compression add ``pipeline.middleware.MinifyHTMLMiddleware``,
to your ``MIDDLEWARE_CLASSES`` settings. 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 = ( MIDDLEWARE_CLASSES = (
'django.middleware.gzip.GZipMiddleware', 'django.middleware.gzip.GZipMiddleware',
......
...@@ -23,20 +23,22 @@ class Compiler(object): ...@@ -23,20 +23,22 @@ class Compiler(object):
return [to_class(compiler) for compiler in settings.PIPELINE_COMPILERS] return [to_class(compiler) for compiler in settings.PIPELINE_COMPILERS]
compilers = property(compilers) compilers = property(compilers)
def compile(self, paths): def compile(self, paths, force=False):
for index, path in enumerate(paths): for index, path in enumerate(paths):
for compiler in self.compilers: for compiler in self.compilers:
compiler = compiler(self.verbose) compiler = compiler(self.verbose)
if compiler.match_file(path): if compiler.match_file(path):
new_path = self.output_path(path, compiler.output_extension) 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: try:
content = self.read_file(path)
compiled_content = compiler.compile_file(content, finders.find(path)) compiled_content = compiler.compile_file(content, finders.find(path))
self.save_file(new_path, compiled_content) self.save_file(new_path, compiled_content)
except CompilerError: except CompilerError:
if not self.storage.exists(new_path) or not settings.PIPELINE: if not self.storage.exists(new_path) or not settings.PIPELINE:
raise raise
paths[index] = new_path
return paths return paths
def output_path(self, path, extension): def output_path(self, path, extension):
...@@ -49,6 +51,12 @@ class Compiler(object): ...@@ -49,6 +51,12 @@ class Compiler(object):
file.close() file.close()
return content 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): def save_file(self, path, content):
return self.storage.save(path, ContentFile(smart_str(content))) return self.storage.save(path, ContentFile(smart_str(content)))
......
...@@ -8,7 +8,7 @@ class SASSCompiler(SubProcessCompiler): ...@@ -8,7 +8,7 @@ class SASSCompiler(SubProcessCompiler):
output_extension = 'css' output_extension = 'css'
def match_file(self, filename): def match_file(self, filename):
return filename.endswith('.scss') return filename.endswith(('.scss', '.sass'))
def compile_file(self, content, path): def compile_file(self, content, path):
command = "%s --scss %s %s" % ( command = "%s --scss %s %s" % (
......
...@@ -5,6 +5,8 @@ import subprocess ...@@ -5,6 +5,8 @@ import subprocess
from itertools import takewhile from itertools import takewhile
from django.utils.encoding import smart_str
try: try:
from staticfiles import finders from staticfiles import finders
except ImportError: except ImportError:
...@@ -21,7 +23,7 @@ URL_DETECTOR = r'url\([\'"]?([^\s)]+\.[a-z]+[\?\#\d\w]*)[\'"]?\)' ...@@ -21,7 +23,7 @@ URL_DETECTOR = r'url\([\'"]?([^\s)]+\.[a-z]+[\?\#\d\w]*)[\'"]?\)'
URL_REPLACER = r'url\(__EMBED__(.+?)(\?\d+)?\)' URL_REPLACER = r'url\(__EMBED__(.+?)(\?\d+)?\)'
DEFAULT_TEMPLATE_FUNC = "template" 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 = { MIME_TYPES = {
'.png': 'image/png', '.png': 'image/png',
...@@ -58,6 +60,8 @@ class Compressor(object): ...@@ -58,6 +60,8 @@ class Compressor(object):
js = self.concatenate(paths) js = self.concatenate(paths)
if templates: if templates:
js = js + self.compile_templates(templates) js = js + self.compile_templates(templates)
if not settings.PIPELINE_DISABLE_WRAPPER:
js = "(function() { %s }).call(this);" % js js = "(function() { %s }).call(this);" % js
compressor = self.js_compressor compressor = self.js_compressor
...@@ -87,7 +91,7 @@ class Compressor(object): ...@@ -87,7 +91,7 @@ class Compressor(object):
base_path = self.base_path(paths) base_path = self.base_path(paths)
for path in paths: for path in paths:
contents = self.read_file(path) 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) contents = re.sub(r"'", "\\'", contents)
name = self.template_name(path, base_path) name = self.template_name(path, base_path)
compiled += "%s['%s'] = %s('%s');\n" % ( compiled += "%s['%s'] = %s('%s');\n" % (
...@@ -132,7 +136,7 @@ class Compressor(object): ...@@ -132,7 +136,7 @@ class Compressor(object):
output_filename, variant) output_filename, variant)
return "url(%s)" % asset_url return "url(%s)" % asset_url
content = self.read_file(path) content = self.read_file(path)
content = re.sub(URL_DETECTOR, reconstruct, content) content = re.sub(URL_DETECTOR, reconstruct, smart_str(content))
stylesheets.append(content) stylesheets.append(content)
return '\n'.join(stylesheets) return '\n'.join(stylesheets)
...@@ -229,7 +233,7 @@ class SubProcessCompressor(CompressorBase): ...@@ -229,7 +233,7 @@ class SubProcessCompressor(CompressorBase):
def execute_command(self, command, content): def execute_command(self, command, content):
pipe = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, pipe = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE,
stdin=subprocess.PIPE, stderr=subprocess.PIPE) stdin=subprocess.PIPE, stderr=subprocess.PIPE)
pipe.stdin.write(content) pipe.stdin.write(smart_str(content))
pipe.stdin.close() pipe.stdin.close()
compressed_content = pipe.stdout.read() compressed_content = pipe.stdout.read()
......
from __future__ import absolute_import
from pipeline.compressors import CompressorBase from pipeline.compressors import CompressorBase
......
from __future__ import absolute_import
from pipeline.compressors import CompressorBase from pipeline.compressors import CompressorBase
......
...@@ -2,7 +2,7 @@ from django.conf import settings ...@@ -2,7 +2,7 @@ from django.conf import settings
PIPELINE = getattr(settings, 'PIPELINE', not settings.DEBUG) 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_URL = getattr(settings, 'PIPELINE_URL', settings.STATIC_URL)
PIPELINE_STORAGE = getattr(settings, 'PIPELINE_STORAGE', PIPELINE_STORAGE = getattr(settings, 'PIPELINE_STORAGE',
...@@ -23,6 +23,8 @@ PIPELINE_TEMPLATE_NAMESPACE = getattr(settings, 'PIPELINE_TEMPLATE_NAMESPACE', " ...@@ -23,6 +23,8 @@ PIPELINE_TEMPLATE_NAMESPACE = getattr(settings, 'PIPELINE_TEMPLATE_NAMESPACE', "
PIPELINE_TEMPLATE_EXT = getattr(settings, 'PIPELINE_TEMPLATE_EXT', ".jst") PIPELINE_TEMPLATE_EXT = getattr(settings, 'PIPELINE_TEMPLATE_EXT', ".jst")
PIPELINE_TEMPLATE_FUNC = getattr(settings, 'PIPELINE_TEMPLATE_FUNC', "template") 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_BINARY = getattr(settings, 'PIPELINE_CSSTIDY_BINARY', '/usr/local/bin/csstidy')
PIPELINE_CSSTIDY_ARGUMENTS = getattr(settings, 'PIPELINE_CSSTIDY_ARGUMENTS', '--template=highest') PIPELINE_CSSTIDY_ARGUMENTS = getattr(settings, 'PIPELINE_CSSTIDY_ARGUMENTS', '--template=highest')
...@@ -48,5 +50,11 @@ PIPELINE_STYLUS_ARGUMENTS = getattr(settings, 'PIPELINE_STYLUS_ARGUMENTS', '') ...@@ -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_BINARY = getattr(settings, 'PIPELINE_LESS_BINARY', '/usr/local/bin/lessc')
PIPELINE_LESS_ARGUMENTS = getattr(settings, 'PIPELINE_LESS_ARGUMENTS', '') 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: if PIPELINE_COMPILERS is None:
PIPELINE_COMPILERS = [] PIPELINE_COMPILERS = []
...@@ -23,6 +23,11 @@ def iglob(pathname): ...@@ -23,6 +23,11 @@ def iglob(pathname):
""" """
if not has_magic(pathname): if not has_magic(pathname):
try:
if default_storage.exists(pathname):
yield pathname
except NotImplementedError:
# Being optimistic
yield pathname yield pathname
return return
dirname, basename = os.path.split(pathname) dirname, basename = os.path.split(pathname)
...@@ -51,7 +56,9 @@ def glob1(dirname, pattern): ...@@ -51,7 +56,9 @@ def glob1(dirname, pattern):
try: try:
directories, files = default_storage.listdir(dirname) directories, files = default_storage.listdir(dirname)
names = directories + files names = directories + files
except NotImplementedError: except Exception:
# We are not sure that dirname is a real directory
# and storage implementations are really exotic.
return [] return []
if pattern[0] != '.': if pattern[0] != '.':
names = filter(lambda x: x[0] != '.', names) names = filter(lambda x: x[0] != '.', names)
......
...@@ -89,14 +89,14 @@ class Packager(object): ...@@ -89,14 +89,14 @@ class Packager(object):
output_filename=package.output_filename, output_filename=package.output_filename,
variant=package.variant, **kwargs) variant=package.variant, **kwargs)
def compile(self, paths): def compile(self, paths, force=False):
return self.compiler.compile(paths) return self.compiler.compile(paths, force=force)
def pack(self, package, compress, signal, **kwargs): def pack(self, package, compress, signal, **kwargs):
output_filename = package.output_filename output_filename = package.output_filename
if self.verbose: if self.verbose:
print "Saving: %s" % output_filename print "Saving: %s" % output_filename
paths = self.compile(package.paths) paths = self.compile(package.paths, force=True)
content = compress(paths, **kwargs) content = compress(paths, **kwargs)
self.save_file(output_filename, content) self.save_file(output_filename, content)
signal.send(sender=self, package=package, **kwargs) signal.send(sender=self, package=package, **kwargs)
......
...@@ -15,6 +15,8 @@ from pipeline.conf import settings ...@@ -15,6 +15,8 @@ from pipeline.conf import settings
class PipelineMixin(object): class PipelineMixin(object):
packing = True
def post_process(self, paths, dry_run=False, **options): def post_process(self, paths, dry_run=False, **options):
if dry_run: if dry_run:
return [] return []
...@@ -23,11 +25,15 @@ class PipelineMixin(object): ...@@ -23,11 +25,15 @@ class PipelineMixin(object):
packager = Packager(storage=self) packager = Packager(storage=self)
for package_name in packager.packages['css']: for package_name in packager.packages['css']:
package = packager.package_for('css', package_name) 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) paths[output_file] = (self, output_file)
for package_name in packager.packages['js']: for package_name in packager.packages['js']:
package = packager.package_for('js', package_name) 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) paths[output_file] = (self, output_file)
super_class = super(PipelineMixin, self) super_class = super(PipelineMixin, self)
...@@ -45,14 +51,26 @@ class PipelineMixin(object): ...@@ -45,14 +51,26 @@ class PipelineMixin(object):
return name return name
class NonPackagingMixin(object):
packing = False
class PipelineStorage(PipelineMixin, StaticFilesStorage): class PipelineStorage(PipelineMixin, StaticFilesStorage):
pass pass
class NonPackagingPipelineStorage(NonPackagingMixin, PipelineStorage):
pass
class PipelineCachedStorage(PipelineMixin, CachedFilesMixin, StaticFilesStorage): class PipelineCachedStorage(PipelineMixin, CachedFilesMixin, StaticFilesStorage):
pass pass
class NonPackagingPipelineCachedStorage(NonPackagingMixin, PipelineCachedStorage):
pass
class BaseFinderStorage(PipelineStorage): class BaseFinderStorage(PipelineStorage):
finders = None finders = None
...@@ -72,7 +90,7 @@ class BaseFinderStorage(PipelineStorage): ...@@ -72,7 +90,7 @@ class BaseFinderStorage(PipelineStorage):
def exists(self, name): def exists(self, name):
exists = self.finders.find(name) != None exists = self.finders.find(name) != None
if not exists: if not exists:
exists = super(BaseFinderStorage, self).exists(name) return super(BaseFinderStorage, self).exists(name)
return exists return exists
def listdir(self, path): def listdir(self, path):
...@@ -83,10 +101,24 @@ class BaseFinderStorage(PipelineStorage): ...@@ -83,10 +101,24 @@ class BaseFinderStorage(PipelineStorage):
except OSError: except OSError:
pass pass
def _save(self, name, content): def find_storage(self, name):
for finder in finders.get_finders(): for finder in finders.get_finders():
for path, storage in finder.list([]): for path, storage in finder.list([]):
if os.path.dirname(name) in path: 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) return storage._save(name, content)
......
<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 %} /> <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 \ No newline at end of file
<script {% if async %}async{% endif %} {% if defer %}defer{% endif %} type="text/javascript" src="{{ url }}" charset="utf-8"></script> <script {% if async %}async{% endif %} {% if defer %}defer{% endif %} type="{{ type }}" src="{{ url }}" charset="utf-8"></script>
\ No newline at end of file \ No newline at end of file
...@@ -8,6 +8,7 @@ from django.template.loader import render_to_string ...@@ -8,6 +8,7 @@ from django.template.loader import render_to_string
from pipeline.conf import settings from pipeline.conf import settings
from pipeline.packager import Packager, PackageNotFound from pipeline.packager import Packager, PackageNotFound
from pipeline.utils import guess_type
register = template.Library() register = template.Library()
...@@ -38,6 +39,7 @@ class CompressedCSSNode(template.Node): ...@@ -38,6 +39,7 @@ class CompressedCSSNode(template.Node):
template_name = package.template_name or "pipeline/css.html" template_name = package.template_name or "pipeline/css.html"
context = package.extra_context context = package.extra_context
context.update({ context.update({
'type': guess_type(path, 'text/css'),
'url': staticfiles_storage.url(path) 'url': staticfiles_storage.url(path)
}) })
return render_to_string(template_name, context) return render_to_string(template_name, context)
...@@ -74,6 +76,7 @@ class CompressedJSNode(template.Node): ...@@ -74,6 +76,7 @@ class CompressedJSNode(template.Node):
template_name = package.template_name or "pipeline/js.html" template_name = package.template_name or "pipeline/js.html"
context = package.extra_context context = package.extra_context
context.update({ context.update({
'type': guess_type(path, 'text/javascript'),
'url': staticfiles_storage.url(path) 'url': staticfiles_storage.url(path)
}) })
return render_to_string(template_name, context) return render_to_string(template_name, context)
......
import mimetypes
import os import os
import sys import sys
import urllib import urllib
...@@ -5,6 +6,8 @@ import urllib ...@@ -5,6 +6,8 @@ import urllib
from django.utils import importlib from django.utils import importlib
from django.utils.encoding import smart_str from django.utils.encoding import smart_str
from pipeline.conf import settings
def to_class(class_str): def to_class(class_str):
if not class_str: if not class_str:
...@@ -22,6 +25,15 @@ def filepath_to_uri(path): ...@@ -22,6 +25,15 @@ def filepath_to_uri(path):
return urllib.quote(smart_str(path).replace("\\", "/"), safe="/~!*()'#?") 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): def _relpath_nt(path, start=os.path.curdir):
"""Return a relative version of a path""" """Return a relative version of a path"""
if not path: if not path:
......
...@@ -4,14 +4,14 @@ from setuptools import setup, find_packages ...@@ -4,14 +4,14 @@ from setuptools import setup, find_packages
setup( setup(
name='django-pipeline', name='django-pipeline',
version='1.2c1', version='1.2.9',
description='Pipeline is an asset packaging library for Django.', description='Pipeline is an asset packaging library for Django.',
long_description="""Pipeline is an asset packaging library for Django, providing long_description=open('README.rst').read() + '\n\n' +
both CSS and JavaScript concatenation and compression, built-in JavaScript open('HISTORY.rst').read(),
template support, and optional data-URI image and font embedding.""",
author='Timothée Peignier', author='Timothée Peignier',
author_email='timothee.peignier@tryphon.org', author_email='timothee.peignier@tryphon.org',
url='https://github.com/cyberdelia/django-pipeline', url='https://github.com/cyberdelia/django-pipeline',
license=open('LICENSE').read(),
packages=find_packages(), packages=find_packages(),
zip_safe=False, zip_safe=False,
include_package_data=True, include_package_data=True,
......
.third {
display: none;
}
...@@ -29,6 +29,7 @@ STATIC_ROOT = local_path('static/') ...@@ -29,6 +29,7 @@ STATIC_ROOT = local_path('static/')
STATIC_URL = '/static/' STATIC_URL = '/static/'
STATICFILES_DIRS = ( STATICFILES_DIRS = (
local_path('assets/'), local_path('assets/'),
local_path('assets2/'),
) )
STATICFILES_FINDERS = ( STATICFILES_FINDERS = (
'staticfiles.finders.FileSystemFinder', 'staticfiles.finders.FileSystemFinder',
......
# -*- coding: utf-8 flake8: noqa -*- # -*- coding: utf-8 flake8: noqa -*-
from packager import *
from compressor import *
from compiler import * from compiler import *
from compressor import *
from glob import *
from packager import *
from storage import * from storage import *
from utils import *
\ No newline at end of file
...@@ -71,12 +71,12 @@ class CompressorTest(TestCase): ...@@ -71,12 +71,12 @@ class CompressorTest(TestCase):
def test_compile_templates(self): def test_compile_templates(self):
templates = self.compressor.compile_templates(['templates/photo/list.jst']) 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 = self.compressor.compile_templates([
'templates/video/detail.jst', 'templates/video/detail.jst',
'templates/photo/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): def test_embeddable(self):
self.assertFalse(self.compressor.embeddable('images/sprite.png', None)) 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): ...@@ -11,6 +11,7 @@ class StorageTest(TestCase):
'testing': { 'testing': {
'source_filenames': ( 'source_filenames': (
'css/first.css', 'css/first.css',
'css/third.css',
), ),
'manifest': False, 'manifest': False,
'output_filename': 'testing.css', '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 ...@@ -17,7 +17,7 @@ basepython = python2.5
deps = deps =
Django==1.2.4 Django==1.2.4
mock mock
https://github.com/jezdez/django-staticfiles/zipball/master django-staticfiles==1.2.1
unittest2 unittest2
[testenv:py26-1.2.X] [testenv:py26-1.2.X]
...@@ -25,7 +25,7 @@ basepython = python2.6 ...@@ -25,7 +25,7 @@ basepython = python2.6
deps = deps =
Django==1.2.4 Django==1.2.4
mock mock
https://github.com/jezdez/django-staticfiles/zipball/master django-staticfiles==1.2.1
unittest2 unittest2
[testenv:py27-1.2.X] [testenv:py27-1.2.X]
...@@ -33,7 +33,7 @@ basepython = python2.7 ...@@ -33,7 +33,7 @@ basepython = python2.7
deps = deps =
Django==1.2.4 Django==1.2.4
mock mock
https://github.com/jezdez/django-staticfiles/zipball/master django-staticfiles==1.2.1
unittest2 unittest2
[testenv:py25-1.3.X] [testenv:py25-1.3.X]
...@@ -41,7 +41,7 @@ basepython = python2.5 ...@@ -41,7 +41,7 @@ basepython = python2.5
deps = deps =
Django==1.3.1 Django==1.3.1
mock mock
https://github.com/jezdez/django-staticfiles/zipball/master django-staticfiles==1.2.1
unittest2 unittest2
[testenv:py26-1.3.X] [testenv:py26-1.3.X]
...@@ -49,7 +49,7 @@ basepython = python2.6 ...@@ -49,7 +49,7 @@ basepython = python2.6
deps = deps =
Django==1.3.1 Django==1.3.1
mock mock
https://github.com/jezdez/django-staticfiles/zipball/master django-staticfiles==1.2.1
unittest2 unittest2
[testenv:py27-1.3.X] [testenv:py27-1.3.X]
...@@ -57,39 +57,39 @@ basepython = python2.7 ...@@ -57,39 +57,39 @@ basepython = python2.7
deps = deps =
Django==1.3.1 Django==1.3.1
mock mock
https://github.com/jezdez/django-staticfiles/zipball/master django-staticfiles==1.2.1
unittest2 unittest2
[testenv:py25] [testenv:py25]
basepython = python2.5 basepython = python2.5
deps = deps =
https://github.com/django/django/zipball/master Django==1.4
mock mock
django-staticfiles django-staticfiles==1.2.1
unittest2 unittest2
[testenv:py26] [testenv:py26]
basepython = python2.6 basepython = python2.6
deps = deps =
https://github.com/django/django/zipball/master Django==1.4
mock mock
https://github.com/jezdez/django-staticfiles/zipball/master django-staticfiles==1.2.1
unittest2 unittest2
[testenv:py27] [testenv:py27]
basepython = python2.7 basepython = python2.7
deps = deps =
https://github.com/django/django/zipball/master Django==1.4
mock mock
https://github.com/jezdez/django-staticfiles/zipball/master django-staticfiles==1.2.1
unittest2 unittest2
[testenv:pypy] [testenv:pypy]
basepython = pypy basepython = pypy
deps = deps =
https://github.com/django/django/zipball/master Django==1.4
mock mock
https://github.com/jezdez/django-staticfiles/zipball/master django-staticfiles==1.2.1
unittest2 unittest2
[testenv:docs] [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