Commit 935094ee by Steven Cummings

Merge remote-tracking branch 'upstream/master'

parents ee0facf5 e1229053
......@@ -12,23 +12,32 @@ or just made Pipeline more awesome.
* Ara Anjargolian <ara818@gmail.com>
* Balazs Kossovics <balazs.kossovics@e-loue.com>
* Ben Vinegar <ben@benv.ca>
* Brant Young <brant.young@gmail.com>
* Bryan Chow <bryan@fullfactor.com>
* Casey Greene <csgreene@princeton.edu>
* Chris Reeves <hello@chris.reeves.io>
* Christian Hammond <chipx86@chipx86.com>
* David Charbonnier <d.charbonnier@oxys.net>
* David Cramer <dcramer@gmail.com>
* Denis V Seleznyov <code@xy2.ru>
* Florent Messa <florent.messa@gmail.com>
* Idan Zalzberg <idanzalz@gmail.com>
* Kyle MacFarlane <kyle@deletethetrees.com>
* Luke Yu-Po Chen <nuemail@gmail.com>
* Matt Dennewitz <mattdennewitz@gmail.com>
* Max Klymyshyn <klymyshyn@gmail.com>
* Melvin Laplanche <melvin.laplanche+dev@gmail.com>
* Michael Weibel <michael.weibel@gmail.com>
* Patrick Altman <paltman@gmail.com>
* Peter Baumgartner <pete@lincolnloop.com>
* Pierre Drescher <pierre.drescher@gmail.com>
* Remco Wendt <remco@maykinmedia.nl>
* Sam Thomson <sammthomson@gmail.com>
* Sander Smits <jhmsmits@gmail.com>
* Sander Smits <jsmits@imac.lan>
* Sirex <sirexas@gmail.com>
* Steven Cummings <estebistec@gmail.com>
* Teo Klestrup Röijezon <teo@nullable.se>
* Timothée Peignier <timothee.peignier@tryphon.org>
* Trey Smith <trey.smith@nasa.gov>
* Victor Shnayder <victor@mitx.mit.edu>
* Zenobius Jiricek <zenobius.jiricek@gmail.com>
Contribute
==========
#. Check for open issues or open a fresh issue to start a discussion around a
feature idea or a bug. There is a **contribute!** tag for issues that should be
ideal for people who are not very familiar with the codebase yet.
#. Fork the repository on Github to start making your changes on a topic branch.
#. Write a test which shows that the bug was fixed or that the feature works as expected.
#. Send a pull request and bug the maintainer until it gets merged and published.
Make sure to add yourself to *AUTHORS*.
Otherwise, if you simply wants to suggest a feature or report a bug, create an issue :
https://github.com/cyberdelia/django-pipeline/issues
......@@ -3,6 +3,76 @@
History
=======
1.1.20
------
* Ensure yui-compressor can still use YUICompressor.
1.2.19
------
* **BACKWARD INCOMPATIBLE** : Replace python cssmin compressor to run the command (works for python or node implementation)
1.2.18
------
* **BACKWARD INCOMPATIBLE** : Replace yui-compressor by yuglify, check your configuration.
* Use finders in manifest. Thanks to Sjoerd Arendsen.
1.2.17
------
* Fully tested windows compatibility. Thanks to Idan Zalzberg.
1.2.16
------
* Fix manifesto module. Thanks to Zenobius Jiricek.
* Ensure coffee-script compiler don't try to overwrite file. Thanks to Teo Klestrup Röijezon.
1.2.15
------
* Ensure asset url are build with ``posixpath``.
* Deal with storage prefix properly.
1.2.14
------
* Jinja2 support, thanks to Christopher Reeves.
* Add read/save_file method to CompilerBase.
1.2.13
------
* Fix unicode bug in compressor. Thanks to Victor Shnayder.
* Fix outdated detection bug. Thanks to Victor Shnayder and Erwan Ameil.
* Add slimit compressor. Thanks to Brant Young.
1.2.12
------
* Fix IO error when creating new compiled file. Thanks to Melvin Laplanche.
1.2.11
------
* Add a small contribution guide
* Add mimetype settings for sass and scss
* Change compiler interface to let compiler determine if file is outdated
1.2.10
------
* Use ``/usr/bin/env`` by default to find compiler executable. Thanks to Michael Weibel.
* Allow to change embed settings : max size and directory. Thanks to Pierre Drescher.
* Some documentation improvements. Thanks to Florent Messa.
1.2.9
-----
* Don't compile non-outdated files.
* Add non-packing storage.
1.2.8
-----
......
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 : ::
Installation
------------
To install it, simply: ::
pip install django-pipeline
Documentation
-------------
For documentation, usage, and examples, see :
http://django-pipeline.readthedocs.org
To suggest a feature or report a bug :
https://github.com/cyberdelia/django-pipeline/issues
......@@ -23,7 +23,7 @@ To use it add this to your ``PIPELINE_COMPILERS`` ::
Command line to execute for coffee program.
You will most likely change this to the location of coffee on your system.
Defaults to ``'/usr/local/bin/coffee'``.
Defaults to ``'/usr/bin/env coffee'``.
``PIPELINE_COFFEE_SCRIPT_ARGUMENTS``
------------------------------------
......@@ -50,7 +50,7 @@ To use it add this to your ``PIPELINE_COMPILERS`` ::
Command line to execute for lessc program.
You will most likely change this to the location of lessc on your system.
Defaults to ``'/usr/local/bin/lessc'``.
Defaults to ``'/usr/bin/env lessc'``.
``PIPELINE_LESS_ARGUMENTS``
---------------------------
......@@ -78,7 +78,7 @@ To use it add this to your ``PIPELINE_COMPILERS`` ::
Command line to execute for sass program.
You will most likely change this to the location of sass on your system.
Defaults to ``'/usr/local/bin/sass'``.
Defaults to ``'/usr/bin/env sass'``.
``PIPELINE_SASS_ARGUMENTS``
---------------------------
......@@ -107,7 +107,7 @@ To use it add this to your ``PIPELINE_COMPILERS`` ::
Command line to execute for stylus program.
You will most likely change this to the location of stylus on your system.
Defaults to ``'/usr/local/bin/stylus'``.
Defaults to ``'/usr/bin/env stylus'``.
``PIPELINE_STYLUS_ARGUMENTS``
-----------------------------
......@@ -142,6 +142,8 @@ A custom compiler for an imaginary compiler called jam ::
def match_file(self, filename):
return filename.endswith('.jam')
def compile_file(self, content, path):
return jam.compile(content)
def compile_file(self, infile, outfile, outdated=False, force=False):
if not outdated and not force:
return # No need to recompiled file
return jam.compile(infile, outfile)
......@@ -5,10 +5,10 @@ Compressors
===========
YUI Compressor compressor
YUI compressor
=========================
The YUI compressor uses `yui-compressor <http://developer.yahoo.com/yui/compressor/>`_
The YUI compressor uses `yuglify <http://github.com/yui/yuglify>`_
for compressing javascript and stylesheets.
To use it for your stylesheets add this to your ``PIPELINE_CSS_COMPRESSOR`` ::
......@@ -26,11 +26,7 @@ To use it for your javascripts add this to your ``PIPELINE_JS_COMPRESSOR`` ::
Command line to execute for the YUI program.
You will most likely change this to the location of yui-compressor on your system.
Defaults to ``'/usr/local/bin/yuicompressor'``.
.. warning::
Don't point to ``yuicompressor.jar`` directly, we expect to find a executable script.
Defaults to ``'/usr/bin/env yuglify'``.
``PIPELINE_YUI_CSS_ARGUMENTS``
------------------------------
......@@ -66,7 +62,7 @@ To use it add this to your ``PIPELINE_JS_COMPRESSOR`` ::
Command line to execute for the Closure Compiler program.
You will most likely change this to the location of closure on your system.
Default to ``'/usr/local/bin/closure'``
Default to ``'/usr/bin/env closure'``
.. warning::
Don't point to ``compiler.jar`` directly, we expect to find a executable script.
......@@ -97,7 +93,7 @@ To use it add this to your ``PIPELINE_JS_COMPRESSOR`` ::
Command line to execute for the Closure Compiler program.
You will most likely change this to the location of closure on your system.
Defaults to ``'/usr/local/bin/uglifyjs'``.
Defaults to ``'/usr/bin/env uglifyjs'``.
``PIPELINE_UGLIFYJS_ARGUMENTS``
-------------------------------
......@@ -122,6 +118,21 @@ Install the jsmin library with your favorite Python package manager ::
pip install jsmin
SlimIt compressor
=================
The slimit compressor uses `SlimIt <http://slimit.org/>`_ to
compress javascripts.
To use it add this to your ``PIPELINE_JS_COMPRESSOR`` ::
PIPELINE_JS_COMPRESSOR = 'pipeline.compressors.slimit.SlimItCompressor'
Install the slimit library with your favorite Python package manager ::
pip install slimit
CSSTidy compressor
==================
......@@ -138,7 +149,7 @@ To us it for your stylesheets add this to your ``PIPELINE_CSS_COMPRESSOR`` ::
Command line to execute for csstidy program.
You will most likely change this to the location of csstidy on your system.
Defaults to ``'/usr/local/bin/csstidy'``
Defaults to ``'/usr/bin/env csstidy'``
``PIPELINE_CSSTIDY_ARGUMENTS``
------------------------------
......@@ -147,18 +158,29 @@ To us it for your stylesheets add this to your ``PIPELINE_CSS_COMPRESSOR`` ::
Default to ``'--template=highest'``
cssmin compressor
CSSMin compressor
=================
The cssmin compressor uses the `cssmin <http://pypi.python.org/pypi/cssmin/>`_
Python library to compress stylesheets. To use it, specify this
``PIPELINE_CSS_COMPRESSOR`` ::
The cssmin compressor uses the `cssmin <https://github.com/jbleuzen/node-cssmin>`_
command to compress stylesheets. To use it, add this to your ``PIPELINE_CSS_COMPRESSOR`` ::
PIPELINE_CSS_COMPRESSOR = 'pipeline.compressors.cssmin.CSSMinCompressor'
``PIPELINE_CSSMIN_BINARY``
---------------------------
Command line to execute for cssmin program.
You will most likely change this to the location of cssmin on your system.
Defaults to ``'/usr/bin/env cssmin'``
PIPELINE_CSS_COMPRESSOR = 'pipeline.compressors.cssmin.CssminCompressor'
``PIPELINE_CSSMIN_ARGUMENTS``
------------------------------
Install the cssmin library with your favorite Python package manager. E.g. ::
Additional arguments to use when cssmin is called.
Default to ``''``
pip install cssmin
Write your own compressor class
===============================
......
......@@ -51,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.2.9'
release = '1.2.21'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
......
......@@ -188,6 +188,26 @@ Images and fonts are embedded following these rules :
at all in Internet Explorer 8.
- If asset path contains a directory named "**embed**".
Overriding embedding settings
-----------------------------
You can override these rules using the following settings:
``PIPELINE_EMBED_MAX_IMAGE_SIZE``
.................................
Setting that controls the maximum image size (in bytes) to embed in CSS using Data-URIs.
Internet Explorer 8 has issues with assets under 32 kilobytes.
Defaults to ``32700``
``PIPELINE_EMBED_PATH``
.......................
Setting the directory that an asset needs to be in so that it is embedded
Defaults to ``r'[/]?embed/'``
Rewriting CSS urls
==================
......
......@@ -27,6 +27,15 @@ And if you want versioning use ::
STATICFILES_STORAGE = 'pipeline.storage.PipelineCachedStorage'
There is also non-packing storage available, that allows you to run ``collectstatic`` command
without packaging your assets. Useful for production when you don't want to run compressor or compilers ::
STATICFILES_STORAGE = 'pipeline.storage.NonPackagingPipelineStorage'
Also available if you want versioning ::
STATICFILES_STORAGE = 'pipeline.storage.NonPackagingPipelineCachedStorage'
Pipeline is also providing a storage that play nicely with staticfiles app
particularly for development : ::
......
......@@ -13,7 +13,7 @@ To use your javascript templates, just add them to your ``PIPELINE_JS`` group ::
'js/application.js',
'js/templates/**/*.jst',
),
'output_filename': 'js/application.r?.js'
'output_filename': 'js/application.js'
}
}
......
......@@ -33,7 +33,6 @@ with the name “scripts”, you would use the following code to output them all
{% compressed_css 'colors' %}
{% compressed_js 'stats' %}
Collect static
==============
......@@ -66,3 +65,42 @@ Pipeline provide a way to add your javascripts and stylesheets files to a
cache-manifest via `Manifesto <http://manifesto.readthedocs.org/>`_.
To do so, you just need to add manifesto app to your ``INSTALLED_APPS``.
Jinja2
======
Pipeline also includes Jinja2 support and is used almost identically to the Django
Template tags implementation.
.. note::
You have to expose the Jinja2 functions provided by pipeline to the Jinja2
environment yourself, Pipeline will not do this for you. There are several implementations
of Jinja2 for Django, like ``django-ninja`` or ``coffin``.
See the vendor documentation for examples on how to expose functions to the Jinja2 environment
and pick a solution that best suites your use case.
For more information on Jinja2 see the documentation at http://jinja.pocoo.org/docs/.
Functions
---------
The functions to expose to the Jinja2 environment are: ::
pipeline.jinja2.ext.compressed_css
pipeline.jinja2.ext.compressed_js
Example
-------
To use in the templates: ::
{{ compressed_css('group_name') }}
{{ compressed_js('group_name') }}
Templates
---------
Unlike the Django template tag implementation the Jinja2 implementation uses different templates, so if you
wish to override them please override ``pipeline/css.jinja`` and ``pipeline/js.jinja``.
......@@ -6,30 +6,33 @@ Sites using Pipeline
The following sites are a partial list of people using Pipeline.
Are you using pipeline and not being in this list ? Drop us a line.
Are you using pipeline and not being in this list? Drop us a line.
20 Minutes
----------
For their internal tools : http://www.20minutes.fr
Pitchfork
---------
For their main website : http://pitchfork.com
For their internal tools: http://www.20minutes.fr
The Molly Project
-----------------
Molly is a framework for the rapid development of information and service
portals targeted at mobile internet devices : http://mollyproject.org
portals targeted at mobile internet devices: http://mollyproject.org
It powers the University of Oxford's mobile portal : http://m.ox.ac.uk/
It powers the University of Oxford's mobile portal: http://m.ox.ac.uk/
Croisé dans le Métro
--------------------
For their main and mobile website :
For their main and mobile website:
* http://www.croisedanslemetro.com
* http://m.croisedanslemetro.com
Ulule
-----
For their main and forum website:
* http://www.ulule.com
* http://vox.ulule.com
......@@ -24,20 +24,23 @@ class Compiler(object):
compilers = property(compilers)
def compile(self, paths, force=False):
for index, path in enumerate(paths):
for index, input_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)
paths[index] = new_path
if not force and not self.is_outdated(path, new_path):
continue
compiler = compiler(verbose=self.verbose, storage=self.storage)
if compiler.match_file(input_path):
output_path = self.output_path(input_path, compiler.output_extension)
paths[index] = output_path
try:
content = self.read_file(path)
compiled_content = compiler.compile_file(content, finders.find(path))
self.save_file(new_path, compiled_content)
infile = finders.find(input_path)
outfile = finders.find(output_path)
if outfile is None:
outfile = self.output_path(infile, compiler.output_extension)
outdated = True
else:
outdated = self.is_outdated(input_path, output_path)
compiler.compile_file(infile, outfile, outdated=outdated, force=force)
except CompilerError:
if not self.storage.exists(new_path) or not settings.PIPELINE:
if not self.storage.exists(output_path) or not settings.PIPELINE:
raise
return paths
......@@ -45,32 +48,33 @@ class Compiler(object):
path = os.path.splitext(path)
return '.'.join((path[0], extension))
def read_file(self, path):
file = self.storage.open(path, 'rb')
content = file.read()
file.close()
return content
def is_outdated(self, path, new_path):
def is_outdated(self, infile, outfile):
try:
return self.storage.modified_time(path) > self.storage.modified_time(new_path)
return self.storage.modified_time(infile) > self.storage.modified_time(outfile)
except (OSError, NotImplementedError):
return True
def save_file(self, path, content):
return self.storage.save(path, ContentFile(smart_str(content)))
class CompilerBase(object):
def __init__(self, verbose):
def __init__(self, verbose, storage):
self.verbose = verbose
self.storage = storage
def match_file(self, filename):
raise NotImplementedError
def compile_file(self, content, path):
def compile_file(self, infile, outfile, outdated=False, force=False):
raise NotImplementedError
def save_file(self, path, content):
return self.storage.save(path, ContentFile(smart_str(content)))
def read_file(self, path):
file = self.storage.open(path, 'rb')
content = file.read()
file.close()
return content
class CompilerError(Exception):
pass
......
......@@ -8,9 +8,13 @@ class CoffeeScriptCompiler(SubProcessCompiler):
def match_file(self, path):
return path.endswith('.coffee')
def compile_file(self, content, path):
command = "%s -sc %s" % (
def compile_file(self, infile, outfile, outdated=False, force=False):
if not outdated and not force:
return # File doesn't need to be recompiled
command = "%s -cp %s %s > %s" % (
settings.PIPELINE_COFFEE_SCRIPT_BINARY,
settings.PIPELINE_COFFEE_SCRIPT_ARGUMENTS
settings.PIPELINE_COFFEE_SCRIPT_ARGUMENTS,
infile,
outfile
)
return self.execute_command(command, content)
return self.execute_command(command)
import os.path
from os.path import dirname
from pipeline.conf import settings
from pipeline.compilers import SubProcessCompiler
......@@ -10,12 +10,11 @@ class LessCompiler(SubProcessCompiler):
def match_file(self, filename):
return filename.endswith('.less')
def compile_file(self, content, path):
command = '%s %s %s' % (
def compile_file(self, infile, outfile, outdated=False, force=False):
command = "%s %s %s %s" % (
settings.PIPELINE_LESS_BINARY,
settings.PIPELINE_LESS_ARGUMENTS,
path
infile,
outfile
)
cwd = os.path.dirname(path)
content = self.execute_command(command, cwd=cwd)
return content
return self.execute_command(command, cwd=dirname(infile))
import os.path
from os.path import dirname
from pipeline.conf import settings
from pipeline.compilers import SubProcessCompiler
......@@ -10,11 +10,11 @@ class SASSCompiler(SubProcessCompiler):
def match_file(self, filename):
return filename.endswith(('.scss', '.sass'))
def compile_file(self, content, path):
command = "%s --scss %s %s" % (
def compile_file(self, infile, outfile, outdated=False, force=False):
command = "%s %s --update %s:%s" % (
settings.PIPELINE_SASS_BINARY,
settings.PIPELINE_SASS_ARGUMENTS,
path
infile,
outfile
)
cwd = os.path.dirname(path)
return self.execute_command(command, cwd=cwd)
return self.execute_command(command, cwd=dirname(infile))
import os.path
from os.path import dirname
from pipeline.conf import settings
from pipeline.compilers import SubProcessCompiler
......@@ -10,10 +10,11 @@ class StylusCompiler(SubProcessCompiler):
def match_file(self, filename):
return filename.endswith('.styl')
def compile_file(self, content, path):
command = "%s %s" % (
def compile_file(self, infile, outfile, outdated=False, force=False):
command = "%s %s < %s > %s" % (
settings.PIPELINE_STYLUS_BINARY,
settings.PIPELINE_STYLUS_ARGUMENTS,
infile,
outfile
)
cwd = os.path.dirname(path)
return self.execute_command(command, content, cwd=cwd)
return self.execute_command(command, cwd=dirname(infile))
import base64
import os
import posixpath
import re
import subprocess
from itertools import takewhile
from django.utils.encoding import smart_str
from django.utils.encoding import smart_str, force_unicode
try:
from staticfiles import finders
......@@ -13,12 +14,9 @@ except ImportError:
from django.contrib.staticfiles import finders # noqa
from pipeline.conf import settings
from pipeline.utils import to_class, relpath
from pipeline.storage import default_storage
from pipeline.utils import to_class, relpath
MAX_IMAGE_SIZE = 32700
EMBEDDABLE = r'[/]?embed/'
URL_DETECTOR = r'url\([\'"]?([^\s)]+\.[a-z]+[\?\#\d\w]*)[\'"]?\)'
URL_REPLACER = r'url\(__EMBED__(.+?)(\?\d+)?\)'
......@@ -136,7 +134,8 @@ class Compressor(object):
output_filename, variant)
return "url(%s)" % asset_url
content = self.read_file(path)
content = re.sub(URL_DETECTOR, reconstruct, smart_str(content))
# content needs to be unicode to avoid explosions with non-ascii chars
content = re.sub(URL_DETECTOR, reconstruct, force_unicode(content))
stylesheets.append(content)
return '\n'.join(stylesheets)
......@@ -146,10 +145,10 @@ class Compressor(object):
def construct_asset_path(self, asset_path, css_path, output_filename, variant=None):
"""Return a rewritten asset URL for a stylesheet"""
public_path = self.absolute_path(asset_path, os.path.dirname(css_path))
public_path = self.absolute_path(asset_path, os.path.dirname(css_path).replace('\\', '/'))
if self.embeddable(public_path, variant):
return "__EMBED__%s" % public_path
if not os.path.isabs(asset_path):
if not posixpath.isabs(asset_path):
asset_path = self.relative_path(public_path, output_filename)
return asset_path
......@@ -159,11 +158,11 @@ class Compressor(object):
font = ext in FONT_EXTS
if not variant:
return False
if not (re.search(EMBEDDABLE, path) and self.storage.exists(path)):
if not (re.search(settings.PIPELINE_EMBED_PATH, path.replace('\\', '/')) and self.storage.exists(path)):
return False
if not ext in EMBED_EXTS:
return False
if not (font or len(self.encoded_content(path)) < MAX_IMAGE_SIZE):
if not (font or len(self.encoded_content(path)) < settings.PIPELINE_EMBED_MAX_IMAGE_SIZE):
return False
return True
......@@ -193,16 +192,16 @@ class Compressor(object):
Return the absolute public path for an asset,
given the path of the stylesheet that contains it.
"""
if os.path.isabs(path):
path = os.path.join(default_storage.location, path)
if posixpath.isabs(path):
path = posixpath.join(default_storage.location, path)
else:
path = os.path.join(start, path)
return os.path.normpath(path)
path = posixpath.join(start, path)
return posixpath.normpath(path)
def relative_path(self, absolute_path, output_filename):
"""Rewrite paths relative to the output stylesheet path"""
absolute_path = os.path.join(settings.PIPELINE_ROOT, absolute_path)
output_path = os.path.join(settings.PIPELINE_ROOT, os.path.dirname(output_filename))
absolute_path = posixpath.join(settings.PIPELINE_ROOT, absolute_path)
output_path = posixpath.join(settings.PIPELINE_ROOT, posixpath.dirname(output_filename))
return relpath(absolute_path, output_path)
def read_file(self, path):
......
from __future__ import absolute_import
from pipeline.conf import settings
from pipeline.compressors import SubProcessCompressor
from pipeline.compressors import CompressorBase
class CssminCompressor(CompressorBase):
"""
CSS compressor based on the Python library cssmin
(http://pypi.python.org/pypi/cssmin/).
"""
class CSSMinCompressor(SubProcessCompressor):
def compress_css(self, css):
from cssmin import cssmin
return cssmin(css)
command = "%s %s" % (settings.PIPELINE_CSSMIN_BINARY, settings.PIPELINE_CSSMIN_ARGUMENTS)
return self.execute_command(command, css)
from __future__ import absolute_import
from pipeline.compressors import CompressorBase
class SlimItCompressor(CompressorBase):
"""
JS compressor based on the Python library slimit
(http://pypi.python.org/pypi/slimit/).
"""
def compress_js(self, js):
from slimit import minify
return minify(js)
......@@ -3,8 +3,8 @@ from pipeline.compressors import SubProcessCompressor
class YUICompressor(SubProcessCompressor):
def compress_common(self, content, type_, arguments):
command = '%s --type=%s %s' % (settings.PIPELINE_YUI_BINARY, type_, arguments)
def compress_common(self, content, compress_type, arguments):
command = '%s --type=%s %s' % (settings.PIPELINE_YUI_BINARY, compress_type, arguments)
return self.execute_command(command, content)
def compress_js(self, js):
......
from django.conf import settings
PIPELINE = getattr(settings, 'PIPELINE', not settings.DEBUG)
PIPELINE_ROOT = getattr(settings, 'PIPELINE_ROOT', settings.STATIC_ROOT)
PIPELINE_URL = getattr(settings, 'PIPELINE_URL', settings.STATIC_URL)
......@@ -25,36 +24,44 @@ 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/bin/env csstidy')
PIPELINE_CSSTIDY_ARGUMENTS = getattr(settings, 'PIPELINE_CSSTIDY_ARGUMENTS', '--template=highest')
PIPELINE_YUI_BINARY = getattr(settings, 'PIPELINE_YUI_BINARY', '/usr/local/bin/yuicompressor')
PIPELINE_YUI_CSS_ARGUMENTS = getattr(settings, 'PIPELINE_YUI_CSS_ARGUMENTS', '')
PIPELINE_YUI_JS_ARGUMENTS = getattr(settings, 'PIPELINE_YUI_JS_ARGUMENTS', '')
PIPELINE_YUI_BINARY = getattr(settings, 'PIPELINE_YUI_BINARY', '/usr/bin/env yuglify')
PIPELINE_YUI_CSS_ARGUMENTS = getattr(settings, 'PIPELINE_YUI_CSS_ARGUMENTS', '--terminal')
PIPELINE_YUI_JS_ARGUMENTS = getattr(settings, 'PIPELINE_YUI_JS_ARGUMENTS', '--terminal')
PIPELINE_CLOSURE_BINARY = getattr(settings, 'PIPELINE_CLOSURE_BINARY', '/usr/local/bin/closure')
PIPELINE_CLOSURE_BINARY = getattr(settings, 'PIPELINE_CLOSURE_BINARY', '/usr/bin/env closure')
PIPELINE_CLOSURE_ARGUMENTS = getattr(settings, 'PIPELINE_CLOSURE_ARGUMENTS', '')
PIPELINE_UGLIFYJS_BINARY = getattr(settings, 'PIPELINE_UGLIFYJS_BINARY', '/usr/local/bin/uglifyjs')
PIPELINE_UGLIFYJS_BINARY = getattr(settings, 'PIPELINE_UGLIFYJS_BINARY', '/usr/bin/env uglifyjs')
PIPELINE_UGLIFYJS_ARGUMENTS = getattr(settings, 'PIPELINE_UGLIFYJS_ARGUMENTS', '')
PIPELINE_COFFEE_SCRIPT_BINARY = getattr(settings, 'PIPELINE_COFFEE_SCRIPT_BINARY', '/usr/local/bin/coffee')
PIPELINE_CSSMIN_BINARY = getattr(settings, 'PIPELINE_CSSMIN_BINARY', '/usr/bin/env cssmin')
PIPELINE_CSSMIN_ARGUMENTS = getattr(settings, 'PIPELINE_CSSMIN_ARGUMENTS', '')
PIPELINE_COFFEE_SCRIPT_BINARY = getattr(settings, 'PIPELINE_COFFEE_SCRIPT_BINARY', '/usr/bin/env coffee')
PIPELINE_COFFEE_SCRIPT_ARGUMENTS = getattr(settings, 'PIPELINE_COFFEE_SCRIPT_ARGUMENTS', '')
PIPELINE_SASS_BINARY = getattr(settings, 'PIPELINE_SASS_BINARY', '/usr/local/bin/sass')
PIPELINE_SASS_BINARY = getattr(settings, 'PIPELINE_SASS_BINARY', '/usr/bin/env sass')
PIPELINE_SASS_ARGUMENTS = getattr(settings, 'PIPELINE_SASS_ARGUMENTS', '')
PIPELINE_STYLUS_BINARY = getattr(settings, 'PIPELINE_STYLUS_BINARY', '/usr/local/bin/stylus')
PIPELINE_STYLUS_BINARY = getattr(settings, 'PIPELINE_STYLUS_BINARY', '/usr/bin/env stylus')
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/bin/env lessc')
PIPELINE_LESS_ARGUMENTS = getattr(settings, 'PIPELINE_LESS_ARGUMENTS', '')
PIPELINE_MIMETYPES = getattr(settings, 'PIPELINE_MIMETYPES', (
('text/coffeescript', '.coffee'),
('text/less', '.less'),
('text/javascript', '.js')
('text/javascript', '.js'),
('text/x-sass', '.sass'),
('text/x-scss', '.scss')
))
PIPELINE_EMBED_MAX_IMAGE_SIZE = getattr(settings, 'PIPELINE_EMBED_MAX_IMAGE_SIZE', 32700)
PIPELINE_EMBED_PATH = getattr(settings, 'PIPELINE_EMBED_PATH', r'[/]?embed/')
if PIPELINE_COMPILERS is None:
PIPELINE_COMPILERS = []
import inspect
try:
from staticfiles.storage import staticfiles_storage
except ImportError:
from django.contrib.staticfiles.storage import staticfiles_storage # noqa
from django.conf import settings as django_settings
from jinja2 import Environment, FileSystemLoader
from pipeline.conf import settings as pipeline_settings
from pipeline.packager import Packager, PackageNotFound
from pipeline.utils import guess_type
class Jinja2Compressed(object):
def __init__(self, package_type):
from django.template.loaders import app_directories
if package_type not in ['css', 'js']:
raise PackageNotFound("Package type must be css or js, supplied %s" % package_type)
self.package_type = package_type
self.loader = FileSystemLoader((app_directories.app_template_dirs +
django_settings.TEMPLATE_DIRS))
self.get_pipeline_settings()
def get_pipeline_settings(self):
"""
Because extra Jinja2 functions have to be declared
at creation time the new functions have to be declared before
django settings evaluation so when pipeline tries to import django
settings it will get the default globals rather than user defined
settings. This function attempts to fudge back in user defined
settings into pipeline settings as django.conf.settings is lazy
loaded and pipeline settings are not.
No harm intended :)
I guess a better more robust solution would be to make pipeline
settings lazy loaded also.
"""
members = inspect.getmembers(pipeline_settings)
for setting, val in members:
if setting.startswith('PIPELINE'):
if hasattr(django_settings, setting):
val = getattr(django_settings, setting)
else:
if type(getattr(pipeline_settings, setting)) == str:
val = "'%s'" % val
val = val if val else "''"
expr = "pipeline_settings.%s = %s" % (
setting, val)
exec expr
pipeline_settings.PIPELINE = getattr(django_settings,
'PIPELINE', not django_settings.DEBUG)
self.settings = pipeline_settings
def get_package(self, name):
"""Get the js or css package."""
package = {
'js': self.settings.PIPELINE_JS.get(name, {}),
'css': self.settings.PIPELINE_CSS.get(name, {}),
}[self.package_type]
if package:
package = {name: package}
self.packager = {
'js': Packager(css_packages={}, js_packages=package),
'css': Packager(css_packages=package, js_packages={}),
}[self.package_type]
try:
self.package = self.packager.package_for(self.package_type, name)
except PackageNotFound:
self.package = None
def render(self, path):
"""Render the HTML tag."""
if not self.package.template_name:
template_name = {
'js': 'pipeline/js.jinja',
'css': 'pipeline/css.jinja',
}[self.package_type]
else:
template_name = self.package.template_name
mimetype = {
'js': 'text/javascript',
'css': 'text/css',
}[self.package_type]
context = self.package.extra_context
context.update({
'type': guess_type(path, mimetype),
'url': staticfiles_storage.url(path)
})
env = Environment(loader=self.loader)
tpl = env.get_template(template_name)
return tpl.render(**context)
def html(self, name):
"""Render the HTML Snippet"""
self.get_package(name)
if self.package:
if self.settings.PIPELINE:
return self.render(self.package.output_filename)
else:
paths = self.packager.compile(self.package.paths)
templates = self.packager.pack_templates(self.package)
return {
'css': self.render_individual_css(paths),
'js': self.render_individual_js(paths, templates)
}[self.package_type]
else:
return '' # don't return anything if no package found
def render_individual_css(self, paths):
"""Render individual CSS files"""
tags = [self.render(path) for path in paths]
return '\n'.join(tags)
def render_individual_js(self, paths, templates=None):
"""Render individual JS files"""
tags = [self.render(path) for path in paths]
if templates:
tags.append(self.render_inline_js(self.package, templates))
return '\n'.join(tags)
def render_inline_js(self, package, js):
template_name = (self.package.template_name or
"pipeline/inline_js.jinja")
context = self.package.extra_context
context.update({
'source': js
})
env = Environment(loader=self.loader)
tpl = env.get_template(template_name)
return tpl.render(**context)
def compressed_css(package_name):
compress = Jinja2Compressed('css')
return compress.html(package_name)
def compressed_js(package_name):
compress = Jinja2Compressed('js')
return compress.html(package_name)
import os
try:
from staticfiles.finders import DefaultStorageFinder
from staticfiles.finders import get_finders
except ImportError:
from django.contrib.staticfiles.storage import DefaultStorageFinder # noqa
from django.contrib.staticfiles.finders import get_finders # noqa
from django.conf import settings
from pipeline.conf import settings
from manifesto import Manifest
......@@ -14,7 +16,8 @@ class PipelineManifest(Manifest):
def __init__(self):
self.packager = Packager()
self.packages = self.collect_packages()
self.finder = DefaultStorageFinder()
self.finders = get_finders()
self.package_files = []
def collect_packages(self):
packages = []
......@@ -29,12 +32,27 @@ class PipelineManifest(Manifest):
return packages
def cache(self):
ignore_patterns = getattr(settings, "STATICFILES_IGNORE_PATTERNS", None)
if settings.PIPELINE:
for package in self.packages:
self.package_files.append(package.output_filename)
yield str(self.packager.individual_url(package.output_filename))
else:
for package in self.packages:
for path in self.packager.compile(package.paths):
self.package_files.append(path)
yield str(self.packager.individual_url(path))
for path in self.finder.list():
yield str(self.packager.individual_url(path))
for finder in self.finders:
for path, storage in finder.list(ignore_patterns):
# Prefix the relative path if the source storage contains it
if getattr(storage, 'prefix', None):
prefixed_path = os.path.join(storage.prefix, path)
else:
prefixed_path = path
# Dont add any doubles
if prefixed_path not in self.package_files:
self.package_files.append(prefixed_path)
yield str(self.packager.individual_url(prefixed_path))
......@@ -88,7 +88,7 @@ class BaseFinderStorage(PipelineStorage):
return path
def exists(self, name):
exists = self.finders.find(name) != None
exists = self.finders.find(name) is not None
if not exists:
return super(BaseFinderStorage, self).exists(name)
return exists
......@@ -101,21 +101,31 @@ class BaseFinderStorage(PipelineStorage):
except OSError:
pass
def match_location(self, name, path, prefix=None):
if prefix:
prefix = "%s%s" % (prefix, os.sep)
name = name[len(prefix):]
if path == name:
return name
if os.path.splitext(path)[0] == os.path.splitext(name)[0]:
return name
return None
def find_storage(self, name):
for finder in finders.get_finders():
for path, storage in finder.list([]):
if path == name:
return storage
if os.path.splitext(path)[0] == os.path.splitext(name)[0]:
return storage
prefix = getattr(storage, 'prefix', None)
matched_path = self.match_location(name, path, prefix)
if matched_path:
return matched_path, 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)
name, storage = self.find_storage(name)
return storage._open(name, mode)
def _save(self, name, content):
storage = self.find_storage(name)
name, storage = self.find_storage(name)
# Ensure we overwrite file, since we have no control on external storage
if storage.exists(name):
storage.delete(name)
......
<link href="{{ url }}" rel="stylesheet" type="{{ type }}"{% if media %} media="{{ media }}"{% endif %}{% if charset %} charset="{{ charset }}"{% endif %} />
<script {% if async %}async{% endif %} {% if defer %}defer{% endif %} type="text/javascript" charset="utf-8">
{{ source|safe }}
</script>
<script {% if async %}async{% endif %} {% if defer %}defer{% endif %} type="{{ type }}" src="{{ url }}" charset="utf-8"></script>
import mimetypes
import os
import sys
import posixpath
import urllib
from django.utils import importlib
......@@ -34,52 +33,18 @@ def guess_type(path, default=None):
return mimetype
def _relpath_nt(path, start=os.path.curdir):
"""Return a relative version of a path"""
if not path:
raise ValueError("no path specified")
start_list = os.path.abspath(start).split(os.path.sep)
path_list = os.path.abspath(path).split(os.path.sep)
if start_list[0].lower() != path_list[0].lower():
unc_path, rest = os.path.splitunc(path)
unc_start, rest = os.path.splitunc(start)
if bool(unc_path) ^ bool(unc_start):
raise ValueError("Cannot mix UNC and non-UNC paths (%s and %s)"
% (path, start))
else:
raise ValueError("path is on drive %s, start on drive %s"
% (path_list[0], start_list[0]))
# Work out how much of the filepath is shared by start and path.
for i in range(min(len(start_list), len(path_list))):
if start_list[i].lower() != path_list[i].lower():
break
else:
i += 1
rel_list = [os.path.pardir] * (len(start_list) - i) + path_list[i:]
if not rel_list:
return os.path.curdir
return os.path.join(*rel_list)
def _relpath_posix(path, start=os.path.curdir):
def relpath(path, start=posixpath.curdir):
"""Return a relative version of a path"""
if not path:
raise ValueError("no path specified")
start_list = os.path.abspath(start).split(os.path.sep)
path_list = os.path.abspath(path).split(os.path.sep)
start_list = posixpath.abspath(start).split(posixpath.sep)
path_list = posixpath.abspath(path).split(posixpath.sep)
# Work out how much of the filepath is shared by start and path.
i = len(os.path.commonprefix([start_list, path_list]))
i = len(posixpath.commonprefix([start_list, path_list]))
rel_list = [os.path.pardir] * (len(start_list) - i) + path_list[i:]
rel_list = [posixpath.pardir] * (len(start_list) - i) + path_list[i:]
if not rel_list:
return os.path.curdir
return os.path.join(*rel_list)
if os.path is sys.modules.get('ntpath'):
relpath = _relpath_nt
else:
relpath = _relpath_posix
return posixpath.curdir
return posixpath.join(*rel_list)
tox
flake8
\ No newline at end of file
......@@ -4,14 +4,14 @@ from setuptools import setup, find_packages
setup(
name='django-pipeline',
version='1.2.9',
version='1.2.21',
description='Pipeline is an asset packaging library for Django.',
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(),
license='MIT',
packages=find_packages(),
zip_safe=False,
include_package_data=True,
......
......@@ -28,7 +28,7 @@ STATICFILES_STORAGE = 'pipeline.storage.PipelineStorage'
STATIC_ROOT = local_path('static/')
STATIC_URL = '/static/'
STATICFILES_DIRS = (
local_path('assets/'),
('pipeline', local_path('assets/')),
local_path('assets2/'),
)
STATICFILES_FINDERS = (
......@@ -45,9 +45,9 @@ TEMPLATE_DIRS = (
PIPELINE_CSS = {
'screen': {
'source_filenames': (
'css/first.css',
'css/second.css',
'css/urls.css',
'pipeline/css/first.css',
'pipeline/css/second.css',
'pipeline/css/urls.css',
),
'output_filename': 'screen.css'
}
......@@ -55,10 +55,10 @@ PIPELINE_CSS = {
PIPELINE_JS = {
'scripts': {
'source_filenames': (
'js/first.js',
'js/second.js',
'js/application.js',
'templates/**/*.jst'
'pipeline/js/first.js',
'pipeline/js/second.js',
'pipeline/js/application.js',
'pipeline/templates/**/*.jst'
),
'output_filename': 'scripts.css'
}
......
{{ compressed_css('screen') }}
{{ compressed_js('scripts') }}
......@@ -2,6 +2,7 @@
from compiler import *
from compressor import *
from glob import *
from jinja2 import *
from packager import *
from storage import *
from utils import *
......@@ -3,6 +3,8 @@ from django.test import TestCase
from pipeline.conf import settings
from pipeline.compilers import Compiler, CompilerBase
from paths import _
class DummyCompiler(CompilerBase):
output_extension = 'js'
......@@ -10,8 +12,8 @@ class DummyCompiler(CompilerBase):
def match_file(self, path):
return path.endswith('.coffee')
def compile_file(self, content, path):
return content
def compile_file(self, infile, outfile, outdated=False, force=False):
return
class CompilerTest(TestCase):
......@@ -30,10 +32,10 @@ class CompilerTest(TestCase):
def test_compile(self):
paths = self.compiler.compile([
'js/dummy.coffee',
'js/application.js',
_('pipeline/js/dummy.coffee'),
_('pipeline/js/application.js'),
])
self.assertEquals(['js/dummy.js', 'js/application.js'], paths)
self.assertEquals([_('pipeline/js/dummy.js'), _('pipeline/js/application.js')], paths)
def tearDown(self):
settings.PIPELINE_COMPILERS = self.old_compilers
......@@ -8,9 +8,12 @@ from django.test import TestCase
from pipeline.compressors import Compressor, TEMPLATE_FUNC
from pipeline.compressors.yui import YUICompressor
from paths import _
class CompressorTest(TestCase):
def setUp(self):
self.maxDiff = None
self.compressor = Compressor()
def test_js_compressor_class(self):
......@@ -21,24 +24,24 @@ class CompressorTest(TestCase):
def test_concatenate_and_rewrite(self):
css = self.compressor.concatenate_and_rewrite([
'css/first.css',
'css/second.css'
_('pipeline/css/first.css'),
_('pipeline/css/second.css')
], 'css/screen.css')
self.assertEquals(""".concat {\n display: none;\n}\n\n.concatenate {\n display: block;\n}\n""", css)
def test_concatenate(self):
js = self.compressor.concatenate([
'js/first.js',
'js/second.js'
_('pipeline/js/first.js'),
_('pipeline/js/second.js')
])
self.assertEquals("""function concat() {\n console.log(arguments);\n}\n\nfunction cat() {\n console.log("hello world");\n}\n""", js)
@patch.object(base64, 'b64encode')
def test_encoded_content(self, mock):
self.compressor.encoded_content('images/arrow.png')
self.compressor.encoded_content(_('pipeline/images/arrow.png'))
self.assertTrue(mock.called)
mock.reset_mock()
self.compressor.encoded_content('images/arrow.png')
self.compressor.encoded_content(_('pipeline/images/arrow.png'))
self.assertFalse(mock.called)
def test_relative_path(self):
......@@ -47,9 +50,9 @@ class CompressorTest(TestCase):
def test_base_path(self):
base_path = self.compressor.base_path([
'js/templates/form.jst', 'js/templates/field.jst'
_('js/templates/form.jst'), _('js/templates/field.jst')
])
self.assertEquals(base_path, 'js/templates')
self.assertEquals(base_path, _('js/templates'))
def test_absolute_path(self):
absolute_path = self.compressor.absolute_path('../../images/sprite.png',
......@@ -70,19 +73,19 @@ class CompressorTest(TestCase):
self.assertEquals(name, 'photo_detail')
def test_compile_templates(self):
templates = self.compressor.compile_templates(['templates/photo/list.jst'])
templates = self.compressor.compile_templates([_('pipeline/templates/photo/list.jst')])
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'
_('pipeline/templates/video/detail.jst'),
_('pipeline/templates/photo/detail.jst')
])
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))
self.assertFalse(self.compressor.embeddable('images/arrow.png', 'datauri'))
self.assertTrue(self.compressor.embeddable('images/embed/arrow.png', 'datauri'))
self.assertFalse(self.compressor.embeddable('images/arrow.dat', 'datauri'))
self.assertFalse(self.compressor.embeddable(_('pipeline/images/sprite.png'), None))
self.assertFalse(self.compressor.embeddable(_('pipeline/images/arrow.png'), 'datauri'))
self.assertTrue(self.compressor.embeddable(_('pipeline/images/embed/arrow.png'), 'datauri'))
self.assertFalse(self.compressor.embeddable(_('pipeline/images/arrow.dat'), 'datauri'))
def test_construct_asset_path(self):
asset_path = self.compressor.construct_asset_path("../../images/sprite.png",
......@@ -94,18 +97,18 @@ class CompressorTest(TestCase):
def test_url_rewrite(self):
output = self.compressor.concatenate_and_rewrite([
'css/urls.css',
_('pipeline/css/urls.css'),
], 'css/screen.css')
self.assertEquals("""@font-face {
self.assertEquals(u"""@font-face {
font-family: 'Pipeline';
src: url(../fonts/pipeline.eot);
src: url(../fonts/pipeline.eot?#iefix) format('embedded-opentype');
src: local('☺'), url(../fonts/pipeline.woff) format('woff'), url(../fonts/pipeline.ttf) format('truetype'), url(../fonts/pipeline.svg#IyfZbseF) format('svg');
src: url(../pipeline/fonts/pipeline.eot);
src: url(../pipeline/fonts/pipeline.eot?#iefix) format('embedded-opentype');
src: local('☺'), url(../pipeline/fonts/pipeline.woff) format('woff'), url(../pipeline/fonts/pipeline.ttf) format('truetype'), url(../pipeline/fonts/pipeline.svg#IyfZbseF) format('svg');
font-weight: normal;
font-style: normal;
}
.relative-url {
background-image: url(../images/sprite-buttons.png);
background-image: url(../pipeline/images/sprite-buttons.png);
}
.absolute-url {
background-image: url(/images/sprite-buttons.png);
......
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from django.conf import settings
from django.test import TestCase
from jinja2 import Environment, FileSystemLoader
from pipeline.packager import PackageNotFound
from pipeline.jinja2.ext import compressed_css, compressed_js, Jinja2Compressed
class Jinja2Test(TestCase):
def setUp(self):
from django.template.loaders import app_directories # has to be here
self.loader = FileSystemLoader((app_directories.app_template_dirs +
settings.TEMPLATE_DIRS))
self.environment = Environment(loader=self.loader)
self.environment.globals['compressed_css'] = compressed_css
self.environment.globals['compressed_js'] = compressed_js
self.maxDiff = None
def test_exception_raised_with_unknown_ftype(self):
try:
Jinja2Compressed('png')
self.fail()
except PackageNotFound:
pass
def test_template_css_function_individual(self):
settings.PIPELINE = False
try:
tpl = self.environment.get_template('css.jinja')
tpl.render()
except:
self.fail('Failed to load individual CSS')
def test_template_css_function_compressed(self):
settings.PIPELINE = True
try:
tpl = self.environment.get_template('css.jinja')
tpl.render()
except:
self.fail('Failed to load compressed CSS')
def test_template_js_function_individual(self):
settings.PIPELINE = False
try:
tpl = self.environment.get_template('js.jinja')
tpl.render()
except:
self.fail('Failed to load individual JS')
def test_template_js_function_compressed(self):
settings.PIPELINE = True
try:
tpl = self.environment.get_template('js.jinja')
tpl.render()
except:
self.fail('Failed to load compressed JS')
......@@ -2,6 +2,8 @@ from django.test import TestCase
from pipeline.packager import Packager, PackageNotFound
from paths import _
class PackagerTest(TestCase):
def test_package_for(self):
......@@ -9,7 +11,7 @@ class PackagerTest(TestCase):
packager.packages['js'] = packager.create_packages({
'application': {
'source_filenames': (
'js/application.js',
_('pipeline/js/application.js'),
),
'output_filename': 'application.js'
}
......@@ -29,9 +31,9 @@ class PackagerTest(TestCase):
packages = packager.create_packages({
'templates': {
'source_filenames': (
'templates/photo/list.jst',
_('pipeline/templates/photo/list.jst'),
),
'output_filename': 'templates.js',
}
})
self.assertEqual(packages['templates'].templates, ['templates/photo/list.jst'])
self.assertEqual(packages['templates'].templates, [_('pipeline/templates/photo/list.jst')])
import os
def _(path):
# Make sure the path contains only the correct separator
return path.replace('/', os.sep).replace('\\', os.sep)
......@@ -3,6 +3,7 @@ from django.utils.datastructures import SortedDict
from pipeline.conf import settings
from pipeline.storage import PipelineStorage
from paths import _
class StorageTest(TestCase):
......@@ -10,8 +11,8 @@ class StorageTest(TestCase):
settings.PIPELINE_CSS = {
'testing': {
'source_filenames': (
'css/first.css',
'css/third.css',
_('pipeline/css/first.css'),
_('css/third.css'),
),
'manifest': False,
'output_filename': 'testing.css',
......
......@@ -19,6 +19,7 @@ deps =
mock
django-staticfiles==1.2.1
unittest2
jinja2
[testenv:py26-1.2.X]
basepython = python2.6
......@@ -27,6 +28,7 @@ deps =
mock
django-staticfiles==1.2.1
unittest2
jinja2
[testenv:py27-1.2.X]
basepython = python2.7
......@@ -35,6 +37,7 @@ deps =
mock
django-staticfiles==1.2.1
unittest2
jinja2
[testenv:py25-1.3.X]
basepython = python2.5
......@@ -43,6 +46,7 @@ deps =
mock
django-staticfiles==1.2.1
unittest2
jinja2
[testenv:py26-1.3.X]
basepython = python2.6
......@@ -51,6 +55,7 @@ deps =
mock
django-staticfiles==1.2.1
unittest2
jinja2
[testenv:py27-1.3.X]
basepython = python2.7
......@@ -59,6 +64,7 @@ deps =
mock
django-staticfiles==1.2.1
unittest2
jinja2
[testenv:py25]
basepython = python2.5
......@@ -67,6 +73,7 @@ deps =
mock
django-staticfiles==1.2.1
unittest2
jinja2
[testenv:py26]
basepython = python2.6
......@@ -75,6 +82,7 @@ deps =
mock
django-staticfiles==1.2.1
unittest2
jinja2
[testenv:py27]
basepython = python2.7
......@@ -83,6 +91,7 @@ deps =
mock
django-staticfiles==1.2.1
unittest2
jinja2
[testenv:pypy]
basepython = pypy
......@@ -91,6 +100,7 @@ deps =
mock
django-staticfiles==1.2.1
unittest2
jinja2
[testenv:docs]
basepython = python2.7
......
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