Commit 48a25dac by Timothée Peignier

Merge branch 'master' into jinja-ext

Conflicts:
	pipeline/jinja2/ext.py
parents b7ac2edc 782de377
......@@ -5,6 +5,7 @@ These people have provided bug fixes, new features, improved the documentation
or just made Pipeline more awesome.
* Adam Charnock <adam@omniwiki.co.uk>
* Alan Lu <gotoalanlu@gmail.com>
* Alexander Artemenko <svetlyak40wt>
* Alexander Pugachev <alexander.pugachev@gmail.com>
* Alexis Svinartchouk <zvin@free.fr>
......@@ -40,4 +41,4 @@ or just made Pipeline more awesome.
* 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>
* Zenobius Jiricek <zenobius.jiricek@gmail.com>
\ No newline at end of file
......@@ -3,19 +3,36 @@
History
=======
1.3.1
-----
* Improve exceptions hierarchy.
* Improve our sub-process calls.
* Update uglify-js documentation. Thanks to Andrey Antukh.
1.3.0
-----
* Add support Python 3, with some help from Alan Lu.
* Add support for Django 1.5.
* Remove support for Django < 1.4.
* Drop support for Python < 2.6.
* Drop support for ``staticfiles`` app, in favor of ``django.contrib.staticfiles``.
* Drop ``PIPELINE`` settings, in favor of ``DEBUG`` to avoid confusion.
* Drop support for ``jinja2`` temporarily.
1.1.24
1.2.24
------
* Fix yui/yuglify settings overriding each other. Thanks to Fábio Santos.
1.1.23
1.2.23
------
* Separate yuglify compressor from YUI compressor.
* Improve HTML compression middleware.
1.1.22
1.2.22
------
* Better compressor error messages. Thanks to Steven Cummings.
......@@ -23,7 +40,7 @@ History
* Fix packaging metadata. Thanks to Rui Coelho for noticing it.
* Add documentation about non-packing storage.
1.1.21
1.2.21
------
* Run stylus even if file is considered outdated.
......
......@@ -121,7 +121,7 @@ To use it add this to your ``PIPELINE_JS_COMPRESSOR`` ::
UglifyJS compressor
===================
The UglifyJS compressor uses `UglifyJS <https://github.com/mishoo/UglifyJS/>`_ to
The UglifyJS compressor uses `UglifyJS <https://github.com/mishoo/UglifyJS2/>`_ to
compress javascripts.
To use it add this to your ``PIPELINE_JS_COMPRESSOR`` ::
......
......@@ -4,16 +4,6 @@
Storages
========
Using with a custom storage
===========================
Pipeline uses `Django Storage <https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#storages>`_
to read, save and delete files, by default it use an improved ``StaticFilesStorage``.
You can provide your own via ``PIPELINE_STORAGE`` : ::
PIPELINE_STORAGE = 'storages.backends.s3boto.S3BotoStorage'
Using with staticfiles
======================
......@@ -36,11 +26,6 @@ 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 : ::
PIPELINE_STORAGE = 'pipeline.storage.PipelineFinderStorage'
Using with other storages
=========================
......
......@@ -65,42 +65,3 @@ 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``.
......@@ -4,11 +4,11 @@ import os
import subprocess
from django.contrib.staticfiles import finders
from django.core.files.base import ContentFile
from django.utils.encoding import smart_str
from pipeline.conf import settings
from pipeline.exceptions import CompilerError
from pipeline.storage import default_storage
from pipeline.utils import to_class
......@@ -18,9 +18,9 @@ class Compiler(object):
self.storage = storage
self.verbose = verbose
@property
def compilers(self):
return [to_class(compiler) for compiler in settings.PIPELINE_COMPILERS]
compilers = property(compilers)
def compile(self, paths, force=False):
for index, input_path in enumerate(paths):
......@@ -39,7 +39,7 @@ class Compiler(object):
outdated = self.is_outdated(input_path, output_path)
compiler.compile_file(infile, outfile, outdated=outdated, force=force)
except CompilerError:
if not self.storage.exists(output_path) or not settings.PIPELINE:
if not self.storage.exists(output_path) or settings.DEBUG:
raise
return paths
......@@ -75,32 +75,16 @@ class CompilerBase(object):
return content
class CompilerError(Exception):
pass
class SubProcessCompiler(CompilerBase):
def execute_command(self, command, content=None, cwd=None):
pipe = subprocess.Popen(command, shell=True, cwd=cwd,
stdout=subprocess.PIPE, stdin=subprocess.PIPE,
stderr=subprocess.PIPE)
if content:
pipe.stdin.write(content)
pipe.stdin.close()
compressed_content = pipe.stdout.read()
pipe.stdout.close()
error = pipe.stderr.read()
pipe.stderr.close()
if pipe.wait() != 0:
if not error:
error = "Unable to apply %s compiler" % self.__class__.__name__
raise CompilerError(error)
if not content:
return content
stdout, stderr = pipe.communicate(content)
if stderr:
raise CompilerError(stderr)
if self.verbose:
print(error)
return compressed_content
print(stderr)
return stdout
......@@ -13,10 +13,9 @@ class StylusCompiler(SubProcessCompiler):
return filename.endswith('.styl')
def compile_file(self, infile, outfile, outdated=False, force=False):
command = "%s %s < %s > %s" % (
command = "%s %s %s" % (
settings.PIPELINE_STYLUS_BINARY,
settings.PIPELINE_STYLUS_ARGUMENTS,
infile,
outfile
infile
)
return self.execute_command(command, cwd=dirname(infile))
......@@ -13,6 +13,7 @@ from django.utils.encoding import smart_bytes, force_text
from pipeline.conf import settings
from pipeline.storage import default_storage
from pipeline.utils import to_class, relpath
from pipeline.exceptions import CompressorError
URL_DETECTOR = r'url\([\'"]?([^\s)]+\.[a-z]+[\?\#\d\w]*)[\'"]?\)'
URL_REPLACER = r'url\(__EMBED__(.+?)(\?\d+)?\)'
......@@ -42,13 +43,13 @@ class Compressor(object):
self.storage = storage
self.verbose = verbose
@property
def js_compressor(self):
return to_class(settings.PIPELINE_JS_COMPRESSOR)
js_compressor = property(js_compressor)
@property
def css_compressor(self):
return to_class(settings.PIPELINE_CSS_COMPRESSOR)
css_compressor = property(css_compressor)
def compress_js(self, paths, templates=None, **kwargs):
"""Concatenate and compress JS files"""
......@@ -224,34 +225,15 @@ class CompressorBase(object):
raise NotImplementedError
class CompressorError(Exception):
"""This exception is raised when a filter fails"""
pass
class SubProcessCompressor(CompressorBase):
def execute_command(self, command, content):
pipe = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE,
stdin=subprocess.PIPE, stderr=subprocess.PIPE)
try:
pipe.stdin.write(smart_bytes(content))
except IOError as e:
message = "Unable to pipe content to command: %s" % command
raise CompressorError(message, e)
pipe.stdin.close()
compressed_content = pipe.stdout.read()
pipe.stdout.close()
error = pipe.stderr.read()
pipe.stderr.close()
if pipe.wait() != 0:
if not error:
error = "Unable to apply %s compressor" % self.__class__.__name__
raise CompressorError(error)
if not content:
return content
stdout, stderr = pipe.communicate(smart_bytes(content))
if stderr:
raise CompressorError(stderr)
if self.verbose:
print(error)
return compressed_content
print(stderr)
return stdout
......@@ -6,7 +6,7 @@ from pipeline.compressors import SubProcessCompressor
class UglifyJSCompressor(SubProcessCompressor):
def compress_js(self, js):
command = '%s -nc %s' % (settings.PIPELINE_UGLIFYJS_BINARY, settings.PIPELINE_UGLIFYJS_ARGUMENTS)
command = '%s %s' % (settings.PIPELINE_UGLIFYJS_BINARY, settings.PIPELINE_UGLIFYJS_ARGUMENTS)
if self.verbose:
command += ' --verbose'
return self.execute_command(command, js)
......@@ -2,7 +2,8 @@ from __future__ import unicode_literals
from django.conf import settings
PIPELINE = getattr(settings, 'PIPELINE', not settings.DEBUG)
DEBUG = getattr(settings, 'DEBUG', False)
PIPELINE_ROOT = getattr(settings, 'PIPELINE_ROOT', settings.STATIC_ROOT)
PIPELINE_URL = getattr(settings, 'PIPELINE_URL', settings.STATIC_URL)
......
from __future__ import unicode_literals
class PipelineException(Exception):
pass
class PackageNotFound(PipelineException):
pass
class CompilerError(PipelineException):
pass
class CompressorError(PipelineException):
pass
......@@ -33,7 +33,7 @@ class PipelineManifest(Manifest):
def cache(self):
ignore_patterns = getattr(settings, "STATICFILES_IGNORE_PATTERNS", None)
if settings.PIPELINE:
if not settings.DEBUG:
for package in self.packages:
self.package_files.append(package.output_filename)
yield str(self.packager.individual_url(package.output_filename))
......
from __future__ import unicode_literals
from django.contrib.staticfiles.finders import find
from django.core.files.base import ContentFile
from django.utils.encoding import smart_str
from pipeline.conf import settings
from pipeline.compilers import Compiler
from pipeline.compressors import Compressor
from pipeline.conf import settings
from pipeline.exceptions import PackageNotFound
from pipeline.glob import glob
from pipeline.signals import css_compressed, js_compressed
from pipeline.storage import default_storage
......@@ -22,7 +24,7 @@ class Package(object):
paths = []
for pattern in self.config.get('source_filenames', []):
for path in glob(pattern):
if not path in paths:
if not path in paths and find(path):
paths.append(str(path))
self._sources = paths
return self._sources
......@@ -120,7 +122,3 @@ class Packager(object):
for name in config:
packages[name] = Package(config[name])
return packages
class PackageNotFound(Exception):
pass
......@@ -28,7 +28,7 @@ class CompressedCSSNode(template.Node):
except PackageNotFound:
return '' # fail silently, do not return anything if an invalid group is specified
if settings.PIPELINE:
if not settings.DEBUG:
return self.render_css(package, package.output_filename)
else:
paths = self.packager.compile(package.paths)
......@@ -64,7 +64,7 @@ class CompressedJSNode(template.Node):
except PackageNotFound:
return '' # fail silently, do not return anything if an invalid group is specified
if settings.PIPELINE:
if not settings.DEBUG:
return self.render_js(package, package.output_filename)
else:
paths = self.packager.compile(package.paths)
......@@ -94,19 +94,19 @@ class CompressedJSNode(template.Node):
return '\n'.join(tags)
@register.tag
def compressed_css(parser, token):
try:
tag_name, name = token.split_contents()
except ValueError:
raise template.TemplateSyntaxError, '%r requires exactly one argument: the name of a group in the PIPELINE_CSS setting' % token.split_contents()[0]
raise template.TemplateSyntaxError('%r requires exactly one argument: the name of a group in the PIPELINE_CSS setting' % token.split_contents()[0])
return CompressedCSSNode(name)
compressed_css = register.tag(compressed_css)
@register.tag
def compressed_js(parser, token):
try:
tag_name, name = token.split_contents()
except ValueError:
raise template.TemplateSyntaxError, '%r requires exactly one argument: the name of a group in the PIPELINE_JS setting' % token.split_contents()[0]
raise template.TemplateSyntaxError('%r requires exactly one argument: the name of a group in the PIPELINE_JS setting' % token.split_contents()[0])
return CompressedJSNode(name)
compressed_js = register.tag(compressed_js)
......@@ -4,7 +4,7 @@ from setuptools import setup, find_packages
setup(
name='django-pipeline',
version='1.3.0',
version='1.3.1',
description='Pipeline is an asset packaging library for Django.',
long_description=open('README.rst').read() + '\n\n' +
open('HISTORY.rst').read(),
......@@ -18,9 +18,11 @@ setup(
classifiers=[
'Environment :: Web Environment',
'Intended Audience :: Developers',
'Development Status :: 5 - Production/Stable',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Topic :: Utilities',
]
)
......@@ -5,4 +5,4 @@ from .test_extension import *
from .test_glob import *
from .test_packager import *
from .test_storage import *
from .test_utils import *
\ No newline at end of file
from .test_utils import *
......@@ -26,18 +26,18 @@ class CompilerTest(TestCase):
def test_output_path(self):
output_path = self.compiler.output_path("js/helpers.coffee", "js")
self.assertEquals(output_path, "js/helpers.js")
self.assertEqual(output_path, "js/helpers.js")
def test_compilers_class(self):
compilers_class = self.compiler.compilers
self.assertEquals(compilers_class[0], DummyCompiler)
self.assertEqual(compilers_class[0], DummyCompiler)
def test_compile(self):
paths = self.compiler.compile([
_('pipeline/js/dummy.coffee'),
_('pipeline/js/application.js'),
])
self.assertEquals([_('pipeline/js/dummy.js'), _('pipeline/js/application.js')], paths)
self.assertEqual([_('pipeline/js/dummy.js'), _('pipeline/js/application.js')], paths)
def tearDown(self):
settings.PIPELINE_COMPILERS = self.old_compilers
......@@ -22,24 +22,24 @@ class CompressorTest(TestCase):
self.compressor = Compressor()
def test_js_compressor_class(self):
self.assertEquals(self.compressor.js_compressor, YuglifyCompressor)
self.assertEqual(self.compressor.js_compressor, YuglifyCompressor)
def test_css_compressor_class(self):
self.assertEquals(self.compressor.css_compressor, YuglifyCompressor)
self.assertEqual(self.compressor.css_compressor, YuglifyCompressor)
def test_concatenate_and_rewrite(self):
css = self.compressor.concatenate_and_rewrite([
_('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)
self.assertEqual(""".concat {\n display: none;\n}\n\n.concatenate {\n display: block;\n}\n""", css)
def test_concatenate(self):
js = self.compressor.concatenate([
_('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)
self.assertEqual("""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):
......@@ -51,35 +51,35 @@ class CompressorTest(TestCase):
def test_relative_path(self):
relative_path = self.compressor.relative_path("images/sprite.png", 'css/screen.css')
self.assertEquals(relative_path, '../images/sprite.png')
self.assertEqual(relative_path, '../images/sprite.png')
def test_base_path(self):
base_path = self.compressor.base_path([
_('js/templates/form.jst'), _('js/templates/field.jst')
])
self.assertEquals(base_path, _('js/templates'))
self.assertEqual(base_path, _('js/templates'))
def test_absolute_path(self):
absolute_path = self.compressor.absolute_path('../../images/sprite.png',
'css/plugins/')
self.assertEquals(absolute_path, 'images/sprite.png')
self.assertEqual(absolute_path, 'images/sprite.png')
absolute_path = self.compressor.absolute_path('/images/sprite.png',
'css/plugins/')
self.assertEquals(absolute_path, '/images/sprite.png')
self.assertEqual(absolute_path, '/images/sprite.png')
def test_template_name(self):
name = self.compressor.template_name('templates/photo/detail.jst',
'templates/')
self.assertEquals(name, 'photo_detail')
self.assertEqual(name, 'photo_detail')
name = self.compressor.template_name('templates/photo_edit.jst', '')
self.assertEquals(name, 'photo_edit')
self.assertEqual(name, 'photo_edit')
name = self.compressor.template_name('templates\photo\detail.jst',
'templates\\')
self.assertEquals(name, 'photo_detail')
self.assertEqual(name, 'photo_detail')
def test_compile_templates(self):
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)
self.assertEqual(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([
_('pipeline/templates/video/detail.jst'),
_('pipeline/templates/photo/detail.jst')
......@@ -95,16 +95,16 @@ class CompressorTest(TestCase):
def test_construct_asset_path(self):
asset_path = self.compressor.construct_asset_path("../../images/sprite.png",
"css/plugins/gallery.css", "css/gallery.css")
self.assertEquals(asset_path, "../images/sprite.png")
self.assertEqual(asset_path, "../images/sprite.png")
asset_path = self.compressor.construct_asset_path("/images/sprite.png",
"css/plugins/gallery.css", "css/gallery.css")
self.assertEquals(asset_path, "/images/sprite.png")
self.assertEqual(asset_path, "/images/sprite.png")
def test_url_rewrite(self):
output = self.compressor.concatenate_and_rewrite([
_('pipeline/css/urls.css'),
], 'css/screen.css')
self.assertEquals("""@font-face {
self.assertEqual("""@font-face {
font-family: 'Pipeline';
src: url(../pipeline/fonts/pipeline.eot);
src: url(../pipeline/fonts/pipeline.eot?#iefix) format('embedded-opentype');
......
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