django-pipeline-1.6.14/0000755000076500000240000000000013231236654014571 5ustar timstaff00000000000000django-pipeline-1.6.14/PKG-INFO0000644000076500000240000001543713231236654015700 0ustar timstaff00000000000000Metadata-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/0000755000076500000240000000000013231236654016376 5ustar timstaff00000000000000django-pipeline-1.6.14/pipeline/templatetags/0000755000076500000240000000000013231236654021070 5ustar timstaff00000000000000django-pipeline-1.6.14/pipeline/templatetags/__init__.py0000664000076500000240000000000012600126513023160 0ustar timstaff00000000000000django-pipeline-1.6.14/pipeline/templatetags/pipeline.py0000644000076500000240000001724213226744361023260 0ustar timstaff00000000000000from __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.py0000664000076500000240000000026512600126513020404 0ustar timstaff00000000000000from __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.py0000644000076500000240000000422712665220514020561 0ustar timstaff00000000000000from __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/0000755000076500000240000000000013231236654017553 5ustar timstaff00000000000000django-pipeline-1.6.14/pipeline/jinja2/pipeline/0000755000076500000240000000000013231236654021360 5ustar timstaff00000000000000django-pipeline-1.6.14/pipeline/jinja2/pipeline/js.jinja0000664000076500000240000000020212600126513022774 0ustar timstaff00000000000000 django-pipeline-1.6.14/pipeline/jinja2/pipeline/css.jinja0000664000076500000240000000023512600126513023156 0ustar timstaff00000000000000 django-pipeline-1.6.14/pipeline/jinja2/pipeline/inline_js.jinja0000664000076500000240000000017412600126513024342 0ustar timstaff00000000000000 {{ source|safe }} django-pipeline-1.6.14/pipeline/jinja2/__init__.py0000664000076500000240000000600112701105433021652 0ustar timstaff00000000000000from __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.py0000644000076500000240000000616113226744361020411 0ustar timstaff00000000000000from 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.py0000644000076500000240000000647112761144777017720 0ustar timstaff00000000000000# -*- 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.py0000644000076500000240000001063213226744361020532 0ustar timstaff00000000000000from __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.py0000644000076500000240000000677413036753101020746 0ustar timstaff00000000000000from __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__.py0000664000076500000240000000000012600126513020466 0ustar timstaff00000000000000django-pipeline-1.6.14/pipeline/glob.py0000664000076500000240000000366212701105433017673 0ustar timstaff00000000000000from __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.py0000644000076500000240000002253213226744361020105 0ustar timstaff00000000000000"""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/0000755000076500000240000000000013231236654020755 5ustar timstaff00000000000000django-pipeline-1.6.14/pipeline/compressors/csstidy.py0000644000076500000240000000116212665220514023007 0ustar timstaff00000000000000from __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.py0000664000076500000240000000053712600126513022626 0ustar timstaff00000000000000from __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.py0000644000076500000240000000122212665220514023012 0ustar timstaff00000000000000from __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__.py0000644000076500000240000002343613226744361023101 0ustar timstaff00000000000000from __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.py0000644000076500000240000000061312665220514023161 0ustar timstaff00000000000000from __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.py0000644000076500000240000000120212665220514022126 0ustar timstaff00000000000000from __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.py0000664000076500000240000000053112600126513022437 0ustar timstaff00000000000000from __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.py0000644000076500000240000000051312665220514022620 0ustar timstaff00000000000000from __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.py0000644000076500000240000000051312665220514023000 0ustar timstaff00000000000000from __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.py0000664000076500000240000000413712701105433020106 0ustar timstaff00000000000000from __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.py0000664000076500000240000000633112627750660020427 0ustar timstaff00000000000000from __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/0000755000076500000240000000000013231236654020374 5ustar timstaff00000000000000django-pipeline-1.6.14/pipeline/templates/pipeline/0000755000076500000240000000000013231236654022201 5ustar timstaff00000000000000django-pipeline-1.6.14/pipeline/templates/pipeline/inline_js.html0000664000076500000240000000017312600126513025033 0ustar timstaff00000000000000 {{ source|safe }} django-pipeline-1.6.14/pipeline/templates/pipeline/css.html0000664000076500000240000000032712600126513023652 0ustar timstaff00000000000000django-pipeline-1.6.14/pipeline/templates/pipeline/js.html0000664000076500000240000000020112600126513023465 0ustar timstaff00000000000000django-pipeline-1.6.14/pipeline/templates/pipeline/js.jinja0000664000076500000240000000020212600126513023615 0ustar timstaff00000000000000 django-pipeline-1.6.14/pipeline/templates/pipeline/compile_error.html0000664000076500000240000000200612701105433025717 0ustar timstaff00000000000000 django-pipeline-1.6.14/pipeline/templates/pipeline/css.jinja0000664000076500000240000000023512600126513023777 0ustar timstaff00000000000000 django-pipeline-1.6.14/pipeline/templates/pipeline/inline_js.jinja0000664000076500000240000000017412600126513025163 0ustar timstaff00000000000000 {{ source|safe }} django-pipeline-1.6.14/pipeline/exceptions.py0000664000076500000240000000065312701105433021126 0ustar timstaff00000000000000from __future__ import unicode_literals class PipelineException(Exception): pass class PackageNotFound(PipelineException): pass class CompilerError(PipelineException): def __init__(self, msg, command=None, error_output=None): super(CompilerError, self).__init__(msg) self.command = command self.error_output = error_output.strip() class CompressorError(PipelineException): pass django-pipeline-1.6.14/pipeline/compilers/0000755000076500000240000000000013231236654020373 5ustar timstaff00000000000000django-pipeline-1.6.14/pipeline/compilers/livescript.py0000644000076500000240000000123212665220514023125 0ustar timstaff00000000000000from __future__ import unicode_literals from pipeline.conf import settings from pipeline.compilers import SubProcessCompiler class LiveScriptCompiler(SubProcessCompiler): output_extension = 'js' def match_file(self, path): return path.endswith('.ls') 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 = ( settings.LIVE_SCRIPT_BINARY, "-cp", settings.LIVE_SCRIPT_ARGUMENTS, infile, ) return self.execute_command(command, stdout_captured=outfile) django-pipeline-1.6.14/pipeline/compilers/__init__.py0000644000076500000240000001410213226744361022505 0ustar timstaff00000000000000from __future__ import unicode_literals import os import shutil import subprocess from tempfile import NamedTemporaryFile from django.contrib.staticfiles import finders from django.contrib.staticfiles.storage import staticfiles_storage from django.core.files.base import ContentFile from django.utils.encoding import smart_bytes from django.utils.six import string_types, text_type from pipeline.conf import settings from pipeline.exceptions import CompilerError from pipeline.utils import to_class, set_std_streams_blocking class Compiler(object): def __init__(self, storage=None, verbose=False): if storage is None: storage = staticfiles_storage self.storage = storage self.verbose = verbose @property def compilers(self): return [to_class(compiler) for compiler in settings.COMPILERS] def compile(self, paths, compiler_options={}, force=False): def _compile(input_path): for compiler in self.compilers: compiler = compiler(verbose=self.verbose, storage=self.storage) if compiler.match_file(input_path): try: infile = self.storage.path(input_path) except NotImplementedError: infile = finders.find(input_path) outfile = compiler.output_path(infile, compiler.output_extension) outdated = compiler.is_outdated(infile, outfile) compiler.compile_file(infile, outfile, outdated=outdated, force=force, **compiler_options) return compiler.output_path(input_path, compiler.output_extension) else: return input_path try: import multiprocessing from concurrent import futures except ImportError: return list(map(_compile, paths)) else: with futures.ThreadPoolExecutor(max_workers=multiprocessing.cpu_count()) as executor: return list(executor.map(_compile, paths)) class CompilerBase(object): def __init__(self, verbose, storage): self.verbose = verbose self.storage = storage def match_file(self, filename): raise NotImplementedError 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_bytes(content))) def read_file(self, path): file = self.storage.open(path, 'rb') content = file.read() file.close() return content def output_path(self, path, extension): path = os.path.splitext(path) return '.'.join((path[0], extension)) def is_outdated(self, infile, outfile): if not os.path.exists(outfile): return True try: return os.path.getmtime(infile) > os.path.getmtime(outfile) except OSError: return True class SubProcessCompiler(CompilerBase): def execute_command(self, command, cwd=None, stdout_captured=None): """Execute a command at cwd, saving its normal output at stdout_captured. Errors, defined as nonzero return code or a failure to start execution, will raise a CompilerError exception with a description of the cause. They do not write output. This is file-system safe (any valid file names are allowed, even with spaces or crazy characters) and OS agnostic (existing and future OSes that Python supports should already work). The only thing weird here is that any incoming command arg item may itself be a tuple. This allows compiler implementations to look clean while supporting historical string config settings and maintaining backwards compatibility. Thus, we flatten one layer deep. ((env, foocomp), infile, (-arg,)) -> (env, foocomp, infile, -arg) """ argument_list = [] for flattening_arg in command: if isinstance(flattening_arg, string_types): argument_list.append(flattening_arg) else: argument_list.extend(flattening_arg) # The first element in argument_list is the program that will be executed; if it is '', then # a PermissionError will be raised. Thus empty arguments are filtered out from argument_list argument_list = filter(None, argument_list) stdout = None try: # We always catch stdout in a file, but we may not have a use for it. temp_file_container = cwd or os.path.dirname(stdout_captured or "") or os.getcwd() with NamedTemporaryFile(delete=False, dir=temp_file_container) as stdout: compiling = subprocess.Popen(argument_list, cwd=cwd, stdout=stdout, stderr=subprocess.PIPE) _, stderr = compiling.communicate() set_std_streams_blocking() if compiling.returncode != 0: stdout_captured = None # Don't save erroneous result. raise CompilerError( "{0!r} exit code {1}\n{2}".format(argument_list, compiling.returncode, stderr), command=argument_list, error_output=stderr) # User wants to see everything that happened. if self.verbose: with open(stdout.name) as out: print(out.read()) print(stderr) except OSError as e: stdout_captured = None # Don't save erroneous result. raise CompilerError(e, command=argument_list, error_output=text_type(e)) finally: # Decide what to do with captured stdout. if stdout: if stdout_captured: shutil.move(stdout.name, os.path.join(cwd or os.curdir, stdout_captured)) else: os.remove(stdout.name) django-pipeline-1.6.14/pipeline/compilers/sass.py0000644000076500000240000000112612665220514021714 0ustar timstaff00000000000000from __future__ import unicode_literals from os.path import dirname from pipeline.conf import settings from pipeline.compilers import SubProcessCompiler class SASSCompiler(SubProcessCompiler): output_extension = 'css' def match_file(self, filename): return filename.endswith(('.scss', '.sass')) def compile_file(self, infile, outfile, outdated=False, force=False): command = ( settings.SASS_BINARY, settings.SASS_ARGUMENTS, infile, outfile ) return self.execute_command(command, cwd=dirname(infile)) django-pipeline-1.6.14/pipeline/compilers/es6.py0000644000076500000240000000120212665220514021433 0ustar timstaff00000000000000from __future__ import unicode_literals from pipeline.conf import settings from pipeline.compilers import SubProcessCompiler class ES6Compiler(SubProcessCompiler): output_extension = 'js' def match_file(self, path): return path.endswith('.es6') 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 = ( settings.BABEL_BINARY, settings.BABEL_ARGUMENTS, infile, "-o", outfile ) return self.execute_command(command) django-pipeline-1.6.14/pipeline/compilers/stylus.py0000644000076500000240000000107412665220514022310 0ustar timstaff00000000000000from __future__ import unicode_literals from os.path import dirname from pipeline.conf import settings from pipeline.compilers import SubProcessCompiler class StylusCompiler(SubProcessCompiler): output_extension = 'css' def match_file(self, filename): return filename.endswith('.styl') def compile_file(self, infile, outfile, outdated=False, force=False): command = ( settings.STYLUS_BINARY, settings.STYLUS_ARGUMENTS, infile ) return self.execute_command(command, cwd=dirname(infile)) django-pipeline-1.6.14/pipeline/compilers/coffee.py0000644000076500000240000000130312665220514022167 0ustar timstaff00000000000000from __future__ import unicode_literals from pipeline.conf import settings from pipeline.compilers import SubProcessCompiler class CoffeeScriptCompiler(SubProcessCompiler): output_extension = 'js' def match_file(self, path): return path.endswith('.coffee') or path.endswith('.litcoffee') 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 = ( settings.COFFEE_SCRIPT_BINARY, "-cp", settings.COFFEE_SCRIPT_ARGUMENTS, infile, ) return self.execute_command(command, stdout_captured=outfile) django-pipeline-1.6.14/pipeline/compilers/less.py0000644000076500000240000000123512665220514021712 0ustar timstaff00000000000000from __future__ import unicode_literals from os.path import dirname from pipeline.conf import settings from pipeline.compilers import SubProcessCompiler class LessCompiler(SubProcessCompiler): output_extension = 'css' def match_file(self, filename): return filename.endswith('.less') def compile_file(self, infile, outfile, outdated=False, force=False): # Pipe to file rather than provide outfile arg due to a bug in lessc command = ( settings.LESS_BINARY, settings.LESS_ARGUMENTS, infile, ) return self.execute_command(command, cwd=dirname(infile), stdout_captured=outfile) django-pipeline-1.6.14/pipeline/middleware.py0000644000076500000240000000206013025340020021043 0ustar timstaff00000000000000from __future__ import unicode_literals from django.core.exceptions import MiddlewareNotUsed from django.utils.encoding import DjangoUnicodeDecodeError from django.utils.html import strip_spaces_between_tags as minify_html from pipeline.conf import settings try: # Support for Django 1.10 new MIDDLEWARE setting from django.utils.deprecation import MiddlewareMixin except ImportError: # Django < 1.10 MiddlewareMixin = object class MinifyHTMLMiddleware(MiddlewareMixin): def __init__(self, *args, **kwargs): super(MinifyHTMLMiddleware, self).__init__(*args, **kwargs) if not settings.PIPELINE_ENABLED: raise MiddlewareNotUsed def process_response(self, request, response): if response.has_header('Content-Type') and 'text/html' in response['Content-Type']: try: response.content = minify_html(response.content.strip()) response['Content-Length'] = str(len(response.content)) except DjangoUnicodeDecodeError: pass return response django-pipeline-1.6.14/pipeline/views.py0000664000076500000240000000323312701105433020077 0ustar timstaff00000000000000from __future__ import unicode_literals from django.conf import settings as django_settings from django.core.exceptions import ImproperlyConfigured from django.views.static import serve from .collector import default_collector from .conf import settings def serve_static(request, path, insecure=False, **kwargs): """Collect and serve static files. This view serves up static files, much like Django's :py:func:`~django.views.static.serve` view, with the addition that it collects static files first (if enabled). This allows images, fonts, and other assets to be served up without first loading a page using the ``{% javascript %}`` or ``{% stylesheet %}`` template tags. You can use this view by adding the following to any :file:`urls.py`:: urlpatterns += static('static/', view='pipeline.views.serve_static') """ # Follow the same logic Django uses for determining access to the # static-serving view. if not django_settings.DEBUG and not insecure: raise ImproperlyConfigured("The staticfiles view can only be used in " "debug mode or if the --insecure " "option of 'runserver' is used") if not settings.PIPELINE_ENABLED and settings.PIPELINE_COLLECTOR_ENABLED: # Collect only the requested file, in order to serve the result as # fast as possible. This won't interfere with the template tags in any # way, as those will still cause Django to collect all media. default_collector.collect(request, files=[path]) return serve(request, path, document_root=django_settings.STATIC_ROOT, **kwargs) django-pipeline-1.6.14/LICENSE0000664000076500000240000000220013231235254015565 0ustar timstaff00000000000000Copyright (©) 2008 Andreas Pelme Copyright (©) 2011-2018 Timothée Peignier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.django-pipeline-1.6.14/CONTRIBUTING.rst0000644000076500000240000000200112665220514017221 0ustar timstaff00000000000000.. image:: https://jazzband.co/static/img/jazzband.svg :target: https://jazzband.co/ :alt: Jazzband This is a `Jazzband `_ project. By contributing you agree to abide by the `Contributor Code of Conduct `_ and follow the `guidelines `_. 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/jazzband/django-pipeline/issues django-pipeline-1.6.14/AUTHORS0000644000076500000240000001140513226744361015645 0ustar timstaff00000000000000Pipeline is a fork of django-compress which was originally created by Andreas Pelme in 2008. These people have provided bug fixes, new features, improved the documentation or just made Pipeline more awesome. * Adam Charnock * Alan Lu * Aleksey Porfirov * Alex Gavrișco * Alexander Artemenko * Alexander Pugachev * Alexis Svinartchouk * Allard Stijnman * Alvin Mites * Andreas Cederström * Andrew Choi * Andy Kish * Ara Anjargolian * Arnar Yngvason * Austin Pua * Axel Haustant * Balazs Kossovics * Ben Vinegar * Brad Pitcher * Brant Young * Brawaga * Brian Montgomery * Bryan Chow * Caio Ariede * Camilo Nova * Carl Meyer * Casey Greene * Chad Miller * Chris Applegate * Chris Reeves * Christian Hammond * Christofer Bertonha * Christopher Dilorenzo * Collin Stedman * Corey Farwell * Corrado Primier * Danielle Madeley * David Charbonnier * David Cramer * David Hughes * David Trowbridge * Denis V Seleznyov * DJ Sharkey * Edwin Lunando * Eric Hamiter * Evan Myller * Fabian Büchler * Feanil Patel * Florent Messa * Frankie Dintino * Hannes Ljungberg * Idan Zalzberg * Jacob Haslehurst * James Keys * Jannis Leidel * Jared Scott * Jaromir Fojtu * Jeff Held * John Whitlock (@jwhitlock) * Jon Dufresne * Josh Braegger * Joshua Kehn * Julien Hartmann * Kristian Glass * Kyle MacFarlane * Leonardo Orozco * Luke Yu-Po Chen * Mark Sandstrom * Matt Dennewitz * Matthieu Gallet * Max Klymyshyn * Melvin Laplanche * Michael Weibel * Michał Górny * Miguel Araujo Perez * Mike Gilbert * Miroslav Shubernetskiy * Natal Ngetal * Nathan Cox * Nathan Shafer * Patrick Altman * Peter Baumgartner * Philipp Wollermann * Pierre Drescher * Rajiv Bose * Rami Chowdhury * Remco Wendt * Remy Sanchez * Sam Thomson * Sander Smits * Sander Steffann * Sassan Haradji (@sassanh) * Sayed Raianul Kabir * Simon Lydell * Sirex * Sławek Ehlert * Stefano Brentegani * Stephan Wienczny * Steven Cummings * Tadas Dailyda * Teo Klestrup Röijezon * Thomas Parslow * Tiago Espinha * Timothée Peignier * Tom Yam * Tomek Paczkowski * Trey Smith * Vadym S. Khondar (@vskh) * Venelin Stoykov * Victor Shnayder * Wictor Olseryd * Wismill * Zachary Kazanski * Zenobius Jiricek django-pipeline-1.6.14/tests/0000755000076500000240000000000013231236654015733 5ustar timstaff00000000000000django-pipeline-1.6.14/tests/.DS_Store0000644000076500000240000001400413061427105017407 0ustar timstaff00000000000000Bud1 cache_ __pycache__lg1ScompR __pycache__moDDdutcҬ __pycache__modDdutcҬ __pycache__ph1Scomp0assetslg1Scomp5assetsmoDDdutc*;assetsmodDdutc*;assetsph1Scomp@staticlg1Scomp:staticmoDDdutcҮystaticmodDdutcҮystaticph1Scomp templateslg1Scomp templatesmoDDdutc&] templatesmodDdutc&] templatesph1Scomp testslg1ScomptestsmoDDdutcԡtestsmodDdutcԡtestsph1Scompp  @ @ @ @ E DSDB ` @ @ @django-pipeline-1.6.14/tests/models.py0000664000076500000240000000000012600126513017547 0ustar timstaff00000000000000django-pipeline-1.6.14/tests/tests/0000755000076500000240000000000013231236654017075 5ustar timstaff00000000000000django-pipeline-1.6.14/tests/tests/test_forms.py0000644000076500000240000001452213226744361021643 0ustar timstaff00000000000000from __future__ import unicode_literals from django.forms import Media from django.test import TestCase from django.utils import six from pipeline.forms import PipelineFormMedia from ..utils import pipeline_settings @pipeline_settings( PIPELINE_COLLECTOR_ENABLED=False, STYLESHEETS={ 'styles1': { 'source_filenames': ( 'pipeline/css/first.css', 'pipeline/css/second.css', ), 'output_filename': 'styles1.min.css', }, 'styles2': { 'source_filenames': ( 'pipeline/css/unicode.css', ), 'output_filename': 'styles2.min.css', }, 'print': { 'source_filenames': ( 'pipeline/css/urls.css', ), 'output_filename': 'print.min.css', }, }, JAVASCRIPT={ 'scripts1': { 'source_filenames': ( 'pipeline/js/first.js', 'pipeline/js/second.js', ), 'output_filename': 'scripts1.min.js', }, 'scripts2': { 'source_filenames': ( 'pipeline/js/application.js', ), 'output_filename': 'scripts2.min.js', }, }) class PipelineFormMediaTests(TestCase): """Unit tests for pipeline.forms.PipelineFormMedia.""" @pipeline_settings(PIPELINE_ENABLED=True) def test_css_packages_with_pipeline_enabled(self): """Testing PipelineFormMedia.css_packages with PIPELINE_ENABLED=True""" class MyMedia(PipelineFormMedia): css_packages = { 'all': ('styles1', 'styles2'), 'print': ('print',), } css = { 'all': ('extra1.css', 'extra2.css') } media = Media(MyMedia) self.assertEqual( MyMedia.css, { 'all': [ 'extra1.css', 'extra2.css', '/static/styles1.min.css', '/static/styles2.min.css', ], 'print': ['/static/print.min.css'], }) self.assertEqual(MyMedia.css, media._css) self.assertEqual( list(media.render_css()), [ '' % path for path in ( '/static/extra1.css', '/static/extra2.css', '/static/styles1.min.css', '/static/styles2.min.css', ) ] + [ '' ]) @pipeline_settings(PIPELINE_ENABLED=False) def test_css_packages_with_pipeline_disabled(self): """Testing PipelineFormMedia.css_packages with PIPELINE_ENABLED=False""" class MyMedia(PipelineFormMedia): css_packages = { 'all': ('styles1', 'styles2'), 'print': ('print',), } css = { 'all': ('extra1.css', 'extra2.css') } media = Media(MyMedia) self.assertEqual( MyMedia.css, { 'all': [ 'extra1.css', 'extra2.css', 'pipeline/css/first.css', 'pipeline/css/second.css', 'pipeline/css/unicode.css', ], 'print': ['pipeline/css/urls.css'], }) self.assertEqual(MyMedia.css, media._css) self.assertEqual( list(media.render_css()), [ '' % path for path in ( '/static/extra1.css', '/static/extra2.css', '/static/pipeline/css/first.css', '/static/pipeline/css/second.css', '/static/pipeline/css/unicode.css', ) ] + [ '' ]) @pipeline_settings(PIPELINE_ENABLED=True) def test_js_packages_with_pipeline_enabled(self): """Testing PipelineFormMedia.js_packages with PIPELINE_ENABLED=True""" class MyMedia(PipelineFormMedia): js_packages = ('scripts1', 'scripts2') js = ('extra1.js', 'extra2.js') media = Media(MyMedia) self.assertEqual( MyMedia.js, [ 'extra1.js', 'extra2.js', '/static/scripts1.min.js', '/static/scripts2.min.js', ]) self.assertEqual(MyMedia.js, media._js) self.assertEqual( media.render_js(), [ '' % path for path in ( '/static/extra1.js', '/static/extra2.js', '/static/scripts1.min.js', '/static/scripts2.min.js', ) ]) @pipeline_settings(PIPELINE_ENABLED=False) def test_js_packages_with_pipeline_disabled(self): """Testing PipelineFormMedia.js_packages with PIPELINE_ENABLED=False""" class MyMedia(PipelineFormMedia): js_packages = ('scripts1', 'scripts2') js = ('extra1.js', 'extra2.js') media = Media(MyMedia) self.assertEqual( MyMedia.js, [ 'extra1.js', 'extra2.js', 'pipeline/js/first.js', 'pipeline/js/second.js', 'pipeline/js/application.js', ]) self.assertEqual(MyMedia.js, media._js) self.assertEqual( media.render_js(), [ '' % path for path in ( '/static/extra1.js', '/static/extra2.js', '/static/pipeline/js/first.js', '/static/pipeline/js/second.js', '/static/pipeline/js/application.js', ) ]) django-pipeline-1.6.14/tests/tests/test_packager.py0000664000076500000240000000254412600126513022261 0ustar timstaff00000000000000from __future__ import unicode_literals from django.test import TestCase from pipeline.collector import default_collector from pipeline.packager import Packager, PackageNotFound from tests.utils import _ class PackagerTest(TestCase): def setUp(self): default_collector.collect() def test_package_for(self): packager = Packager() packager.packages['js'] = packager.create_packages({ 'application': { 'source_filenames': ( _('pipeline/js/application.js'), ), 'output_filename': 'application.js' } }) try: packager.package_for('js', 'application') except PackageNotFound: self.fail() try: packager.package_for('js', 'broken') self.fail() except PackageNotFound: pass def test_templates(self): packager = Packager() packages = packager.create_packages({ 'templates': { 'source_filenames': ( _('pipeline/templates/photo/list.jst'), ), 'output_filename': 'templates.js', } }) self.assertEqual(packages['templates'].templates, [_('pipeline/templates/photo/list.jst')]) def tearDown(self): default_collector.clear() django-pipeline-1.6.14/tests/tests/test_utils.py0000664000076500000240000000063412600126513021642 0ustar timstaff00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.test import TestCase from pipeline.utils import guess_type class UtilTest(TestCase): def test_guess_type(self): self.assertEqual('text/css', guess_type('stylesheet.css')) self.assertEqual('text/coffeescript', guess_type('application.coffee')) self.assertEqual('text/less', guess_type('stylesheet.less')) django-pipeline-1.6.14/tests/tests/test_compressor.py0000644000076500000240000003406213226744361022712 0ustar timstaff00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals import base64 import io import os import sys try: from mock import patch except ImportError: from unittest.mock import patch # noqa from unittest import skipIf, skipUnless from django.conf import settings from django.test import TestCase from django.test.client import RequestFactory from django.utils.encoding import smart_bytes from pipeline.compressors import ( Compressor, TEMPLATE_FUNC, SubProcessCompressor) from pipeline.compressors.yuglify import YuglifyCompressor from pipeline.collector import default_collector from tests.utils import _, pipeline_settings @pipeline_settings( CSS_COMPRESSOR='pipeline.compressors.yuglify.YuglifyCompressor', JS_COMPRESSOR='pipeline.compressors.yuglify.YuglifyCompressor') class CompressorTest(TestCase): def setUp(self): self.maxDiff = None self.compressor = Compressor() default_collector.collect() def test_js_compressor_class(self): self.assertEqual(self.compressor.js_compressor, YuglifyCompressor) def test_css_compressor_class(self): 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.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.assertEqual("""(function() {\n window.concat = function() {\n console.log(arguments);\n }\n}()) // No semicolon\n\n;(function() {\n window.cat = function() {\n console.log("hello world");\n }\n}());\n""", js) @patch.object(base64, 'b64encode') def test_encoded_content(self, mock): self.compressor.asset_contents.clear() self.compressor.encoded_content(_('pipeline/images/arrow.png')) self.assertTrue(mock.called) mock.reset_mock() self.compressor.encoded_content(_('pipeline/images/arrow.png')) self.assertFalse(mock.called) def test_encoded_content_output(self): self.compressor.asset_contents.clear() encoded = self.compressor.encoded_content(_('pipeline/images/arrow.png')) expected = ('iVBORw0KGgoAAAANSUhEUgAAAAkAAAAGCAYAAAARx7TFAAAAMk' 'lEQVR42oXKwQkAMAxC0Q7rEk5voSEepCHC9/SOpLV3JPULgArV' 'RtDIMEEiQ4NECRNdciCfK3K3wvEAAAAASUVORK5CYII=') self.assertEqual(encoded, expected) def test_relative_path(self): relative_path = self.compressor.relative_path("images/sprite.png", 'css/screen.css') 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.assertEqual(base_path, _('js/templates')) def test_absolute_path(self): absolute_path = self.compressor.absolute_path( '../../images/sprite.png', 'css/plugins/') self.assertEqual(absolute_path, 'images/sprite.png') absolute_path = self.compressor.absolute_path( '/images/sprite.png', 'css/plugins/') self.assertEqual(absolute_path, '/images/sprite.png') def test_template_name(self): name = self.compressor.template_name( 'templates/photo/detail.jst', 'templates/') self.assertEqual(name, 'photo_detail') name = self.compressor.template_name('templates/photo_edit.jst', '') self.assertEqual(name, 'photo_edit') name = self.compressor.template_name( 'templates\photo\detail.jst', 'templates\\') self.assertEqual(name, 'photo_detail') @pipeline_settings(TEMPLATE_SEPARATOR='/') def test_template_name_separator(self): name = self.compressor.template_name( 'templates/photo/detail.jst', 'templates/') self.assertEqual(name, 'photo/detail') name = self.compressor.template_name('templates/photo_edit.jst', '') self.assertEqual(name, 'photo_edit') name = self.compressor.template_name( 'templates\photo\detail.jst', 'templates\\') self.assertEqual(name, 'photo/detail') def test_compile_templates(self): templates = self.compressor.compile_templates([_('pipeline/templates/photo/list.jst')]) self.assertEqual(templates, """window.JST = window.JST || {};\n%s\nwindow.JST[\'list\'] = template(\'
\\n \\n
\\n <%%= caption %%>\\n
\\n
\');\n""" % TEMPLATE_FUNC) templates = self.compressor.compile_templates([ _('pipeline/templates/video/detail.jst'), _('pipeline/templates/photo/detail.jst') ]) self.assertEqual(templates, """window.JST = window.JST || {};\n%s\nwindow.JST[\'video_detail\'] = template(\'
\\n
\');\nwindow.JST[\'photo_detail\'] = template(\'
\\n \\n
\\n <%%= caption %%> by <%%= author %%>\\n
\\n
\');\n""" % TEMPLATE_FUNC) def test_embeddable(self): 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", "css/plugins/gallery.css", "css/gallery.css") 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.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.assertEqual(""".embedded-url-svg { background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 8h24M4 16h24M4 24h24'/%3E% 3C/svg%3E"); } @font-face { font-family: 'Pipeline'; 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(../pipeline/images/sprite-buttons.png); } .relative-url-querystring { background-image: url(../pipeline/images/sprite-buttons.png?v=1.0#foo=bar); } .absolute-url { background-image: url(/images/sprite-buttons.png); } .absolute-full-url { background-image: url(http://localhost/images/sprite-buttons.png); } .no-protocol-url { background-image: url(//images/sprite-buttons.png); } .anchor-tag-url { background-image: url(#image-gradient); } @font-face{src:url(../pipeline/fonts/pipeline.eot);src:url(../pipeline/fonts/pipeline.eot?#iefix) format('embedded-opentype'),url(../pipeline/fonts/pipeline.woff) format('woff'),url(../pipeline/fonts/pipeline.ttf) format('truetype');} """, output) def test_url_rewrite_data_uri(self): output = self.compressor.concatenate_and_rewrite([ _('pipeline/css/nested/nested.css'), ], 'pipeline/screen.css') self.assertEqual(""".data-url { background-image: url(data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2212px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2012%2014%22%20style%3D%22enable-background%3Anew%200%200%2012%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M11%2C6V5c0-2.762-2.239-5-5-5S1%2C2.238%2C1%2C5v1H0v8h12V6H11z%20M6.5%2C9.847V12h-1V9.847C5.207%2C9.673%2C5%2C9.366%2C5%2C9%20c0-0.553%2C0.448-1%2C1-1s1%2C0.447%2C1%2C1C7%2C9.366%2C6.793%2C9.673%2C6.5%2C9.847z%20M9%2C6H3V5c0-1.657%2C1.343-3%2C3-3s3%2C1.343%2C3%2C3V6z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E); } .data-url-quoted { background-image: url('data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2212px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2012%2014%22%20style%3D%22enable-background%3Anew%200%200%2012%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M11%2C6V5c0-2.762-2.239-5-5-5S1%2C2.238%2C1%2C5v1H0v8h12V6H11z%20M6.5%2C9.847V12h-1V9.847C5.207%2C9.673%2C5%2C9.366%2C5%2C9%20c0-0.553%2C0.448-1%2C1-1s1%2C0.447%2C1%2C1C7%2C9.366%2C6.793%2C9.673%2C6.5%2C9.847z%20M9%2C6H3V5c0-1.657%2C1.343-3%2C3-3s3%2C1.343%2C3%2C3V6z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E'); } """, output) @skipIf(sys.platform.startswith("win"), "requires posix platform") def test_compressor_subprocess_unicode(self): path = os.path.dirname(os.path.dirname(__file__)) content = io.open(path + '/assets/css/unicode.css', encoding="utf-8").read() output = SubProcessCompressor(False).execute_command(('cat',), content) self.assertEqual(""".some_class { // Some unicode content: "áéíóú"; } """, output) def tearDown(self): default_collector.clear() class CompressorImplementationTest(TestCase): maxDiff = None def setUp(self): self.compressor = Compressor() default_collector.collect(RequestFactory().get('/')) def tearDown(self): default_collector.clear() def _test_compressor(self, compressor_cls, compress_type, expected_file): override_settings = { ("%s_COMPRESSOR" % compress_type.upper()): compressor_cls, } with pipeline_settings(**override_settings): if compress_type == 'js': result = self.compressor.compress_js( [_('pipeline/js/first.js'), _('pipeline/js/second.js')]) else: result = self.compressor.compress_css( [_('pipeline/css/first.css'), _('pipeline/css/second.css')], os.path.join('pipeline', 'css', os.path.basename(expected_file))) with self.compressor.storage.open(expected_file) as f: expected = f.read() self.assertEqual(smart_bytes(result), expected) def test_jsmin(self): self._test_compressor('pipeline.compressors.jsmin.JSMinCompressor', 'js', 'pipeline/compressors/jsmin.js') def test_slimit(self): self._test_compressor('pipeline.compressors.slimit.SlimItCompressor', 'js', 'pipeline/compressors/slimit.js') @skipUnless(settings.HAS_NODE, "requires node") def test_uglifyjs(self): self._test_compressor('pipeline.compressors.uglifyjs.UglifyJSCompressor', 'js', 'pipeline/compressors/uglifyjs.js') @skipUnless(settings.HAS_NODE, "requires node") def test_yuglify_js(self): self._test_compressor('pipeline.compressors.yuglify.YuglifyCompressor', 'js', 'pipeline/compressors/yuglify.js') @skipUnless(settings.HAS_NODE, "requires node") def test_yuglify_css(self): self._test_compressor('pipeline.compressors.yuglify.YuglifyCompressor', 'css', 'pipeline/compressors/yuglify.css') @skipUnless(settings.HAS_NODE, "requires node") def test_cssmin(self): self._test_compressor('pipeline.compressors.cssmin.CSSMinCompressor', 'css', 'pipeline/compressors/cssmin.css') @skipUnless(settings.HAS_NODE, "requires node") @skipUnless(settings.HAS_JAVA, "requires java") def test_closure(self): self._test_compressor('pipeline.compressors.closure.ClosureCompressor', 'js', 'pipeline/compressors/closure.js') @skipUnless(settings.HAS_NODE, "requires node") @skipUnless(settings.HAS_JAVA, "requires java") def test_yui_js(self): self._test_compressor('pipeline.compressors.yui.YUICompressor', 'js', 'pipeline/compressors/yui.js') @skipUnless(settings.HAS_NODE, "requires node") @skipUnless(settings.HAS_JAVA, "requires java") def test_yui_css(self): self._test_compressor('pipeline.compressors.yui.YUICompressor', 'css', 'pipeline/compressors/yui.css') @skipUnless(settings.HAS_CSSTIDY, "requires csstidy") def test_csstidy(self): self._test_compressor('pipeline.compressors.csstidy.CSSTidyCompressor', 'css', 'pipeline/compressors/csstidy.css') django-pipeline-1.6.14/tests/tests/test_middleware.py0000664000076500000240000000224712600126513022621 0ustar timstaff00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.test import TestCase from django.http import HttpRequest, HttpResponse from pipeline.middleware import MinifyHTMLMiddleware class MiddlewareTest(TestCase): whitespace = b' ' def setUp(self): self.req = HttpRequest() self.req.META = { 'SERVER_NAME': 'testserver', 'SERVER_PORT': 80, } self.req.path = self.req.path_info = "/" self.resp = HttpResponse() self.resp.status_code = 200 self.resp.content = self.whitespace def test_middleware_html(self): self.resp['Content-Type'] = 'text/html; charset=UTF-8' response = MinifyHTMLMiddleware().process_response(self.req, self.resp) self.assertIn('text/html', response['Content-Type']) self.assertNotIn(self.whitespace, response.content) def test_middleware_text(self): self.resp['Content-Type'] = 'text/plain; charset=UTF-8' response = MinifyHTMLMiddleware().process_response(self.req, self.resp) self.assertIn('text/plain', response['Content-Type']) self.assertIn(self.whitespace, response.content) django-pipeline-1.6.14/tests/tests/models.py0000664000076500000240000000000012600126513020711 0ustar timstaff00000000000000django-pipeline-1.6.14/tests/tests/test_compiler.py0000644000076500000240000002200612761712006022315 0ustar timstaff00000000000000from __future__ import unicode_literals import sys from unittest import skipIf, skipUnless from django.conf import settings from django.contrib.staticfiles.storage import staticfiles_storage from django.test import TestCase from django.test.client import RequestFactory from django.utils.encoding import smart_bytes from pipeline.collector import default_collector from pipeline.compilers import Compiler, CompilerBase, SubProcessCompiler from pipeline.exceptions import CompilerError from pipeline.utils import to_class from tests.utils import _, pipeline_settings class FailingCompiler(SubProcessCompiler): output_extension = 'junk' def match_file(self, path): return path.endswith('.coffee') def compile_file(self, infile, outfile, outdated=False, force=False): command = (("/usr/bin/env", "false",),) return self.execute_command(command) class InvalidCompiler(SubProcessCompiler): output_extension = 'junk' def match_file(self, path): return path.endswith('.coffee') def compile_file(self, infile, outfile, outdated=False, force=False): command = ( ("this-exists-nowhere-as-a-command-and-should-fail",), infile, outfile ) return self.execute_command(command) class CompilerWithEmptyFirstArg(SubProcessCompiler): output_extension = 'junk' def match_file(self, path): return path.endswith('.coffee') def compile_file(self, infile, outfile, outdated=False, force=False): command = ( ('', '/usr/bin/env', 'cat'), infile, ) return self.execute_command(command, stdout_captured=outfile) class CopyingCompiler(SubProcessCompiler): output_extension = 'junk' def match_file(self, path): return path.endswith('.coffee') def compile_file(self, infile, outfile, outdated=False, force=False): command = ( "cp", infile, outfile ) return self.execute_command(command) class LineNumberingCompiler(SubProcessCompiler): output_extension = 'junk' def match_file(self, path): return path.endswith('.coffee') def compile_file(self, infile, outfile, outdated=False, force=False): command = (("/usr/bin/env", "cat"), ("-n",), infile,) return self.execute_command(command, stdout_captured=outfile) class DummyCompiler(CompilerBase): output_extension = 'js' def match_file(self, path): return path.endswith('.coffee') def compile_file(self, infile, outfile, outdated=False, force=False): return @pipeline_settings(COMPILERS=['tests.tests.test_compiler.DummyCompiler']) class DummyCompilerTest(TestCase): def setUp(self): default_collector.collect() self.compiler = Compiler() def test_output_path(self): compiler_class = self.compiler.compilers[0] compiler = compiler_class(verbose=self.compiler.verbose, storage=self.compiler.storage) output_path = compiler.output_path("js/helpers.coffee", "js") self.assertEqual(output_path, "js/helpers.js") def test_compilers_class(self): compilers_class = self.compiler.compilers self.assertEqual(compilers_class[0], DummyCompiler) def test_compile(self): paths = self.compiler.compile([ _('pipeline/js/dummy.coffee'), _('pipeline/js/application.js'), ]) self.assertEqual([_('pipeline/js/dummy.js'), _('pipeline/js/application.js')], list(paths)) def tearDown(self): default_collector.clear() @skipIf(sys.platform.startswith("win"), "requires posix platform") @pipeline_settings(COMPILERS=['tests.tests.test_compiler.LineNumberingCompiler']) class CompilerStdoutTest(TestCase): def setUp(self): default_collector.collect() self.compiler = Compiler() def test_output_path(self): compiler_class = self.compiler.compilers[0] compiler = compiler_class(verbose=self.compiler.verbose, storage=self.compiler.storage) output_path = compiler.output_path("js/helpers.coffee", "js") self.assertEqual(output_path, "js/helpers.js") def test_compile(self): paths = self.compiler.compile([_('pipeline/js/dummy.coffee')]) self.assertEqual([_('pipeline/js/dummy.junk')], list(paths)) def tearDown(self): default_collector.clear() @skipIf(sys.platform.startswith("win"), "requires posix platform") @pipeline_settings(COMPILERS=['tests.tests.test_compiler.CopyingCompiler']) class CompilerSelfWriterTest(TestCase): def setUp(self): default_collector.collect() self.compiler = Compiler() def test_output_path(self): compiler_class = self.compiler.compilers[0] compiler = compiler_class(verbose=self.compiler.verbose, storage=self.compiler.storage) output_path = compiler.output_path("js/helpers.coffee", "js") self.assertEqual(output_path, "js/helpers.js") def test_compile(self): paths = self.compiler.compile([_('pipeline/js/dummy.coffee')]) default_collector.collect() self.assertEqual([_('pipeline/js/dummy.junk')], list(paths)) def tearDown(self): default_collector.clear() @pipeline_settings(COMPILERS=['tests.tests.test_compiler.CompilerWithEmptyFirstArg']) class CompilerWithEmptyFirstArgTest(TestCase): def setUp(self): default_collector.collect() self.compiler = Compiler() def test_compile(self): paths = self.compiler.compile([_('pipeline/js/dummy.coffee')]) default_collector.collect() self.assertEqual([_('pipeline/js/dummy.junk')], list(paths)) def tearDown(self): default_collector.clear() @pipeline_settings(COMPILERS=['tests.tests.test_compiler.InvalidCompiler']) class InvalidCompilerTest(TestCase): def setUp(self): default_collector.collect() self.compiler = Compiler() def test_compile(self): with self.assertRaises(CompilerError) as cm: self.compiler.compile([_('pipeline/js/dummy.coffee')]) e = cm.exception self.assertEqual( e.command, ['this-exists-nowhere-as-a-command-and-should-fail', 'pipeline/js/dummy.coffee', 'pipeline/js/dummy.junk']) self.assertEqual(e.error_output, '') def tearDown(self): default_collector.clear() @skipIf(sys.platform.startswith("win"), "requires posix platform") @pipeline_settings(COMPILERS=['tests.tests.test_compiler.FailingCompiler']) class FailingCompilerTest(TestCase): def setUp(self): default_collector.collect() self.compiler = Compiler() def test_compile(self): with self.assertRaises(CompilerError) as cm: self.compiler.compile([_('pipeline/js/dummy.coffee')]) e = cm.exception self.assertEqual(e.command, ['/usr/bin/env', 'false']) self.assertEqual(e.error_output, '') def tearDown(self): default_collector.clear() @skipUnless(settings.HAS_NODE, "requires node") class CompilerImplementation(TestCase): def setUp(self): self.compiler = Compiler() default_collector.collect(RequestFactory().get('/')) def tearDown(self): default_collector.clear() def _test_compiler(self, compiler_cls_str, infile, expected): compiler_cls = to_class(compiler_cls_str) compiler = compiler_cls(verbose=False, storage=staticfiles_storage) infile_path = staticfiles_storage.path(infile) outfile_path = compiler.output_path(infile_path, compiler.output_extension) compiler.compile_file(_(infile_path), _(outfile_path), force=True) with open(outfile_path) as f: result = f.read() with staticfiles_storage.open(expected) as f: expected = f.read() self.assertEqual(smart_bytes(result), expected) def test_sass(self): self._test_compiler('pipeline.compilers.sass.SASSCompiler', 'pipeline/compilers/scss/input.scss', 'pipeline/compilers/scss/expected.css') def test_coffeescript(self): self._test_compiler('pipeline.compilers.coffee.CoffeeScriptCompiler', 'pipeline/compilers/coffee/input.coffee', 'pipeline/compilers/coffee/expected.js') def test_less(self): self._test_compiler('pipeline.compilers.less.LessCompiler', 'pipeline/compilers/less/input.less', 'pipeline/compilers/less/expected.css') def test_es6(self): self._test_compiler('pipeline.compilers.es6.ES6Compiler', 'pipeline/compilers/es6/input.es6', 'pipeline/compilers/es6/expected.js') def test_stylus(self): self._test_compiler('pipeline.compilers.stylus.StylusCompiler', 'pipeline/compilers/stylus/input.styl', 'pipeline/compilers/stylus/expected.css') def test_livescript(self): self._test_compiler('pipeline.compilers.livescript.LiveScriptCompiler', 'pipeline/compilers/livescript/input.ls', 'pipeline/compilers/livescript/expected.js') django-pipeline-1.6.14/tests/tests/test_conf.py0000664000076500000240000000306212701105433021425 0ustar timstaff00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals import sys from unittest import skipIf, skipUnless from django.test import TestCase from pipeline.conf import PipelineSettings class TestSettings(TestCase): def test_3unicode(self): s = PipelineSettings({"FOO_BINARY": "env actualprogram"}) self.assertEqual(s.FOO_BINARY, ('env', 'actualprogram')) def test_2unicode(self): s = PipelineSettings({"FOO_BINARY": u"env actualprogram"}) self.assertEqual(s.FOO_BINARY, ('env', 'actualprogram')) def test_2bytes(self): s = PipelineSettings({"FOO_BINARY": "env actualprogram"}) self.assertEqual(s.FOO_BINARY, ('env', 'actualprogram')) def test_expected_splitting(self): s = PipelineSettings({"FOO_BINARY": "env actualprogram"}) self.assertEqual(s.FOO_BINARY, ('env', 'actualprogram')) @skipIf(sys.platform.startswith("win"), "requires posix platform") def test_expected_preservation(self): s = PipelineSettings({"FOO_BINARY": r"actual\ program"}) self.assertEqual(s.FOO_BINARY, ('actual program',)) @skipUnless(sys.platform.startswith("win"), "requires windows") def test_win_path_preservation(self): s = PipelineSettings({"FOO_BINARY": "C:\\Test\\ActualProgram.exe argument"}) self.assertEqual(s.FOO_BINARY, ('C:\\Test\\ActualProgram.exe', 'argument')) def test_tuples_are_normal(self): s = PipelineSettings({"FOO_ARGUMENTS": ("explicit", "with", "args")}) self.assertEqual(s.FOO_ARGUMENTS, ('explicit', 'with', 'args')) django-pipeline-1.6.14/tests/tests/__init__.py0000664000076500000240000000066412701105433021205 0ustar timstaff00000000000000# -*- coding: utf-8 flake8: noqa -*- import os import sys if sys.platform.startswith('win'): os.environ.setdefault('NUMBER_OF_PROCESSORS', '1') from .test_collector import * from .test_compiler import * from .test_compressor import * from .test_template import * from .test_glob import * from .test_middleware import * from .test_packager import * from .test_storage import * from .test_utils import * from .test_views import * django-pipeline-1.6.14/tests/tests/test_views.py0000664000076500000240000000612012701105433021633 0ustar timstaff00000000000000from __future__ import unicode_literals from django.contrib.staticfiles.storage import staticfiles_storage from django.core.exceptions import ImproperlyConfigured from django.http import Http404 from django.test import RequestFactory, TestCase from django.test.utils import override_settings from pipeline.collector import default_collector from pipeline.views import serve_static from tests.utils import pipeline_settings @override_settings(DEBUG=True) @pipeline_settings(PIPELINE_COLLECTOR_ENABLED=True, PIPELINE_ENABLED=False) class ServeStaticViewsTest(TestCase): def setUp(self): super(ServeStaticViewsTest, self).setUp() self.filename = 'pipeline/js/first.js' self.storage = staticfiles_storage self.request = RequestFactory().get('/static/%s' % self.filename) default_collector.clear() def tearDown(self): super(ServeStaticViewsTest, self).tearDown() default_collector.clear() staticfiles_storage._setup() def test_found(self): self._test_found() def test_not_found(self): self._test_not_found('missing-file') @override_settings(DEBUG=False) def test_debug_false(self): with self.assertRaises(ImproperlyConfigured): serve_static(self.request, self.filename) self.assertFalse(self.storage.exists(self.filename)) @override_settings(DEBUG=False) def test_debug_false_and_insecure(self): self._test_found(insecure=True) @pipeline_settings(PIPELINE_ENABLED=True) def test_pipeline_enabled_and_found(self): self._write_content() self._test_found() @pipeline_settings(PIPELINE_ENABLED=True) def test_pipeline_enabled_and_not_found(self): self._test_not_found(self.filename) @pipeline_settings(PIPELINE_COLLECTOR_ENABLED=False) def test_collector_disabled_and_found(self): self._write_content() self._test_found() @pipeline_settings(PIPELINE_COLLECTOR_ENABLED=False) def test_collector_disabled_and_not_found(self): self._test_not_found(self.filename) def _write_content(self, content='abc123'): """Write sample content to the test static file.""" with self.storage.open(self.filename, 'w') as f: f.write(content) def _test_found(self, **kwargs): """Test that a file can be found and contains the correct content.""" response = serve_static(self.request, self.filename, **kwargs) self.assertEqual(response.status_code, 200) self.assertTrue(self.storage.exists(self.filename)) if hasattr(response, 'streaming_content'): content = b''.join(response.streaming_content) else: content = response.content with self.storage.open(self.filename) as f: self.assertEqual(f.read(), content) def _test_not_found(self, filename): """Test that a file could not be found.""" self.assertFalse(self.storage.exists(filename)) with self.assertRaises(Http404): serve_static(self.request, filename) self.assertFalse(self.storage.exists(filename)) django-pipeline-1.6.14/tests/tests/test_storage.py0000644000076500000240000000703113226744361022156 0ustar timstaff00000000000000from __future__ import unicode_literals from django.contrib.staticfiles import finders from django.contrib.staticfiles.storage import staticfiles_storage from django.core.management import call_command from django.test import TestCase from django.test.utils import override_settings try: from django.test.utils import modify_settings except ImportError: # Django < 1.7 from tests.utils import modify_settings from pipeline.collector import default_collector from pipeline.storage import PipelineStorage from tests.tests.test_compiler import DummyCompiler from tests.utils import pipeline_settings try: from io import StringIO except ImportError: from StringIO import StringIO class PipelineNoPathStorage(PipelineStorage): """Storage without an implemented path method""" def path(self, *args): raise NotImplementedError() def delete(self, *args): return def exists(self, *args): return True def save(self, *args): return def open(self, *args): return StringIO() def listdir(self, *args): return [] class DummyCSSCompiler(DummyCompiler): """ Handles css files """ output_extension = 'css' def match_file(self, path): return path.endswith('.css') class StorageTest(TestCase): def tearDown(self): staticfiles_storage._setup() @pipeline_settings(JS_COMPRESSOR=None, CSS_COMPRESSOR=None) def test_post_process_dry_run(self): default_collector.collect() processed_files = PipelineStorage().post_process({}, True) self.assertEqual(list(processed_files), []) @pipeline_settings(JS_COMPRESSOR=None, CSS_COMPRESSOR=None, COMPILERS=['tests.tests.test_storage.DummyCSSCompiler']) def test_post_process(self): default_collector.collect() storage = PipelineStorage() processed_files = storage.post_process({}) self.assertTrue(('screen.css', 'screen.css', True) in processed_files) self.assertTrue(('scripts.js', 'scripts.js', True) in processed_files) @override_settings(STATICFILES_STORAGE='tests.tests.test_storage.PipelineNoPathStorage') @pipeline_settings(JS_COMPRESSOR=None, CSS_COMPRESSOR=None, COMPILERS=['tests.tests.test_storage.DummyCSSCompiler']) def test_post_process_no_path(self): """ Test post_process with a storage that doesn't implement the path method. """ staticfiles_storage._setup() try: call_command('collectstatic', verbosity=0, interactive=False) except NotImplementedError: self.fail('Received an error running collectstatic') @modify_settings(STATICFILES_FINDERS={ 'append': 'pipeline.finders.PipelineFinder' }) def test_nonexistent_file_pipeline_finder(self): path = finders.find('nothing.css') self.assertIsNone(path) @modify_settings(STATICFILES_FINDERS={ 'append': 'pipeline.finders.CachedFileFinder' }) def test_nonexistent_file_cached_finder(self): path = finders.find('nothing.css') self.assertIsNone(path) @modify_settings(STATICFILES_FINDERS={ 'append': 'pipeline.finders.PipelineFinder' }) def test_nonexistent_double_extension_file_pipeline_finder(self): path = finders.find('app.css.map') self.assertIsNone(path) @modify_settings(STATICFILES_FINDERS={ 'append': 'pipeline.finders.CachedFileFinder' }) def test_nonexistent_double_extension_file_cached_finder(self): path = finders.find('app.css.map') self.assertIsNone(path) django-pipeline-1.6.14/tests/tests/test_glob.py0000664000076500000240000000712712701105433021431 0ustar timstaff00000000000000from __future__ import unicode_literals import os import shutil from django.core.files.base import ContentFile from django.core.files.storage import FileSystemStorage from django.test import TestCase from pipeline import glob def local_path(path): return os.path.join(os.path.dirname(__file__), path) class GlobTest(TestCase): def normpath(self, *parts): return os.path.normpath(os.path.join(*parts)) def mktemp(self, *parts): filename = self.normpath(*parts) base, file = os.path.split(filename) base = os.path.join(self.storage.location, base) if not os.path.exists(base): os.makedirs(base) self.storage.save(filename, ContentFile("")) def assertSequenceEqual(self, l1, l2): self.assertEqual(set(l1), set(l2)) def setUp(self): self.storage = FileSystemStorage(local_path('glob_dir')) self.old_storage = glob.staticfiles_storage glob.staticfiles_storage = self.storage self.mktemp('a', 'D') self.mktemp('aab', 'F') self.mktemp('aaa', 'zzzF') self.mktemp('ZZZ') self.mktemp('a', 'bcd', 'EF') self.mktemp('a', 'bcd', 'efg', 'ha') def glob(self, *parts): if len(parts) == 1: pattern = parts[0] else: pattern = os.path.join(*parts) return glob.glob(pattern) def tearDown(self): shutil.rmtree(self.storage.location) glob.staticfiles_storage = self.old_storage def test_glob_literal(self): self.assertSequenceEqual(self.glob('a'), [self.normpath('a')]) self.assertSequenceEqual(self.glob('a', 'D'), [self.normpath('a', 'D')]) self.assertSequenceEqual(self.glob('aab'), [self.normpath('aab')]) def test_glob_one_directory(self): self.assertSequenceEqual( self.glob('a*'), map(self.normpath, ['a', 'aab', 'aaa'])) self.assertSequenceEqual( self.glob('*a'), map(self.normpath, ['a', 'aaa'])) self.assertSequenceEqual( self.glob('aa?'), map(self.normpath, ['aaa', 'aab'])) self.assertSequenceEqual( self.glob('aa[ab]'), map(self.normpath, ['aaa', 'aab'])) self.assertSequenceEqual(self.glob('*q'), []) def test_glob_nested_directory(self): if os.path.normcase("abCD") == "abCD": # case-sensitive filesystem self.assertSequenceEqual( self.glob('a', 'bcd', 'E*'), [self.normpath('a', 'bcd', 'EF')]) else: # case insensitive filesystem self.assertSequenceEqual(self.glob('a', 'bcd', 'E*'), [ self.normpath('a', 'bcd', 'EF'), self.normpath('a', 'bcd', 'efg') ]) self.assertSequenceEqual( self.glob('a', 'bcd', '*g'), [self.normpath('a', 'bcd', 'efg')]) def test_glob_directory_names(self): self.assertSequenceEqual( self.glob('*', 'D'), [self.normpath('a', 'D')]) self.assertSequenceEqual(self.glob('*', '*a'), []) self.assertSequenceEqual( self.glob('a', '*', '*', '*a'), [self.normpath('a', 'bcd', 'efg', 'ha')]) self.assertSequenceEqual( self.glob('?a?', '*F'), map(self.normpath, [os.path.join('aaa', 'zzzF'), os.path.join('aab', 'F')])) def test_glob_directory_with_trailing_slash(self): # We are verifying that when there is wildcard pattern which # ends with os.sep doesn't blow up. paths = glob.glob('*' + os.sep) self.assertEqual(len(paths), 4) self.assertTrue(all([os.sep in path for path in paths])) django-pipeline-1.6.14/tests/tests/test_collector.py0000644000076500000240000000412213036753101022465 0ustar timstaff00000000000000from __future__ import unicode_literals import os from django.contrib.staticfiles import finders from django.core.files.storage import FileSystemStorage from django.test import TestCase from pipeline.collector import default_collector from pipeline.finders import PipelineFinder def local_path(path): return os.path.abspath(os.path.join(os.path.dirname(__file__), '..', path)) class CollectorTest(TestCase): def tearDown(self): super(CollectorTest, self).tearDown() default_collector.clear() def test_collect(self): self.assertEqual( set(default_collector.collect()), set(self._get_collectable_files())) def test_collect_with_files(self): self.assertEqual( set(default_collector.collect(files=[ 'pipeline/js/first.js', 'pipeline/js/second.js', ])), set([ 'pipeline/js/first.js', 'pipeline/js/second.js', ])) def test_delete_file_with_modified(self): list(default_collector.collect()) storage = FileSystemStorage(local_path('assets')) new_mtime = os.path.getmtime(storage.path('js/first.js')) - 1000 os.utime(default_collector.storage.path('pipeline/js/first.js'), (new_mtime, new_mtime)) self.assertTrue(default_collector.delete_file( 'js/first.js', 'pipeline/js/first.js', storage)) def test_delete_file_with_unmodified(self): list(default_collector.collect(files=['pipeline/js/first.js'])) self.assertFalse(default_collector.delete_file( 'js/first.js', 'pipeline/js/first.js', FileSystemStorage(local_path('assets')))) def _get_collectable_files(self): for finder in finders.get_finders(): if not isinstance(finder, PipelineFinder): for path, storage in finder.list(['CVS', '.*', '*~']): if getattr(storage, 'prefix', None): yield os.path.join(storage.prefix, path) else: yield path django-pipeline-1.6.14/tests/tests/test_template.py0000644000076500000240000001000212665220514022310 0ustar timstaff00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals from jinja2 import Environment, PackageLoader from django.template import Template, Context from django.test import TestCase from pipeline.jinja2 import PipelineExtension from tests.utils import pipeline_settings class JinjaTest(TestCase): def setUp(self): self.env = Environment(extensions=[PipelineExtension], loader=PackageLoader('pipeline', 'templates')) def test_no_package(self): template = self.env.from_string(u"""{% stylesheet "unknow" %}""") self.assertEqual(u'', template.render()) template = self.env.from_string(u"""{% javascript "unknow" %}""") self.assertEqual(u'', template.render()) def test_package_css(self): template = self.env.from_string(u"""{% stylesheet "screen" %}""") self.assertEqual(u'', template.render()) @pipeline_settings(PIPELINE_ENABLED=False) def test_package_css_disabled(self): template = self.env.from_string(u"""{% stylesheet "screen" %}""") self.assertEqual(u''' ''', template.render()) def test_package_js(self): template = self.env.from_string(u"""{% javascript "scripts" %}""") self.assertEqual(u'', template.render()) def test_package_js_async(self): template = self.env.from_string(u"""{% javascript "scripts_async" %}""") self.assertEqual(u'', template.render()) def test_package_js_defer(self): template = self.env.from_string(u"""{% javascript "scripts_defer" %}""") self.assertEqual(u'', template.render()) def test_package_js_async_defer(self): template = self.env.from_string(u"""{% javascript "scripts_async_defer" %}""") self.assertEqual(u'', template.render()) class DjangoTest(TestCase): def render_template(self, template): return Template(template).render(Context()) def test_compressed_empty(self): rendered = self.render_template(u"""{% load pipeline %}{% stylesheet "unknow" %}""") self.assertEqual(u"", rendered) def test_compressed_css(self): rendered = self.render_template(u"""{% load pipeline %}{% stylesheet "screen" %}""") self.assertEqual(u'', rendered) def test_compressed_js(self): rendered = self.render_template(u"""{% load pipeline %}{% javascript "scripts" %}""") self.assertEqual(u'', rendered) def test_compressed_js_async(self): rendered = self.render_template(u"""{% load pipeline %}{% javascript "scripts_async" %}""") self.assertEqual(u'', rendered) def test_compressed_js_defer(self): rendered = self.render_template(u"""{% load pipeline %}{% javascript "scripts_defer" %}""") self.assertEqual(u'', rendered) def test_compressed_js_async_defer(self): rendered = self.render_template(u"""{% load pipeline %}{% javascript "scripts_async_defer" %}""") self.assertEqual(u'', rendered) django-pipeline-1.6.14/tests/__init__.py0000664000076500000240000000000012600126513020023 0ustar timstaff00000000000000django-pipeline-1.6.14/tests/utils.py0000644000076500000240000000535213025340020017432 0ustar timstaff00000000000000import os import django from django.conf import settings from django.utils import six try: from django.test import override_settings except ImportError: # Django < 1.7 from django.test.utils import override_settings def _(path): # Make sure the path contains only the correct separator return path.replace('/', os.sep).replace('\\', os.sep) class pipeline_settings(override_settings): def __init__(self, **kwargs): if django.VERSION[:2] >= (1, 10): # Django 1.10's override_settings inherits from TestContextDecorator # and its __init__ method calls its superclass' __init__ method too, # so we must do the same. super(pipeline_settings, self).__init__() self.options = {'PIPELINE': kwargs} # Django < 1.7 (copy-pasted from Django 1.7) class modify_settings(override_settings): """ Like override_settings, but makes it possible to append, prepend or remove items instead of redefining the entire list. """ def __init__(self, *args, **kwargs): if args: # Hack used when instantiating from SimpleTestCase._pre_setup. assert not kwargs self.operations = args[0] else: assert not args self.operations = list(kwargs.items()) def save_options(self, test_func): if test_func._modified_settings is None: test_func._modified_settings = self.operations else: # Duplicate list to prevent subclasses from altering their parent. test_func._modified_settings = list( test_func._modified_settings) + self.operations def enable(self): self.options = {} for name, operations in self.operations: try: # When called from SimpleTestCase._pre_setup, values may be # overridden several times; cumulate changes. value = self.options[name] except KeyError: value = list(getattr(settings, name, [])) for action, items in operations.items(): # items my be a single value or an iterable. if isinstance(items, six.string_types): items = [items] if action == 'append': value = value + [item for item in items if item not in value] elif action == 'prepend': value = [item for item in items if item not in value] + value elif action == 'remove': value = [item for item in value if item not in items] else: raise ValueError("Unsupported action: %s" % action) self.options[name] = value super(modify_settings, self).enable() django-pipeline-1.6.14/tests/static/0000755000076500000240000000000013231236654017222 5ustar timstaff00000000000000django-pipeline-1.6.14/tests/static/scripts.js0000664000076500000240000000003512642144570021247 0ustar timstaff00000000000000(function() { }).call(this);django-pipeline-1.6.14/tests/static/scripts_async.js0000664000076500000240000000003512642144570022444 0ustar timstaff00000000000000(function() { }).call(this);django-pipeline-1.6.14/tests/static/screen.css0000664000076500000240000000000012642144570021203 0ustar timstaff00000000000000django-pipeline-1.6.14/tests/settings.py0000644000076500000240000001305513226744361020154 0ustar timstaff00000000000000import glob import os import distutils.spawn def local_path(path): return os.path.join(os.path.dirname(__file__), path) DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'TEST_NAME': ':memory:' } } DEBUG = False SITE_ID = 1 INSTALLED_APPS = [ 'django.contrib.contenttypes', 'django.contrib.sites', 'django.contrib.sessions', 'django.contrib.staticfiles', 'django.contrib.auth', 'django.contrib.admin', 'pipeline', 'tests.tests' ] MIDDLEWARE_CLASSES = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware' ) ROOT_URLCONF = 'tests.urls' MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware' ) MEDIA_URL = '/media/' MEDIA_ROOT = local_path('media') STATICFILES_STORAGE = 'pipeline.storage.PipelineStorage' STATIC_ROOT = local_path('static/') STATIC_URL = '/static/' STATICFILES_DIRS = ( ('pipeline', local_path('assets/')), ) STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 'pipeline.finders.PipelineFinder', ) SECRET_KEY = "django-pipeline" PIPELINE = { 'PIPELINE_ENABLED': True, 'JS_COMPRESSOR': None, 'CSS_COMPRESSOR': None, 'STYLESHEETS': { 'screen': { 'source_filenames': ( 'pipeline/css/first.css', 'pipeline/css/second.css', 'pipeline/css/urls.css' ), 'output_filename': 'screen.css' } }, 'JAVASCRIPT': { 'scripts': { 'source_filenames': ( 'pipeline/js/first.js', 'pipeline/js/second.js', 'pipeline/js/application.js', 'pipeline/templates/**/*.jst' ), 'output_filename': 'scripts.js' }, 'scripts_async': { 'source_filenames': ( 'pipeline/js/first.js', 'pipeline/js/second.js', 'pipeline/js/application.js', 'pipeline/templates/**/*.jst' ), 'output_filename': 'scripts_async.js', 'extra_context': { 'async': True, } }, 'scripts_defer': { 'source_filenames': ( 'pipeline/js/first.js', 'pipeline/js/second.js', 'pipeline/js/application.js', 'pipeline/templates/**/*.jst' ), 'output_filename': 'scripts_defer.js', 'extra_context': { 'defer': True, } }, 'scripts_async_defer': { 'source_filenames': ( 'pipeline/js/first.js', 'pipeline/js/second.js', 'pipeline/js/application.js', 'pipeline/templates/**/*.jst' ), 'output_filename': 'scripts_async_defer.js', 'extra_context': { 'async': True, 'defer': True, } } } } NODE_MODULES_PATH = local_path('../node_modules') NODE_BIN_PATH = os.path.join(NODE_MODULES_PATH, '.bin') NODE_EXE_PATH = distutils.spawn.find_executable('node') JAVA_EXE_PATH = distutils.spawn.find_executable('java') CSSTIDY_EXE_PATH = distutils.spawn.find_executable('csstidy') HAS_NODE = bool(NODE_EXE_PATH) HAS_JAVA = bool(JAVA_EXE_PATH) HAS_CSSTIDY = bool(CSSTIDY_EXE_PATH) if HAS_NODE: def node_exe_path(command): exe_ext = '.cmd' if os.name == 'nt' else '' return os.path.join(NODE_BIN_PATH, "%s%s" % (command, exe_ext)) PIPELINE.update({ 'SASS_BINARY': node_exe_path('node-sass'), 'COFFEE_SCRIPT_BINARY': node_exe_path('coffee'), 'COFFEE_SCRIPT_ARGUMENTS': ['--no-header'], 'LESS_BINARY': node_exe_path('lessc'), 'BABEL_BINARY': node_exe_path('babel'), 'BABEL_ARGUMENTS': ['--presets', 'es2015'], 'STYLUS_BINARY': node_exe_path('stylus'), 'LIVE_SCRIPT_BINARY': node_exe_path('lsc'), 'LIVE_SCRIPT_ARGUMENTS': ['--no-header'], 'YUGLIFY_BINARY': node_exe_path('yuglify'), 'UGLIFYJS_BINARY': node_exe_path('uglifyjs'), 'CSSMIN_BINARY': node_exe_path('cssmin'), }) if HAS_NODE and HAS_JAVA: PIPELINE.update({ 'CLOSURE_BINARY': [ JAVA_EXE_PATH, '-jar', os.path.join(NODE_MODULES_PATH, 'google-closure-compiler', 'compiler.jar')], 'YUI_BINARY': [ JAVA_EXE_PATH, '-jar', glob.glob(os.path.join(NODE_MODULES_PATH, 'yuicompressor', 'build', '*.jar'))[0]] }) if HAS_CSSTIDY: PIPELINE.update({'CSSTIDY_BINARY': CSSTIDY_EXE_PATH}) TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'APP_DIRS': True, 'DIRS': [local_path('templates')], 'OPTIONS': { 'context_processors': [ 'django.contrib.auth.context_processors.auth', ] } }, { 'BACKEND': 'django.template.backends.jinja2.Jinja2', 'APP_DIRS': True, 'DIRS': [local_path('templates')], 'OPTIONS': { 'extensions': ['pipeline.jinja2.PipelineExtension'] } } ] LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console': { 'class': 'logging.StreamHandler', }, }, 'loggers': { 'pipeline.templatetags.pipeline': { 'handlers': ['console'], 'level': 'ERROR', }, }, } django-pipeline-1.6.14/tests/templates/0000755000076500000240000000000013231236654017731 5ustar timstaff00000000000000django-pipeline-1.6.14/tests/templates/index.html0000664000076500000240000000031512600126513021716 0ustar timstaff00000000000000{% load pipeline %} Pipeline {% stylesheet 'screen' %} {% javascript 'scripts' %} django-pipeline-1.6.14/tests/templates/empty.html0000664000076500000240000000000112600126513021735 0ustar timstaff00000000000000 django-pipeline-1.6.14/tests/urls.py0000644000076500000240000000052013226744361017272 0ustar timstaff00000000000000from django.conf.urls import url from django.contrib import admin from django.views.generic import TemplateView urlpatterns = [ url(r'^$', TemplateView.as_view(template_name="index.html"), name="index"), url(r'^empty/$', TemplateView.as_view(template_name="empty.html"), name="empty"), url(r'^admin/', admin.site.urls), ] django-pipeline-1.6.14/tests/assets/0000755000076500000240000000000013231236654017235 5ustar timstaff00000000000000django-pipeline-1.6.14/tests/assets/css/0000755000076500000240000000000013231236654020025 5ustar timstaff00000000000000django-pipeline-1.6.14/tests/assets/css/unicode.css0000664000076500000240000000007312600126513022156 0ustar timstaff00000000000000.some_class { // Some unicode content: "áéíóú"; } django-pipeline-1.6.14/tests/assets/css/first.css0000664000076500000240000000003512600126513021655 0ustar timstaff00000000000000.concat { display: none; } django-pipeline-1.6.14/tests/assets/css/urls.css0000644000076500000240000000247313226744361021535 0ustar timstaff00000000000000.embedded-url-svg { background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 8h24M4 16h24M4 24h24'/%3E% 3C/svg%3E"); } @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'); font-weight: normal; font-style: normal; } .relative-url { background-image: url(../images/sprite-buttons.png); } .relative-url-querystring { background-image: url(../images/sprite-buttons.png?v=1.0#foo=bar); } .absolute-url { background-image: url(/images/sprite-buttons.png); } .absolute-full-url { background-image: url(http://localhost/images/sprite-buttons.png); } .no-protocol-url { background-image: url(//images/sprite-buttons.png); } .anchor-tag-url { background-image: url(#image-gradient); } @font-face{src:url(../fonts/pipeline.eot);src:url(../fonts/pipeline.eot?#iefix) format('embedded-opentype'),url(../fonts/pipeline.woff) format('woff'),url(../fonts/pipeline.ttf) format('truetype');} django-pipeline-1.6.14/tests/assets/css/nested/0000755000076500000240000000000013231236654021307 5ustar timstaff00000000000000django-pipeline-1.6.14/tests/assets/css/nested/nested.css0000664000076500000240000000463512600126513023304 0ustar timstaff00000000000000.data-url { background-image: url(data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2212px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2012%2014%22%20style%3D%22enable-background%3Anew%200%200%2012%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M11%2C6V5c0-2.762-2.239-5-5-5S1%2C2.238%2C1%2C5v1H0v8h12V6H11z%20M6.5%2C9.847V12h-1V9.847C5.207%2C9.673%2C5%2C9.366%2C5%2C9%20c0-0.553%2C0.448-1%2C1-1s1%2C0.447%2C1%2C1C7%2C9.366%2C6.793%2C9.673%2C6.5%2C9.847z%20M9%2C6H3V5c0-1.657%2C1.343-3%2C3-3s3%2C1.343%2C3%2C3V6z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E); } .data-url-quoted { background-image: url('data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2212px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2012%2014%22%20style%3D%22enable-background%3Anew%200%200%2012%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M11%2C6V5c0-2.762-2.239-5-5-5S1%2C2.238%2C1%2C5v1H0v8h12V6H11z%20M6.5%2C9.847V12h-1V9.847C5.207%2C9.673%2C5%2C9.366%2C5%2C9%20c0-0.553%2C0.448-1%2C1-1s1%2C0.447%2C1%2C1C7%2C9.366%2C6.793%2C9.673%2C6.5%2C9.847z%20M9%2C6H3V5c0-1.657%2C1.343-3%2C3-3s3%2C1.343%2C3%2C3V6z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E'); } django-pipeline-1.6.14/tests/assets/css/second.css0000664000076500000240000000004312600126513022000 0ustar timstaff00000000000000.concatenate { display: block; } django-pipeline-1.6.14/tests/assets/images/0000755000076500000240000000000013231236654020502 5ustar timstaff00000000000000django-pipeline-1.6.14/tests/assets/images/embed/0000755000076500000240000000000013231236654021556 5ustar timstaff00000000000000django-pipeline-1.6.14/tests/assets/images/embed/arrow.png0000664000076500000240000000015312600126513023406 0ustar timstaff00000000000000PNG  IHDR Ǵ2IDATxڅ 0 BNo!!􎤵w$ F0A"CD ]r +rIENDB`django-pipeline-1.6.14/tests/assets/images/arrow.png0000664000076500000240000000015312600126513022332 0ustar timstaff00000000000000PNG  IHDR Ǵ2IDATxڅ 0 BNo!!􎤵w$ F0A"CD ]r +rIENDB`django-pipeline-1.6.14/tests/assets/images/sprite-buttons.png0000664000076500000240000000000012600126513024171 0ustar timstaff00000000000000django-pipeline-1.6.14/tests/assets/js/0000755000076500000240000000000013231236654017651 5ustar timstaff00000000000000django-pipeline-1.6.14/tests/assets/js/second.js0000664000076500000240000000012412600126513021450 0ustar timstaff00000000000000(function() { window.cat = function() { console.log("hello world"); } }()); django-pipeline-1.6.14/tests/assets/js/dummy.coffee0000664000076500000240000000006412600126513022146 0ustar timstaff00000000000000square = (x) -> x * x cube = (x) -> square(x) * x django-pipeline-1.6.14/tests/assets/js/first.js0000664000076500000240000000014212600126513021324 0ustar timstaff00000000000000(function() { window.concat = function() { console.log(arguments); } }()) // No semicolon django-pipeline-1.6.14/tests/assets/js/application.js0000664000076500000240000000005712600126513022505 0ustar timstaff00000000000000function test() { alert('this is a test'); } django-pipeline-1.6.14/tests/assets/compressors/0000755000076500000240000000000013231236654021614 5ustar timstaff00000000000000django-pipeline-1.6.14/tests/assets/compressors/yuglify.js0000664000076500000240000000023312701105433023631 0ustar timstaff00000000000000(function(){(function(){window.concat=function(){console.log(arguments)}})(),function(){window.cat=function(){console.log("hello world")}}()}).call(this); django-pipeline-1.6.14/tests/assets/compressors/slimit.js0000664000076500000240000000024112701105433023441 0ustar timstaff00000000000000(function(){(function(){window.concat=function(){console.log(arguments);};}());(function(){window.cat=function(){console.log("hello world");};}());}).call(this);django-pipeline-1.6.14/tests/assets/compressors/yui.css0000664000076500000240000000006012701105433023121 0ustar timstaff00000000000000.concat{display:none}.concatenate{display:block}django-pipeline-1.6.14/tests/assets/compressors/cssmin.css0000664000076500000240000000006012701105433023607 0ustar timstaff00000000000000.concat{display:none}.concatenate{display:block}django-pipeline-1.6.14/tests/assets/compressors/closure.js0000664000076500000240000000023512701105433023617 0ustar timstaff00000000000000(function(){(function(){window.concat=function(){console.log(arguments)}})();(function(){window.cat=function(){console.log("hello world")}})()}).call(this); django-pipeline-1.6.14/tests/assets/compressors/yuglify.css0000664000076500000240000000006112701105433024004 0ustar timstaff00000000000000.concat{display:none}.concatenate{display:block} django-pipeline-1.6.14/tests/assets/compressors/yui.js0000664000076500000240000000023412701105433022750 0ustar timstaff00000000000000(function(){(function(){window.concat=function(){console.log(arguments)}}());(function(){window.cat=function(){console.log("hello world")}}())}).call(this);django-pipeline-1.6.14/tests/assets/compressors/csstidy.css0000664000076500000240000000006212701105433023777 0ustar timstaff00000000000000.concat{display:none;}.concatenate{display:block;}django-pipeline-1.6.14/tests/assets/compressors/uglifyjs.js0000664000076500000240000000023512701105433023777 0ustar timstaff00000000000000(function(){(function(){window.concat=function(){console.log(arguments)}})();(function(){window.cat=function(){console.log("hello world")}})()}).call(this); django-pipeline-1.6.14/tests/assets/compressors/jsmin.js0000664000076500000240000000023712701105433023265 0ustar timstaff00000000000000(function(){(function(){window.concat=function(){console.log(arguments);}}());(function(){window.cat=function(){console.log("hello world");}}());}).call(this);django-pipeline-1.6.14/tests/assets/templates/0000755000076500000240000000000013231236654021233 5ustar timstaff00000000000000django-pipeline-1.6.14/tests/assets/templates/video/0000755000076500000240000000000013231236654022341 5ustar timstaff00000000000000django-pipeline-1.6.14/tests/assets/templates/video/detail.jst0000664000076500000240000000015212600126513024314 0ustar timstaff00000000000000
django-pipeline-1.6.14/tests/assets/templates/photo/0000755000076500000240000000000013231236654022364 5ustar timstaff00000000000000django-pipeline-1.6.14/tests/assets/templates/photo/list.jst0000664000076500000240000000014412600126513024051 0ustar timstaff00000000000000
<%= caption %>
django-pipeline-1.6.14/tests/assets/templates/photo/detail.jst0000664000076500000240000000016512600126513024343 0ustar timstaff00000000000000
<%= caption %> by <%= author %>
django-pipeline-1.6.14/tests/assets/compilers/0000755000076500000240000000000013231236654021232 5ustar timstaff00000000000000django-pipeline-1.6.14/tests/assets/compilers/less/0000755000076500000240000000000013231236654022200 5ustar timstaff00000000000000django-pipeline-1.6.14/tests/assets/compilers/less/expected.css0000664000076500000240000000002512701105433024501 0ustar timstaff00000000000000.a { width: 1px; } django-pipeline-1.6.14/tests/assets/compilers/less/input.less0000664000076500000240000000004612701105433024220 0ustar timstaff00000000000000@a: 1; .a { width: (@a + 0px); } django-pipeline-1.6.14/tests/assets/compilers/scss/0000755000076500000240000000000013231236654022205 5ustar timstaff00000000000000django-pipeline-1.6.14/tests/assets/compilers/scss/expected.css0000664000076500000240000000007012701105433024506 0ustar timstaff00000000000000.a .b { display: none; } .c .d { display: block; } django-pipeline-1.6.14/tests/assets/compilers/scss/input.scss0000664000076500000240000000013312701105433024227 0ustar timstaff00000000000000.a { .b { display: none; } } .c { .d { display: block; } } django-pipeline-1.6.14/tests/assets/compilers/stylus/0000755000076500000240000000000013231236654022575 5ustar timstaff00000000000000django-pipeline-1.6.14/tests/assets/compilers/stylus/expected.css0000664000076500000240000000002612701105433025077 0ustar timstaff00000000000000.a { color: #000; } django-pipeline-1.6.14/tests/assets/compilers/stylus/input.styl0000664000076500000240000000002312701105433024635 0ustar timstaff00000000000000.a color: blackdjango-pipeline-1.6.14/tests/assets/compilers/coffee/0000755000076500000240000000000013231236654022461 5ustar timstaff00000000000000django-pipeline-1.6.14/tests/assets/compilers/coffee/input.coffee0000664000076500000240000000006412701105433024762 0ustar timstaff00000000000000square = (x) -> x * x cube = (x) -> square(x) * x django-pipeline-1.6.14/tests/assets/compilers/coffee/expected.js0000664000076500000240000000023212701105433024606 0ustar timstaff00000000000000(function() { var cube, square; square = function(x) { return x * x; }; cube = function(x) { return square(x) * x; }; }).call(this); django-pipeline-1.6.14/tests/assets/compilers/es6/0000755000076500000240000000000013231236654021727 5ustar timstaff00000000000000django-pipeline-1.6.14/tests/assets/compilers/es6/input.es60000664000076500000240000000052712701105433023502 0ustar timstaff00000000000000// Expression bodies var odds = evens.map(v => v + 1); var nums = evens.map((v, i) => v + i); // Statement bodies nums.forEach(v => { if (v % 5 === 0) fives.push(v); }); // Lexical this var bob = { _name: "Bob", _friends: [], printFriends() { this._friends.forEach(f => console.log(this._name + " knows " + f)); } }; django-pipeline-1.6.14/tests/assets/compilers/es6/expected.js0000664000076500000240000000073312701105433024062 0ustar timstaff00000000000000"use strict"; // Expression bodies var odds = evens.map(function (v) { return v + 1; }); var nums = evens.map(function (v, i) { return v + i; }); // Statement bodies nums.forEach(function (v) { if (v % 5 === 0) fives.push(v); }); // Lexical this var bob = { _name: "Bob", _friends: [], printFriends: function printFriends() { var _this = this; this._friends.forEach(function (f) { return console.log(_this._name + " knows " + f); }); } }; django-pipeline-1.6.14/tests/assets/compilers/livescript/0000755000076500000240000000000013231236654023416 5ustar timstaff00000000000000django-pipeline-1.6.14/tests/assets/compilers/livescript/input.ls0000664000076500000240000000003212701105433025101 0ustar timstaff00000000000000times = (x, y) -> x * y django-pipeline-1.6.14/tests/assets/compilers/livescript/expected.js0000664000076500000240000000013212701105433025542 0ustar timstaff00000000000000(function(){ var times; times = function(x, y){ return x * y; }; }).call(this); django-pipeline-1.6.14/tests/assets/fonts/0000755000076500000240000000000013231236654020366 5ustar timstaff00000000000000django-pipeline-1.6.14/tests/assets/fonts/pipeline.eot0000664000076500000240000000000012600126513022663 0ustar timstaff00000000000000django-pipeline-1.6.14/tests/assets/fonts/pipeline.ttf0000664000076500000240000000000012600126513022671 0ustar timstaff00000000000000django-pipeline-1.6.14/tests/assets/fonts/pipeline.svg0000664000076500000240000000000012600126513022673 0ustar timstaff00000000000000django-pipeline-1.6.14/tests/assets/fonts/pipeline.woff0000664000076500000240000000000012600126513023035 0ustar timstaff00000000000000django-pipeline-1.6.14/tests/views.py0000664000076500000240000000000012600126513017421 0ustar timstaff00000000000000django-pipeline-1.6.14/MANIFEST.in0000644000076500000240000000071013226744361016330 0ustar timstaff00000000000000recursive-include pipeline/templates *.html *.jinja recursive-include pipeline/jinja2 *.html *.jinja include AUTHORS LICENSE README.rst HISTORY.rst CONTRIBUTING.rst recursive-include tests * recursive-exclude tests *.pyc *.pyo recursive-exclude tests/node_modules * recursive-exclude tests/npm-cache * recursive-exclude tests/npm * include docs/Makefile docs/make.bat docs/conf.py recursive-include docs *.rst exclude package.json requirements.txt tox.ini django-pipeline-1.6.14/docs/0000755000076500000240000000000013231236654015521 5ustar timstaff00000000000000django-pipeline-1.6.14/docs/templates.rst0000644000076500000240000000622412665220514020253 0ustar timstaff00000000000000.. _ref-templates: ==================== Javascript Templates ==================== Pipeline allows you to use javascript templates along with your javascript views. To use your javascript templates, just add them to your ``JAVASCRIPT`` group :: PIPELINE['JAVASCRIPT'] = { 'application': { 'source_filenames': ( 'js/application.js', 'js/templates/**/*.jst', ), 'output_filename': 'js/application.js' } } For example, if you have the following template ``js/templates/photo/detail.jst`` ::
<%= caption %>
It will be available from your javascript code via window.JST :: JST.photo_detail({ src:"images/baby-panda.jpg", caption:"A baby panda is born" }); Configuration ------------- Template function ................. By default, Pipeline uses a variant of `Micro Templating `_ to compile the templates, but you can choose your preferred JavaScript templating engine by changing ``PIPELINE['TEMPLATE_FUNC']`` :: PIPELINE['TEMPLATE_FUNC'] = 'template' Template namespace .................. Your templates are made available in a top-level object, by default ``window.JST``, but you can choose your own via ``PIPELINE['TEMPLATE_NAMESPACE']`` :: PIPELINE['TEMPLATE_NAMESPACE'] = 'window.Template' Template extension .................. Templates are detected by their extension, by default ``.jst``, but you can use your own extension via ``PIPELINE['TEMPLATE_EXT']`` :: PIPELINE['TEMPLATE_EXT'] = '.mustache' Template separator .................. Templates identifier are built using a replacement for directory separator, by default ``_``, but you specify your own separator via ``PIPELINE['TEMPLATE_SEPARATOR']`` :: PIPELINE['TEMPLATE_SEPARATOR'] = '/' Using it with your favorite template library -------------------------------------------- Mustache ........ To use it with `Mustache `_ you will need some extra javascript :: Mustache.template = function(templateString) { return function() { if (arguments.length < 1) { return templateString; } else { return Mustache.to_html(templateString, arguments[0], arguments[1]); } }; }; And use these settings :: PIPELINE['TEMPLATE_EXT'] = '.mustache' PIPELINE['TEMPLATE_FUNC'] = 'Mustache.template' Handlebars .......... To use it with `Handlebars `_, use the following settings :: PIPELINE['TEMPLATE_EXT'] = '.handlebars' PIPELINE['TEMPLATE_FUNC'] = 'Handlebars.compile' PIPELINE['TEMPLATE_NAMESPACE'] = 'Handlebars.templates' Ember.js + Handlebars ..................... To use it with `Ember.js `_, use the following settings :: PIPELINE['TEMPLATE_EXT'] = '.handlebars' PIPELINE['TEMPLATE_FUNC'] = 'Ember.Handlebars.compile' PIPELINE['TEMPLATE_NAMESPACE'] = 'window.Ember.TEMPLATES' PIPELINE['TEMPLATE_SEPARATOR'] = '/' Prototype ......... To use it with `Prototype `_, just setup your ``PIPELINE['TEMPLATE_FUNC']`` :: PIPELINE['TEMPLATE_FUNC'] = 'new Template' django-pipeline-1.6.14/docs/index.rst0000664000076500000240000000132512640545147017370 0ustar timstaff00000000000000Introduction ============ 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. You can report bugs and discuss features on the `issues page `_. You can discuss features or ask questions on the IRC channel on freenode : `#django-pipeline `_ Table Of Contents ================= .. toctree:: :maxdepth: 2 installation configuration usage compressors compilers templates storages signals using Indices and tables ================== * :ref:`search` django-pipeline-1.6.14/docs/signals.rst0000664000076500000240000000126012600126513017703 0ustar timstaff00000000000000.. _ref-signals: ======= Signals ======= List of all signals sent by pipeline. css_compressed -------------- **pipeline.signals.css_compressed** Whenever a css package is compressed, this signal is sent after the compression. Arguments sent with this signal : :sender: The ``Packager`` class that compressed the group. :package: The package actually compressed. js_compressed -------------- **pipeline.signals.js_compressed** Whenever a js package is compressed, this signal is sent after the compression. Arguments sent with this signal : :sender: The ``Packager`` class that compressed the group. :package: The package actually compressed. django-pipeline-1.6.14/docs/Makefile0000664000076500000240000001102212600126513017146 0ustar timstaff00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-pipeline.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-pipeline.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/django-pipeline" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-pipeline" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." django-pipeline-1.6.14/docs/configuration.rst0000644000076500000240000001664413226744361021140 0ustar timstaff00000000000000.. _ref-configuration: ============= Configuration ============= Configuration and list of available settings for Pipeline. Pipeline settings are namespaced in a PIPELINE dictionary in your project settings, e.g.: :: PIPELINE = { 'PIPELINE_ENABLED': True, 'JAVASCRIPT': { 'stats': { 'source_filenames': ( 'js/jquery.js', 'js/d3.js', 'js/collections/*.js', 'js/application.js', ), 'output_filename': 'js/stats.js', } } } Specifying files ================ You specify groups of files to be compressed in your settings. You can use glob syntax to select multiples files. The basic syntax for specifying CSS/JavaScript groups files is :: PIPELINE = { 'STYLESHEETS': { 'colors': { 'source_filenames': ( 'css/core.css', 'css/colors/*.css', 'css/layers.css' ), 'output_filename': 'css/colors.css', 'extra_context': { 'media': 'screen,projection', }, }, }, 'JAVASCRIPT': { 'stats': { 'source_filenames': ( 'js/jquery.js', 'js/d3.js', 'js/collections/*.js', 'js/application.js', ), 'output_filename': 'js/stats.js', } } } Group options ------------- ``source_filenames`` .................... **Required** Is a tuple with the source files to be compressed. The files are concatenated in the order specified in the tuple. ``output_filename`` ................... **Required** Is the filename of the (to be) compressed file. ``variant`` ........... **Optional** Is the variant you want to apply to your CSS. This allow you to embed images and fonts in CSS with data-URI. Allowed values are : ``None`` and ``datauri``. Defaults to ``None``. ``template_name`` ................. **Optional** Name of the template used to render ``