django-pipeline-1.6.14/ 0000755 0000765 0000024 00000000000 13231236654 014571 5 ustar tim staff 0000000 0000000 django-pipeline-1.6.14/PKG-INFO 0000644 0000765 0000024 00000015437 13231236654 015700 0 ustar tim staff 0000000 0000000 Metadata-Version: 1.1
Name: django-pipeline
Version: 1.6.14
Summary: Pipeline is an asset packaging library for Django.
Home-page: https://github.com/jazzband/django-pipeline
Author: Timothée Peignier
Author-email: timothee.peignier@tryphon.org
License: MIT
Description: Pipeline
========
.. image:: https://travis-ci.org/jazzband/django-pipeline.svg?branch=master
:alt: Build Status
:target: http://travis-ci.org/jazzband/django-pipeline
.. image:: https://coveralls.io/repos/github/jazzband/django-pipeline/badge.svg?branch=master
:alt: Code Coverage
:target: https://coveralls.io/github/jazzband/django-pipeline?branch=master
.. image:: https://jazzband.co/static/img/badge.svg
:alt: Jazzband
:target: https://jazzband.co/
.. image:: https://badge.fury.io/py/django-pipeline.svg
:alt: PYPI
:target: https://badge.fury.io/py/django-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.
Installation
------------
To install it, simply: ::
pip install django-pipeline
Documentation
-------------
For documentation, usage, and examples, see :
https://django-pipeline.readthedocs.io
.. :changelog:
History
=======
1.6.14
======
* Fix packaging issues.
1.6.13
======
* Fix forward-slashed paths on Windows. Thanks to @etiago
* Fix CSS URL detector to match quotes correctly. Thanks to @vskh
* Add a compiler_options dict to compile, to allow passing options to custom
compilers. Thanks to @sassanh
* Verify support for Django 1.11. Thanks to @jwhitlock
1.6.12
======
* Supports Django 1.11
* Fix a bug with os.rename on windows. Thanks to @wismill
* Fix to view compile error if happens. Thanks to @brawaga
* Add support for Pipeline CSS/JS packages in forms and widgets. Thanks to @chipx86
1.6.11
======
* Fix performance regression. Thanks to Christian Hammond.
1.6.10
======
* Added Django 1.10 compatiblity issues. Thanks to Austin Pua and Silvan Spross.
* Documentation improvements. Thanks to Chris Streeter.
1.6.9
=====
* Various build improvements.
* Improved setup.py classifiers. Thanks to Sobolev Nikita.
* Documentation improvements. Thanks to Adam Chainz.
1.6.8
=====
* Made templatetags easier to subclass for special rendering behavior. Thanks
to Christian Hammond.
* Updated the link to readthedocs. Thanks to Corey Farwell.
* Fixed some log messages to correctly refer to the new PIPELINE settings
tructure. Thanks to Alvin Mites.
* Changed file outdated checks to use os.path methods directly, avoiding
potential SuspiciousFileOperation errors which could appear with some django
storage configurations.
1.6.7
=====
* Add a view for collecting static files before serving them. This behaves like
django's built-in ``static`` view and allows running the collector for
images, fonts, and other static files that do not need to be compiled. Thanks
to Christian Hammond.
* Update documentation for the ES6Compiler to clarify filename requirements.
Thanks to Nathan Cox.
* Add error output for compiler errors within the browser. This provides for a
much better experience when compiling files from the devserver. Thanks to
Christian Hammond.
* Make unit tests run against Django 1.6 and 1.7. Thanks to Sławek Ehlert.
1.6.6
=====
* Fix filtering-out of files which require a finder to locate.
* Allow compilers to override the output path.
* Fix error reporting when a compiler fails to execute.
* Fix IOErrors when running collectstatic with some nodejs-based compilers and
compressors. Thanks to Frankie Dintino.
* Fix compatibility of unit tests when running on Windows. Thanks to Frankie
Dintino.
* Add unit tests for compilers and compressors. Thanks to Frankie Dintino.
1.6.5
=====
* Fix Django < 1.8 compatibility. Thanks to David Trowbridge.
* Allow to disable collector during development. Thanks to Leonardo Orozco.
1.6.4
=====
* Fix compressor subprocess calls.
1.6.3
=====
* Fix compressor command flattening.
1.6.2
=====
* Remove subprocess32 usage since it breaks universal support.
1.6.1
=====
* Fix path quoting issues. Thanks to Chad Miller.
* Use subprocess32 package when possible.
* Documentation fixes. Thanks to Sławek Ehlert and Jannis Leidel.
1.6.0
=====
* Add full support for Django 1.9.
* Drop support for Django 1.7.
* Drop support for Python 2.6.
* **BACKWARD INCOMPATIBLE** : Change configuration settings.
Keywords: django pipeline asset compiling concatenation compression packaging
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Framework :: Django
Classifier: Framework :: Django :: 1.6
Classifier: Framework :: Django :: 1.7
Classifier: Framework :: Django :: 1.8
Classifier: Framework :: Django :: 1.9
Classifier: Framework :: Django :: 1.10
Classifier: Framework :: Django :: 1.11
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Utilities
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
django-pipeline-1.6.14/pipeline/ 0000755 0000765 0000024 00000000000 13231236654 016376 5 ustar tim staff 0000000 0000000 django-pipeline-1.6.14/pipeline/templatetags/ 0000755 0000765 0000024 00000000000 13231236654 021070 5 ustar tim staff 0000000 0000000 django-pipeline-1.6.14/pipeline/templatetags/__init__.py 0000664 0000765 0000024 00000000000 12600126513 023160 0 ustar tim staff 0000000 0000000 django-pipeline-1.6.14/pipeline/templatetags/pipeline.py 0000644 0000765 0000024 00000017242 13226744361 023260 0 ustar tim staff 0000000 0000000 from __future__ import unicode_literals
import logging
import subprocess
from django.contrib.staticfiles.storage import staticfiles_storage
from django import template
from django.template.base import Context, VariableDoesNotExist
from django.template.loader import render_to_string
from django.utils.safestring import mark_safe
from ..collector import default_collector
from ..conf import settings
from ..exceptions import CompilerError
from ..packager import Packager, PackageNotFound
from ..utils import guess_type
logger = logging.getLogger(__name__)
register = template.Library()
class PipelineMixin(object):
request = None
_request_var = None
@property
def request_var(self):
if not self._request_var:
self._request_var = template.Variable('request')
return self._request_var
def package_for(self, package_name, package_type):
package = {
'js': getattr(settings, 'JAVASCRIPT', {}).get(package_name, {}),
'css': getattr(settings, 'STYLESHEETS', {}).get(package_name, {}),
}[package_type]
if package:
package = {package_name: package}
packager = {
'js': Packager(css_packages={}, js_packages=package),
'css': Packager(css_packages=package, js_packages={}),
}[package_type]
return packager.package_for(package_type, package_name)
def render(self, context):
try:
self.request = self.request_var.resolve(context)
except VariableDoesNotExist:
pass
def render_compressed(self, package, package_name, package_type):
"""Render HTML for the package.
If ``PIPELINE_ENABLED`` is ``True``, this will render the package's
output file (using :py:meth:`render_compressed_output`). Otherwise,
this will render the package's source files (using
:py:meth:`render_compressed_sources`).
Subclasses can override this method to provide custom behavior for
determining what to render.
"""
if settings.PIPELINE_ENABLED:
return self.render_compressed_output(package, package_name,
package_type)
else:
return self.render_compressed_sources(package, package_name,
package_type)
def render_compressed_output(self, package, package_name, package_type):
"""Render HTML for using the package's output file.
Subclasses can override this method to provide custom behavior for
rendering the output file.
"""
method = getattr(self, 'render_{0}'.format(package_type))
return method(package, package.output_filename)
def render_compressed_sources(self, package, package_name, package_type):
"""Render HTML for using the package's list of source files.
Each source file will first be collected, if
``PIPELINE_COLLECTOR_ENABLED`` is ``True``.
If there are any errors compiling any of the source files, an
``SHOW_ERRORS_INLINE`` is ``True``, those errors will be shown at
the top of the page.
Subclasses can override this method to provide custom behavior for
rendering the source files.
"""
if settings.PIPELINE_COLLECTOR_ENABLED:
default_collector.collect(self.request)
packager = Packager()
method = getattr(self, 'render_individual_{0}'.format(package_type))
try:
paths = packager.compile(package.paths)
except CompilerError as e:
if settings.SHOW_ERRORS_INLINE:
method = getattr(self, 'render_error_{0}'.format(
package_type))
return method(package_name, e)
else:
raise
templates = packager.pack_templates(package)
return method(package, paths, templates=templates)
def render_error(self, package_type, package_name, e):
return render_to_string('pipeline/compile_error.html', {
'package_type': package_type,
'package_name': package_name,
'command': subprocess.list2cmdline(e.command),
'errors': e.error_output,
})
class StylesheetNode(PipelineMixin, template.Node):
def __init__(self, name):
self.name = name
def render(self, context):
super(StylesheetNode, self).render(context)
package_name = template.Variable(self.name).resolve(context)
try:
package = self.package_for(package_name, 'css')
except PackageNotFound:
logger.warn("Package %r is unknown. Check PIPELINE['STYLESHEETS'] in your settings.", package_name)
return '' # fail silently, do not return anything if an invalid group is specified
return self.render_compressed(package, package_name, 'css')
def render_css(self, package, path):
template_name = package.template_name or "pipeline/css.html"
context = package.extra_context
context.update({
'type': guess_type(path, 'text/css'),
'url': mark_safe(staticfiles_storage.url(path))
})
return render_to_string(template_name, context)
def render_individual_css(self, package, paths, **kwargs):
tags = [self.render_css(package, path) for path in paths]
return '\n'.join(tags)
def render_error_css(self, package_name, e):
return super(StylesheetNode, self).render_error(
'CSS', package_name, e)
class JavascriptNode(PipelineMixin, template.Node):
def __init__(self, name):
self.name = name
def render(self, context):
super(JavascriptNode, self).render(context)
package_name = template.Variable(self.name).resolve(context)
try:
package = self.package_for(package_name, 'js')
except PackageNotFound:
logger.warn("Package %r is unknown. Check PIPELINE['JAVASCRIPT'] in your settings.", package_name)
return '' # fail silently, do not return anything if an invalid group is specified
return self.render_compressed(package, package_name, 'js')
def render_js(self, package, path):
template_name = package.template_name or "pipeline/js.html"
context = package.extra_context
context.update({
'type': guess_type(path, 'text/javascript'),
'url': mark_safe(staticfiles_storage.url(path))
})
return render_to_string(template_name, context)
def render_inline(self, package, js):
context = package.extra_context
context.update({
'source': js
})
return render_to_string("pipeline/inline_js.html", context)
def render_individual_js(self, package, paths, templates=None):
tags = [self.render_js(package, js) for js in paths]
if templates:
tags.append(self.render_inline(package, templates))
return '\n'.join(tags)
def render_error_js(self, package_name, e):
return super(JavascriptNode, self).render_error(
'JavaScript', package_name, e)
@register.tag
def stylesheet(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.STYLESHEETS setting' % token.split_contents()[0])
return StylesheetNode(name)
@register.tag
def javascript(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.JAVASVRIPT setting' % token.split_contents()[0])
return JavascriptNode(name)
django-pipeline-1.6.14/pipeline/signals.py 0000664 0000765 0000024 00000000265 12600126513 020404 0 ustar tim staff 0000000 0000000 from __future__ import unicode_literals
from django.dispatch import Signal
css_compressed = Signal(providing_args=["package"])
js_compressed = Signal(providing_args=["package"])
django-pipeline-1.6.14/pipeline/manifest.py 0000644 0000765 0000024 00000004227 12665220514 020561 0 ustar tim staff 0000000 0000000 from __future__ import unicode_literals
import os
from django.conf.settings import settings as django_settings
from django.contrib.staticfiles.finders import get_finders
from django.contrib.staticfiles.storage import staticfiles_storage
from pipeline.conf import settings
from manifesto import Manifest
from pipeline.packager import Packager
class PipelineManifest(Manifest):
def __init__(self):
self.packager = Packager()
self.packages = self.collect_packages()
self.finders = get_finders()
self.package_files = []
def collect_packages(self):
packages = []
for package_name in self.packager.packages['css']:
package = self.packager.package_for('css', package_name)
if package.manifest:
packages.append(package)
for package_name in self.packager.packages['js']:
package = self.packager.package_for('js', package_name)
if package.manifest:
packages.append(package)
return packages
def cache(self):
if settings.PIPELINE_ENABLED:
for package in self.packages:
path = package.output_filename
self.package_files.append(path)
yield staticfiles_storage.url(path)
else:
for package in self.packages:
for path in self.packager.compile(package.paths):
self.package_files.append(path)
yield staticfiles_storage.url(path)
ignore_patterns = getattr(django_settings, "STATICFILES_IGNORE_PATTERNS", None)
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 staticfiles_storage.url(prefixed_path)
django-pipeline-1.6.14/pipeline/jinja2/ 0000755 0000765 0000024 00000000000 13231236654 017553 5 ustar tim staff 0000000 0000000 django-pipeline-1.6.14/pipeline/jinja2/pipeline/ 0000755 0000765 0000024 00000000000 13231236654 021360 5 ustar tim staff 0000000 0000000 django-pipeline-1.6.14/pipeline/jinja2/pipeline/js.jinja 0000664 0000765 0000024 00000000202 12600126513 022774 0 ustar tim staff 0000000 0000000
django-pipeline-1.6.14/pipeline/jinja2/pipeline/css.jinja 0000664 0000765 0000024 00000000235 12600126513 023156 0 ustar tim staff 0000000 0000000
django-pipeline-1.6.14/pipeline/jinja2/pipeline/inline_js.jinja 0000664 0000765 0000024 00000000174 12600126513 024342 0 ustar tim staff 0000000 0000000
django-pipeline-1.6.14/pipeline/jinja2/__init__.py 0000664 0000765 0000024 00000006001 12701105433 021652 0 ustar tim staff 0000000 0000000 from __future__ import unicode_literals
from jinja2 import nodes, TemplateSyntaxError
from jinja2.ext import Extension
from django.contrib.staticfiles.storage import staticfiles_storage
from ..packager import PackageNotFound
from ..utils import guess_type
from ..templatetags.pipeline import PipelineMixin
class PipelineExtension(PipelineMixin, Extension):
tags = set(['stylesheet', 'javascript'])
def parse(self, parser):
tag = next(parser.stream)
package_name = parser.parse_expression()
if not package_name:
raise TemplateSyntaxError("Bad package name", tag.lineno)
args = [package_name]
if tag.value == "stylesheet":
return nodes.CallBlock(self.call_method('package_css', args), [], [], []).set_lineno(tag.lineno)
if tag.value == "javascript":
return nodes.CallBlock(self.call_method('package_js', args), [], [], []).set_lineno(tag.lineno)
return []
def package_css(self, package_name, *args, **kwargs):
try:
package = self.package_for(package_name, 'css')
except PackageNotFound:
return '' # fail silently, do not return anything if an invalid group is specified
return self.render_compressed(package, package_name, 'css')
def render_css(self, package, path):
template_name = package.template_name or "pipeline/css.jinja"
context = package.extra_context
context.update({
'type': guess_type(path, 'text/css'),
'url': staticfiles_storage.url(path)
})
template = self.environment.get_template(template_name)
return template.render(context)
def render_individual_css(self, package, paths, **kwargs):
tags = [self.render_css(package, path) for path in paths]
return '\n'.join(tags)
def package_js(self, package_name, *args, **kwargs):
try:
package = self.package_for(package_name, 'js')
except PackageNotFound:
return '' # fail silently, do not return anything if an invalid group is specified
return self.render_compressed(package, package_name, 'js')
def render_js(self, package, path):
template_name = package.template_name or "pipeline/js.jinja"
context = package.extra_context
context.update({
'type': guess_type(path, 'text/javascript'),
'url': staticfiles_storage.url(path)
})
template = self.environment.get_template(template_name)
return template.render(context)
def render_inline(self, package, js):
context = package.extra_context
context.update({
'source': js
})
template = self.environment.get_template("pipeline/inline_js.jinja")
return template.render(context)
def render_individual_js(self, package, paths, templates=None):
tags = [self.render_js(package, js) for js in paths]
if templates:
tags.append(self.render_inline(package, templates))
return '\n'.join(tags)
django-pipeline-1.6.14/pipeline/finders.py 0000644 0000765 0000024 00000006161 13226744361 020411 0 ustar tim staff 0000000 0000000 from itertools import chain
from django.contrib.staticfiles.storage import staticfiles_storage
from django.contrib.staticfiles.finders import BaseFinder, BaseStorageFinder, find, \
AppDirectoriesFinder as DjangoAppDirectoriesFinder, FileSystemFinder as DjangoFileSystemFinder
from django.utils._os import safe_join
from os.path import normpath
from pipeline.conf import settings
class PipelineFinder(BaseStorageFinder):
storage = staticfiles_storage
def find(self, path, all=False):
if not settings.PIPELINE_ENABLED:
return super(PipelineFinder, self).find(path, all)
else:
return []
def list(self, ignore_patterns):
return []
class ManifestFinder(BaseFinder):
def find(self, path, all=False):
"""
Looks for files in PIPELINE.STYLESHEETS and PIPELINE.JAVASCRIPT
"""
matches = []
for elem in chain(settings.STYLESHEETS.values(), settings.JAVASCRIPT.values()):
if normpath(elem['output_filename']) == normpath(path):
match = safe_join(settings.PIPELINE_ROOT, path)
if not all:
return match
matches.append(match)
return matches
def list(self, *args):
return []
class CachedFileFinder(BaseFinder):
def find(self, path, all=False):
"""
Work out the uncached name of the file and look that up instead
"""
try:
start, _, extn = path.rsplit('.', 2)
except ValueError:
return []
path = '.'.join((start, extn))
return find(path, all=all) or []
def list(self, *args):
return []
class PatternFilterMixin(object):
ignore_patterns = []
def get_ignored_patterns(self):
return list(set(self.ignore_patterns))
def list(self, ignore_patterns):
if ignore_patterns:
ignore_patterns = ignore_patterns + self.get_ignored_patterns()
return super(PatternFilterMixin, self).list(ignore_patterns)
class AppDirectoriesFinder(PatternFilterMixin, DjangoAppDirectoriesFinder):
"""
Like AppDirectoriesFinder, but doesn't return any additional ignored
patterns.
This allows us to concentrate/compress our components without dragging
the raw versions in via collectstatic.
"""
ignore_patterns = [
'*.js',
'*.css',
'*.less',
'*.scss',
'*.styl',
]
class FileSystemFinder(PatternFilterMixin, DjangoFileSystemFinder):
"""
Like FileSystemFinder, but doesn't return any additional ignored patterns
This allows us to concentrate/compress our components without dragging
the raw versions in too.
"""
ignore_patterns = [
'*.js',
'*.css',
'*.less',
'*.scss',
'*.styl',
'*.sh',
'*.html',
'*.md',
'*.markdown',
'*.php',
'*.txt',
'README*',
'LICENSE*',
'*examples*',
'*test*',
'*bin*',
'*samples*',
'*docs*',
'*build*',
'*demo*',
'Makefile*',
'Gemfile*',
'node_modules',
]
django-pipeline-1.6.14/pipeline/conf.py 0000644 0000765 0000024 00000006471 12761144777 017720 0 ustar tim staff 0000000 0000000 # -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os
import collections
import shlex
from django.conf import settings as _settings
try:
from django.core.signals import setting_changed
except ImportError:
# Django < 1.8
from django.test.signals import setting_changed
from django.dispatch import receiver
from django.utils.six import string_types
DEFAULTS = {
'PIPELINE_ENABLED': not _settings.DEBUG,
'PIPELINE_COLLECTOR_ENABLED': True,
'PIPELINE_ROOT': _settings.STATIC_ROOT,
'PIPELINE_URL': _settings.STATIC_URL,
'SHOW_ERRORS_INLINE': _settings.DEBUG,
'CSS_COMPRESSOR': 'pipeline.compressors.yuglify.YuglifyCompressor',
'JS_COMPRESSOR': 'pipeline.compressors.yuglify.YuglifyCompressor',
'COMPILERS': [],
'STYLESHEETS': {},
'JAVASCRIPT': {},
'TEMPLATE_NAMESPACE': "window.JST",
'TEMPLATE_EXT': ".jst",
'TEMPLATE_FUNC': "template",
'TEMPLATE_SEPARATOR': "_",
'DISABLE_WRAPPER': False,
'JS_WRAPPER': "(function() {\n%s\n}).call(this);",
'CSSTIDY_BINARY': '/usr/bin/env csstidy',
'CSSTIDY_ARGUMENTS': '--template=highest',
'YUGLIFY_BINARY': '/usr/bin/env yuglify',
'YUGLIFY_CSS_ARGUMENTS': '--terminal',
'YUGLIFY_JS_ARGUMENTS': '--terminal',
'YUI_BINARY': '/usr/bin/env yuicompressor',
'YUI_CSS_ARGUMENTS': '',
'YUI_JS_ARGUMENTS': '',
'CLOSURE_BINARY': '/usr/bin/env closure',
'CLOSURE_ARGUMENTS': '',
'UGLIFYJS_BINARY': '/usr/bin/env uglifyjs',
'UGLIFYJS_ARGUMENTS': '',
'CSSMIN_BINARY': '/usr/bin/env cssmin',
'CSSMIN_ARGUMENTS': '',
'COFFEE_SCRIPT_BINARY': '/usr/bin/env coffee',
'COFFEE_SCRIPT_ARGUMENTS': '',
'BABEL_BINARY': '/usr/bin/env babel',
'BABEL_ARGUMENTS': '',
'LIVE_SCRIPT_BINARY': '/usr/bin/env lsc',
'LIVE_SCRIPT_ARGUMENTS': '',
'SASS_BINARY': '/usr/bin/env sass',
'SASS_ARGUMENTS': '',
'STYLUS_BINARY': '/usr/bin/env stylus',
'STYLUS_ARGUMENTS': '',
'LESS_BINARY': '/usr/bin/env lessc',
'LESS_ARGUMENTS': '',
'MIMETYPES': (
(b'text/coffeescript', '.coffee'),
(b'text/less', '.less'),
(b'text/javascript', '.js'),
(b'text/x-sass', '.sass'),
(b'text/x-scss', '.scss')
),
'EMBED_MAX_IMAGE_SIZE': 32700,
'EMBED_PATH': r'[/]?embed/',
}
class PipelineSettings(collections.MutableMapping):
"""
Container object for pipeline settings
"""
def __init__(self, wrapped_settings):
self.settings = DEFAULTS.copy()
self.settings.update(wrapped_settings)
def __getitem__(self, key):
value = self.settings[key]
if key.endswith(("_BINARY", "_ARGUMENTS")):
if isinstance(value, string_types):
return tuple(shlex.split(value, posix=(os.name == 'posix')))
return tuple(value)
return value
def __setitem__(self, key, value):
self.settings[key] = value
def __delitem__(self, key):
del self.store[key]
def __iter__(self):
return iter(self.settings)
def __len__(self):
return len(self.settings)
def __getattr__(self, name):
return self.__getitem__(name)
settings = PipelineSettings(_settings.PIPELINE)
@receiver(setting_changed)
def reload_settings(**kwargs):
if kwargs['setting'] == 'PIPELINE':
settings.update(kwargs['value'])
django-pipeline-1.6.14/pipeline/packager.py 0000644 0000765 0000024 00000010632 13226744361 020532 0 ustar tim staff 0000000 0000000 from __future__ import unicode_literals
from django.contrib.staticfiles.storage import staticfiles_storage
from django.contrib.staticfiles.finders import find
from django.core.files.base import ContentFile
from django.utils.encoding import smart_bytes
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
class Package(object):
def __init__(self, config):
self.config = config
self._sources = []
@property
def sources(self):
if not self._sources:
paths = []
for pattern in self.config.get('source_filenames', []):
for path in glob(pattern):
if path not in paths and find(path):
paths.append(str(path))
self._sources = paths
return self._sources
@property
def paths(self):
return [path for path in self.sources
if not path.endswith(settings.TEMPLATE_EXT)]
@property
def templates(self):
return [path for path in self.sources
if path.endswith(settings.TEMPLATE_EXT)]
@property
def output_filename(self):
return self.config.get('output_filename')
@property
def extra_context(self):
return self.config.get('extra_context', {})
@property
def template_name(self):
return self.config.get('template_name')
@property
def variant(self):
return self.config.get('variant')
@property
def manifest(self):
return self.config.get('manifest', True)
@property
def compiler_options(self):
return self.config.get('compiler_options', {})
class Packager(object):
def __init__(self, storage=None, verbose=False, css_packages=None, js_packages=None):
if storage is None:
storage = staticfiles_storage
self.storage = storage
self.verbose = verbose
self.compressor = Compressor(storage=storage, verbose=verbose)
self.compiler = Compiler(storage=storage, verbose=verbose)
if css_packages is None:
css_packages = settings.STYLESHEETS
if js_packages is None:
js_packages = settings.JAVASCRIPT
self.packages = {
'css': self.create_packages(css_packages),
'js': self.create_packages(js_packages),
}
def package_for(self, kind, package_name):
try:
return self.packages[kind][package_name]
except KeyError:
raise PackageNotFound(
"No corresponding package for %s package name : %s" % (
kind, package_name
)
)
def individual_url(self, filename):
return self.storage.url(filename)
def pack_stylesheets(self, package, **kwargs):
return self.pack(package, self.compressor.compress_css, css_compressed,
output_filename=package.output_filename,
variant=package.variant, **kwargs)
def compile(self, paths, compiler_options={}, force=False):
return self.compiler.compile(
paths,
compiler_options=compiler_options,
force=force,
)
def pack(self, package, compress, signal, **kwargs):
output_filename = package.output_filename
if self.verbose:
print("Saving: %s" % output_filename)
paths = self.compile(
package.paths,
compiler_options=package.compiler_options,
force=True,
)
content = compress(paths, **kwargs)
self.save_file(output_filename, content)
signal.send(sender=self, package=package, **kwargs)
return output_filename
def pack_javascripts(self, package, **kwargs):
return self.pack(package, self.compressor.compress_js, js_compressed, templates=package.templates, **kwargs)
def pack_templates(self, package):
return self.compressor.compile_templates(package.templates)
def save_file(self, path, content):
return self.storage.save(path, ContentFile(smart_bytes(content)))
def create_packages(self, config):
packages = {}
if not config:
return packages
for name in config:
packages[name] = Package(config[name])
return packages
django-pipeline-1.6.14/pipeline/collector.py 0000644 0000765 0000024 00000006774 13036753101 020746 0 ustar tim staff 0000000 0000000 from __future__ import unicode_literals
import os
from collections import OrderedDict
import django
from django.contrib.staticfiles import finders
from django.contrib.staticfiles.storage import staticfiles_storage
from django.utils import six
from pipeline.finders import PipelineFinder
class Collector(object):
request = None
def __init__(self, storage=None):
if storage is None:
storage = staticfiles_storage
self.storage = storage
def _get_modified_time(self, storage, prefixed_path):
if django.VERSION[:2] >= (1, 10):
return storage.get_modified_time(prefixed_path)
return storage.modified_time(prefixed_path)
def clear(self, path=""):
dirs, files = self.storage.listdir(path)
for f in files:
fpath = os.path.join(path, f)
self.storage.delete(fpath)
for d in dirs:
self.clear(os.path.join(path, d))
def collect(self, request=None, files=[]):
if self.request and self.request is request:
return
self.request = request
found_files = OrderedDict()
for finder in finders.get_finders():
# Ignore our finder to avoid looping
if isinstance(finder, PipelineFinder):
continue
for path, storage in finder.list(['CVS', '.*', '*~']):
# 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
if (prefixed_path not in found_files and
(not files or prefixed_path in files)):
found_files[prefixed_path] = (storage, path)
self.copy_file(path, prefixed_path, storage)
if files and len(files) == len(found_files):
break
return six.iterkeys(found_files)
def copy_file(self, path, prefixed_path, source_storage):
# Delete the target file if needed or break
if not self.delete_file(path, prefixed_path, source_storage):
return
# Finally start copying
with source_storage.open(path) as source_file:
self.storage.save(prefixed_path, source_file)
def delete_file(self, path, prefixed_path, source_storage):
if self.storage.exists(prefixed_path):
try:
# When was the target file modified last time?
target_last_modified = self._get_modified_time(self.storage, prefixed_path)
except (OSError, NotImplementedError, AttributeError):
# The storage doesn't support ``modified_time`` or failed
pass
else:
try:
# When was the source file modified last time?
source_last_modified = self._get_modified_time(source_storage, path)
except (OSError, NotImplementedError, AttributeError):
pass
else:
# Skip the file if the source file is younger
# Avoid sub-second precision
if (target_last_modified.replace(microsecond=0)
>= source_last_modified.replace(microsecond=0)):
return False
# Then delete the existing file if really needed
self.storage.delete(prefixed_path)
return True
default_collector = Collector()
django-pipeline-1.6.14/pipeline/__init__.py 0000664 0000765 0000024 00000000000 12600126513 020466 0 ustar tim staff 0000000 0000000 django-pipeline-1.6.14/pipeline/glob.py 0000664 0000765 0000024 00000003662 12701105433 017673 0 ustar tim staff 0000000 0000000 from __future__ import unicode_literals
import os
import re
import fnmatch
from django.contrib.staticfiles.storage import staticfiles_storage
__all__ = ["glob", "iglob"]
def glob(pathname):
"""Return a list of paths matching a pathname pattern.
The pattern may contain simple shell-style wildcards a la fnmatch.
"""
return sorted(list(iglob(pathname)))
def iglob(pathname):
"""Return an iterator which yields the paths matching a pathname pattern.
The pattern may contain simple shell-style wildcards a la fnmatch.
"""
if not has_magic(pathname):
yield pathname
return
dirname, basename = os.path.split(pathname)
if not dirname:
for name in glob1(dirname, basename):
yield name
return
if has_magic(dirname):
dirs = iglob(dirname)
else:
dirs = [dirname]
if has_magic(basename):
glob_in_dir = glob1
else:
glob_in_dir = glob0
for dirname in dirs:
for name in glob_in_dir(dirname, basename):
yield os.path.join(dirname, name)
# These 2 helper functions non-recursively glob inside a literal directory.
# They return a list of basenames. `glob1` accepts a pattern while `glob0`
# takes a literal basename (so it only has to check for its existence).
def glob1(dirname, pattern):
try:
directories, files = staticfiles_storage.listdir(dirname)
names = directories + files
except Exception:
# We are not sure that dirname is a real directory
# and storage implementations are really exotic.
return []
if pattern[0] != '.':
names = [x for x in names if x[0] != '.']
return fnmatch.filter(names, pattern)
def glob0(dirname, basename):
if staticfiles_storage.exists(os.path.join(dirname, basename)):
return [basename]
return []
magic_check = re.compile('[*?[]')
def has_magic(s):
return magic_check.search(s) is not None
django-pipeline-1.6.14/pipeline/forms.py 0000644 0000765 0000024 00000022532 13226744361 020105 0 ustar tim staff 0000000 0000000 """Support for referencing Pipeline packages in forms and widgets."""
from __future__ import unicode_literals
from django.contrib.staticfiles.storage import staticfiles_storage
from django.utils import six
from django.utils.functional import cached_property
from .collector import default_collector
from .conf import settings
from .packager import Packager
class PipelineFormMediaProperty(object):
"""A property that converts Pipeline packages to lists of files.
This is used behind the scenes for any Media classes that subclass
:py:class:`PipelineFormMedia`. When accessed, it converts any Pipeline
packages into lists of media files and returns or forwards on lookups to
that list.
"""
def __init__(self, get_media_files_func, media_cls, extra_files):
"""Initialize the property.
Args:
get_media_files_func (callable):
The function to call to generate the media files.
media_cls (type):
The Media class owning the property.
extra_files (object):
Files listed in the original ``css`` or ``js`` attribute on
the Media class.
"""
self._get_media_files_func = get_media_files_func
self._media_cls = media_cls
self._extra_files = extra_files
@cached_property
def _media_files(self):
"""The media files represented by the property."""
return self._get_media_files_func(self._media_cls, self._extra_files)
def __get__(self, *args, **kwargs):
"""Return the media files when accessed as an attribute.
This is called when accessing the attribute directly through the
Media class (for example, ``Media.css``). It returns the media files
directly.
Args:
*args (tuple, unused):
Unused positional arguments.
**kwargs (dict, unused):
Unused keyword arguments.
Returns:
object:
The list or dictionary containing the media files definition.
"""
return self._media_files
def __getattr__(self, attr_name):
"""Return an attribute on the media files definition.
This is called when accessing an attribute that doesn't otherwise
exist in the property's dictionary. The call is forwarded onto the
media files definition.
Args:
attr_name (unicode):
The attribute name.
Returns:
object:
The attribute value.
Raises:
AttributeError:
An attribute with this name could not be found.
"""
return getattr(self._media_files, attr_name)
def __iter__(self):
"""Iterate through the media files definition.
This is called when attempting to iterate over this property. It
iterates over the media files definition instead.
Yields:
object:
Each entry in the media files definition.
"""
return iter(self._media_files)
class PipelineFormMediaMetaClass(type):
"""Metaclass for the PipelineFormMedia class.
This is responsible for converting CSS/JavaScript packages defined in
Pipeline into lists of files to include on a page. It handles access to the
:py:attr:`css` and :py:attr:`js` attributes on the class, generating a
list of files to return based on the Pipelined packages and individual
files listed in the :py:attr:`css`/:py:attr:`css_packages` or
:py:attr:`js`/:py:attr:`js_packages` attributes.
"""
def __new__(cls, name, bases, attrs):
"""Construct the class.
Args:
name (bytes):
The name of the class.
bases (tuple):
The base classes for the class.
attrs (dict):
The attributes going into the class.
Returns:
type:
The new class.
"""
new_class = super(PipelineFormMediaMetaClass, cls).__new__(
cls, name, bases, attrs)
# If we define any packages, we'll need to use our special
# PipelineFormMediaProperty class. We use this instead of intercepting
# in __getattribute__ because Django does not access them through
# normal properpty access. Instead, grabs the Media class's __dict__
# and accesses them from there. By using these special properties, we
# can handle direct access (Media.css) and dictionary-based access
# (Media.__dict__['css']).
if 'css_packages' in attrs:
new_class.css = PipelineFormMediaProperty(
cls._get_css_files, new_class, attrs.get('css') or {})
if 'js_packages' in attrs:
new_class.js = PipelineFormMediaProperty(
cls._get_js_files, new_class, attrs.get('js') or [])
return new_class
def _get_css_files(cls, extra_files):
"""Return all CSS files from the Media class.
Args:
extra_files (dict):
The contents of the Media class's original :py:attr:`css`
attribute, if one was provided.
Returns:
dict:
The CSS media types and files to return for the :py:attr:`css`
attribute.
"""
packager = Packager()
css_packages = getattr(cls, 'css_packages', {})
return dict(
(media_target,
cls._get_media_files(packager=packager,
media_packages=media_packages,
media_type='css',
extra_files=extra_files.get(media_target,
[])))
for media_target, media_packages in six.iteritems(css_packages)
)
def _get_js_files(cls, extra_files):
"""Return all JavaScript files from the Media class.
Args:
extra_files (list):
The contents of the Media class's original :py:attr:`js`
attribute, if one was provided.
Returns:
list:
The JavaScript files to return for the :py:attr:`js` attribute.
"""
return cls._get_media_files(
packager=Packager(),
media_packages=getattr(cls, 'js_packages', {}),
media_type='js',
extra_files=extra_files)
def _get_media_files(cls, packager, media_packages, media_type,
extra_files):
"""Return source or output media files for a list of packages.
This will go through the media files belonging to the provided list
of packages referenced in a Media class and return the output files
(if Pipeline is enabled) or the source files (if not enabled).
Args:
packager (pipeline.packager.Packager):
The packager responsible for media compilation for this type
of package.
media_packages (list of unicode):
The list of media packages referenced in Media to compile or
return.
extra_files (list of unicode):
The list of extra files to include in the result. This would
be the list stored in the Media class's original :py:attr:`css`
or :py:attr:`js` attributes.
Returns:
list:
The list of media files for the given packages.
"""
source_files = list(extra_files)
if (not settings.PIPELINE_ENABLED and
settings.PIPELINE_COLLECTOR_ENABLED):
default_collector.collect()
for media_package in media_packages:
package = packager.package_for(media_type, media_package)
if settings.PIPELINE_ENABLED:
source_files.append(
staticfiles_storage.url(package.output_filename))
else:
source_files += packager.compile(package.paths)
return source_files
@six.add_metaclass(PipelineFormMediaMetaClass)
class PipelineFormMedia(object):
"""Base class for form or widget Media classes that use Pipeline packages.
Forms or widgets that need custom CSS or JavaScript media on a page can
define a standard :py:class:`Media` class that subclasses this class,
listing the CSS or JavaScript packages in :py:attr:`css_packages` and
:py:attr:`js_packages` attributes. These are formatted the same as the
standard :py:attr:`css` and :py:attr:`js` attributes, but reference
Pipeline package names instead of individual source files.
If Pipeline is enabled, these will expand to the output files for the
packages. Otherwise, these will expand to the list of source files for the
packages.
Subclasses can also continue to define :py:attr:`css` and :py:attr:`js`
attributes, which will be returned along with the other output/source
files.
Example:
from django import forms
from pipeline.forms import PipelineFormMedia
class MyForm(forms.Media):
...
class Media(PipelineFormMedia):
css_packages = {
'all': ('my-form-styles-package',
'other-form-styles-package'),
'print': ('my-form-print-styles-package',),
}
js_packages = ('my-form-scripts-package',)
js = ('some-file.js',)
"""
django-pipeline-1.6.14/pipeline/compressors/ 0000755 0000765 0000024 00000000000 13231236654 020755 5 ustar tim staff 0000000 0000000 django-pipeline-1.6.14/pipeline/compressors/csstidy.py 0000644 0000765 0000024 00000001162 12665220514 023007 0 ustar tim staff 0000000 0000000 from __future__ import unicode_literals
from django.core.files import temp as tempfile
from pipeline.conf import settings
from pipeline.compressors import SubProcessCompressor
class CSSTidyCompressor(SubProcessCompressor):
def compress_css(self, css):
output_file = tempfile.NamedTemporaryFile(suffix='.pipeline')
command = (
settings.CSSTIDY_BINARY,
"-",
settings.CSSTIDY_ARGUMENTS,
output_file.name
)
self.execute_command(command, css)
filtered_css = output_file.read()
output_file.close()
return filtered_css
django-pipeline-1.6.14/pipeline/compressors/slimit.py 0000664 0000765 0000024 00000000537 12600126513 022626 0 ustar tim staff 0000000 0000000 from __future__ import absolute_import, unicode_literals
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)
django-pipeline-1.6.14/pipeline/compressors/yuglify.py 0000644 0000765 0000024 00000001222 12665220514 023012 0 ustar tim staff 0000000 0000000 from __future__ import unicode_literals
from pipeline.conf import settings
from pipeline.compressors import SubProcessCompressor
class YuglifyCompressor(SubProcessCompressor):
def compress_common(self, content, compress_type, arguments):
command = (
settings.YUGLIFY_BINARY,
"--type={}".format(compress_type),
arguments
)
return self.execute_command(command, content)
def compress_js(self, js):
return self.compress_common(js, 'js', settings.YUGLIFY_JS_ARGUMENTS)
def compress_css(self, css):
return self.compress_common(css, 'css', settings.YUGLIFY_CSS_ARGUMENTS)
django-pipeline-1.6.14/pipeline/compressors/__init__.py 0000644 0000765 0000024 00000023436 13226744361 023101 0 ustar tim staff 0000000 0000000 from __future__ import unicode_literals
import base64
import os
import posixpath
import re
import subprocess
from itertools import takewhile
from django.contrib.staticfiles.storage import staticfiles_storage
from django.utils.encoding import smart_bytes, force_text
from django.utils.six import string_types
from pipeline.conf import settings
from pipeline.exceptions import CompressorError
from pipeline.utils import to_class, relpath, set_std_streams_blocking
URL_DETECTOR = r"""url\((['"]?)\s*(.*?)\1\)"""
URL_REPLACER = r"""url\(__EMBED__(.+?)(\?\d+)?\)"""
NON_REWRITABLE_URL = re.compile(r'^(#|http:|https:|data:|//)')
DEFAULT_TEMPLATE_FUNC = "template"
TEMPLATE_FUNC = r"""var template = function(str){var fn = new Function('obj', 'var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push(\''+str.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/<%=([\s\S]+?)%>/g,function(match,code){return "',"+code.replace(/\\'/g, "'")+",'";}).replace(/<%([\s\S]+?)%>/g,function(match,code){return "');"+code.replace(/\\'/g, "'").replace(/[\r\n\t]/g,' ')+"__p.push('";}).replace(/\r/g,'\\r').replace(/\n/g,'\\n').replace(/\t/g,'\\t')+"');}return __p.join('');");return fn;};"""
MIME_TYPES = {
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.tif': 'image/tiff',
'.tiff': 'image/tiff',
'.ttf': 'font/truetype',
'.otf': 'font/opentype',
'.woff': 'font/woff'
}
EMBED_EXTS = MIME_TYPES.keys()
FONT_EXTS = ['.ttf', '.otf', '.woff']
class Compressor(object):
asset_contents = {}
def __init__(self, storage=None, verbose=False):
if storage is None:
storage = staticfiles_storage
self.storage = storage
self.verbose = verbose
@property
def js_compressor(self):
return to_class(settings.JS_COMPRESSOR)
@property
def css_compressor(self):
return to_class(settings.CSS_COMPRESSOR)
def compress_js(self, paths, templates=None, **kwargs):
"""Concatenate and compress JS files"""
js = self.concatenate(paths)
if templates:
js = js + self.compile_templates(templates)
if not settings.DISABLE_WRAPPER:
js = settings.JS_WRAPPER % js
compressor = self.js_compressor
if compressor:
js = getattr(compressor(verbose=self.verbose), 'compress_js')(js)
return js
def compress_css(self, paths, output_filename, variant=None, **kwargs):
"""Concatenate and compress CSS files"""
css = self.concatenate_and_rewrite(paths, output_filename, variant)
compressor = self.css_compressor
if compressor:
css = getattr(compressor(verbose=self.verbose), 'compress_css')(css)
if not variant:
return css
elif variant == "datauri":
return self.with_data_uri(css)
else:
raise CompressorError("\"%s\" is not a valid variant" % variant)
def compile_templates(self, paths):
compiled = []
if not paths:
return ''
namespace = settings.TEMPLATE_NAMESPACE
base_path = self.base_path(paths)
for path in paths:
contents = self.read_text(path)
contents = re.sub("\r?\n", "\\\\n", contents)
contents = re.sub("'", "\\'", contents)
name = self.template_name(path, base_path)
compiled.append("%s['%s'] = %s('%s');\n" % (
namespace,
name,
settings.TEMPLATE_FUNC,
contents
))
compiler = TEMPLATE_FUNC if settings.TEMPLATE_FUNC == DEFAULT_TEMPLATE_FUNC else ""
return "\n".join([
"%(namespace)s = %(namespace)s || {};" % {'namespace': namespace},
compiler,
''.join(compiled)
])
def base_path(self, paths):
def names_equal(name):
return all(n == name[0] for n in name[1:])
directory_levels = zip(*[p.split(os.sep) for p in paths])
return os.sep.join(x[0] for x in takewhile(names_equal, directory_levels))
def template_name(self, path, base):
"""Find out the name of a JS template"""
if not base:
path = os.path.basename(path)
if path == base:
base = os.path.dirname(path)
name = re.sub(r"^%s[\/\\]?(.*)%s$" % (
re.escape(base), re.escape(settings.TEMPLATE_EXT)
), r"\1", path)
return re.sub(r"[\/\\]", settings.TEMPLATE_SEPARATOR, name)
def concatenate_and_rewrite(self, paths, output_filename, variant=None):
"""Concatenate together files and rewrite urls"""
stylesheets = []
for path in paths:
def reconstruct(match):
quote = match.group(1) or ''
asset_path = match.group(2)
if NON_REWRITABLE_URL.match(asset_path):
return "url(%s%s%s)" % (quote, asset_path, quote)
asset_url = self.construct_asset_path(asset_path, path,
output_filename, variant)
return "url(%s)" % asset_url
content = self.read_text(path)
# content needs to be unicode to avoid explosions with non-ascii chars
content = re.sub(URL_DETECTOR, reconstruct, content)
stylesheets.append(content)
return '\n'.join(stylesheets)
def concatenate(self, paths):
"""Concatenate together a list of files"""
# Note how a semicolon is added between the two files to make sure that
# their behavior is not changed. '(expression1)\n(expression2)' calls
# `expression1` with `expression2` as an argument! Superfluos semicolons
# are valid in JavaScript and will be removed by the minifier.
return "\n;".join([self.read_text(path) for path in paths])
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).replace('\\', '/'))
if self.embeddable(public_path, variant):
return "__EMBED__%s" % public_path
if not posixpath.isabs(asset_path):
asset_path = self.relative_path(public_path, output_filename)
return asset_path
def embeddable(self, path, variant):
"""Is the asset embeddable ?"""
name, ext = os.path.splitext(path)
font = ext in FONT_EXTS
if not variant:
return False
if not (re.search(settings.EMBED_PATH, path.replace('\\', '/')) and self.storage.exists(path)):
return False
if ext not in EMBED_EXTS:
return False
if not (font or len(self.encoded_content(path)) < settings.EMBED_MAX_IMAGE_SIZE):
return False
return True
def with_data_uri(self, css):
def datauri(match):
path = match.group(1)
mime_type = self.mime_type(path)
data = self.encoded_content(path)
return "url(\"data:%s;charset=utf-8;base64,%s\")" % (mime_type, data)
return re.sub(URL_REPLACER, datauri, css)
def encoded_content(self, path):
"""Return the base64 encoded contents"""
if path in self.__class__.asset_contents:
return self.__class__.asset_contents[path]
data = self.read_bytes(path)
self.__class__.asset_contents[path] = force_text(base64.b64encode(data))
return self.__class__.asset_contents[path]
def mime_type(self, path):
"""Get mime-type from filename"""
name, ext = os.path.splitext(path)
return MIME_TYPES[ext]
def absolute_path(self, path, start):
"""
Return the absolute public path for an asset,
given the path of the stylesheet that contains it.
"""
if posixpath.isabs(path):
path = posixpath.join(staticfiles_storage.location, path)
else:
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 = 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_bytes(self, path):
"""Read file content in binary mode"""
file = staticfiles_storage.open(path)
content = file.read()
file.close()
return content
def read_text(self, path):
content = self.read_bytes(path)
return force_text(content)
class CompressorBase(object):
def __init__(self, verbose):
self.verbose = verbose
def filter_css(self, css):
raise NotImplementedError
def filter_js(self, js):
raise NotImplementedError
class SubProcessCompressor(CompressorBase):
def execute_command(self, command, content):
argument_list = []
for flattening_arg in command:
if isinstance(flattening_arg, string_types):
argument_list.append(flattening_arg)
else:
argument_list.extend(flattening_arg)
pipe = subprocess.Popen(argument_list, stdout=subprocess.PIPE,
stdin=subprocess.PIPE, stderr=subprocess.PIPE)
if content:
content = smart_bytes(content)
stdout, stderr = pipe.communicate(content)
set_std_streams_blocking()
if stderr.strip() and pipe.returncode != 0:
raise CompressorError(stderr)
elif self.verbose:
print(stderr)
return force_text(stdout)
class NoopCompressor(CompressorBase):
def compress_js(self, js):
return js
def compress_css(self, css):
return css
django-pipeline-1.6.14/pipeline/compressors/uglifyjs.py 0000644 0000765 0000024 00000000613 12665220514 023161 0 ustar tim staff 0000000 0000000 from __future__ import unicode_literals
from pipeline.conf import settings
from pipeline.compressors import SubProcessCompressor
class UglifyJSCompressor(SubProcessCompressor):
def compress_js(self, js):
command = (settings.UGLIFYJS_BINARY, settings.UGLIFYJS_ARGUMENTS)
if self.verbose:
command += ' --verbose'
return self.execute_command(command, js)
django-pipeline-1.6.14/pipeline/compressors/yui.py 0000644 0000765 0000024 00000001202 12665220514 022126 0 ustar tim staff 0000000 0000000 from __future__ import unicode_literals
from pipeline.conf import settings
from pipeline.compressors import SubProcessCompressor
class YUICompressor(SubProcessCompressor):
def compress_common(self, content, compress_type, arguments):
command = (
settings.YUI_BINARY,
"--type={}".format(compress_type),
arguments
)
return self.execute_command(command, content)
def compress_js(self, js):
return self.compress_common(js, 'js', settings.YUI_JS_ARGUMENTS)
def compress_css(self, css):
return self.compress_common(css, 'css', settings.YUI_CSS_ARGUMENTS)
django-pipeline-1.6.14/pipeline/compressors/jsmin.py 0000664 0000765 0000024 00000000531 12600126513 022437 0 ustar tim staff 0000000 0000000 from __future__ import absolute_import, unicode_literals
from pipeline.compressors import CompressorBase
class JSMinCompressor(CompressorBase):
"""
JS compressor based on the Python library jsmin
(http://pypi.python.org/pypi/jsmin/).
"""
def compress_js(self, js):
from jsmin import jsmin
return jsmin(js)
django-pipeline-1.6.14/pipeline/compressors/cssmin.py 0000644 0000765 0000024 00000000513 12665220514 022620 0 ustar tim staff 0000000 0000000 from __future__ import unicode_literals
from pipeline.conf import settings
from pipeline.compressors import SubProcessCompressor
class CSSMinCompressor(SubProcessCompressor):
def compress_css(self, css):
command = (settings.CSSMIN_BINARY, settings.CSSMIN_ARGUMENTS)
return self.execute_command(command, css)
django-pipeline-1.6.14/pipeline/compressors/closure.py 0000644 0000765 0000024 00000000513 12665220514 023000 0 ustar tim staff 0000000 0000000 from __future__ import unicode_literals
from pipeline.conf import settings
from pipeline.compressors import SubProcessCompressor
class ClosureCompressor(SubProcessCompressor):
def compress_js(self, js):
command = (settings.CLOSURE_BINARY, settings.CLOSURE_ARGUMENTS)
return self.execute_command(command, js)
django-pipeline-1.6.14/pipeline/utils.py 0000664 0000765 0000024 00000004137 12701105433 020106 0 ustar tim staff 0000000 0000000 from __future__ import unicode_literals
try:
import fcntl
except ImportError:
# windows
fcntl = None
import importlib
import mimetypes
import posixpath
import os
import sys
try:
from urllib.parse import quote
except ImportError:
from urllib import quote
from django.utils.encoding import smart_text
from pipeline.conf import settings
def to_class(class_str):
if not class_str:
return None
module_bits = class_str.split('.')
module_path, class_name = '.'.join(module_bits[:-1]), module_bits[-1]
module = importlib.import_module(module_path)
return getattr(module, class_name, None)
def filepath_to_uri(path):
if path is None:
return path
return quote(smart_text(path).replace("\\", "/"), safe="/~!*()'#?")
def guess_type(path, default=None):
for type, ext in settings.MIMETYPES:
mimetypes.add_type(type, ext)
mimetype, _ = mimetypes.guess_type(path)
if not mimetype:
return default
return smart_text(mimetype)
def relpath(path, start=posixpath.curdir):
"""Return a relative version of a path"""
if not path:
raise ValueError("no path specified")
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(posixpath.commonprefix([start_list, path_list]))
rel_list = [posixpath.pardir] * (len(start_list) - i) + path_list[i:]
if not rel_list:
return posixpath.curdir
return posixpath.join(*rel_list)
def set_std_streams_blocking():
"""
Set stdout and stderr to be blocking.
This is called after Popen.communicate() to revert stdout and stderr back
to be blocking (the default) in the event that the process to which they
were passed manipulated one or both file descriptors to be non-blocking.
"""
if not fcntl:
return
for f in (sys.__stdout__, sys.__stderr__):
fileno = f.fileno()
flags = fcntl.fcntl(fileno, fcntl.F_GETFL)
fcntl.fcntl(fileno, fcntl.F_SETFL, flags & ~os.O_NONBLOCK)
django-pipeline-1.6.14/pipeline/storage.py 0000664 0000765 0000024 00000006331 12627750660 020427 0 ustar tim staff 0000000 0000000 from __future__ import unicode_literals
import gzip
from io import BytesIO
from django.contrib.staticfiles.storage import CachedStaticFilesStorage, StaticFilesStorage
from django.contrib.staticfiles.utils import matches_patterns
from django.core.files.base import File
class PipelineMixin(object):
packing = True
def post_process(self, paths, dry_run=False, **options):
if dry_run:
return
from pipeline.packager import Packager
packager = Packager(storage=self)
for package_name in packager.packages['css']:
package = packager.package_for('css', package_name)
output_file = package.output_filename
if self.packing:
packager.pack_stylesheets(package)
paths[output_file] = (self, output_file)
yield output_file, output_file, True
for package_name in packager.packages['js']:
package = packager.package_for('js', package_name)
output_file = package.output_filename
if self.packing:
packager.pack_javascripts(package)
paths[output_file] = (self, output_file)
yield output_file, output_file, True
super_class = super(PipelineMixin, self)
if hasattr(super_class, 'post_process'):
for name, hashed_name, processed in super_class.post_process(paths.copy(), dry_run, **options):
yield name, hashed_name, processed
def get_available_name(self, name, max_length=None):
if self.exists(name):
self.delete(name)
return name
class GZIPMixin(object):
gzip_patterns = ("*.css", "*.js")
def _compress(self, original_file):
content = BytesIO()
gzip_file = gzip.GzipFile(mode='wb', fileobj=content)
gzip_file.write(original_file.read())
gzip_file.close()
content.seek(0)
return File(content)
def post_process(self, paths, dry_run=False, **options):
super_class = super(GZIPMixin, self)
if hasattr(super_class, 'post_process'):
for name, hashed_name, processed in super_class.post_process(paths.copy(), dry_run, **options):
if hashed_name != name:
paths[hashed_name] = (self, hashed_name)
yield name, hashed_name, processed
if dry_run:
return
for path in paths:
if path:
if not matches_patterns(path, self.gzip_patterns):
continue
original_file = self.open(path)
gzipped_path = "{0}.gz".format(path)
if self.exists(gzipped_path):
self.delete(gzipped_path)
gzipped_file = self._compress(original_file)
gzipped_path = self.save(gzipped_path, gzipped_file)
yield gzipped_path, gzipped_path, True
class NonPackagingMixin(object):
packing = False
class PipelineStorage(PipelineMixin, StaticFilesStorage):
pass
class NonPackagingPipelineStorage(NonPackagingMixin, PipelineStorage):
pass
class PipelineCachedStorage(PipelineMixin, CachedStaticFilesStorage):
pass
class NonPackagingPipelineCachedStorage(NonPackagingMixin, PipelineCachedStorage):
pass
django-pipeline-1.6.14/pipeline/templates/ 0000755 0000765 0000024 00000000000 13231236654 020374 5 ustar tim staff 0000000 0000000 django-pipeline-1.6.14/pipeline/templates/pipeline/ 0000755 0000765 0000024 00000000000 13231236654 022201 5 ustar tim staff 0000000 0000000 django-pipeline-1.6.14/pipeline/templates/pipeline/inline_js.html 0000664 0000765 0000024 00000000173 12600126513 025033 0 ustar tim staff 0000000 0000000