Skip to content

Commit

Permalink
Merge pull request #545 from chipx86/inline-compile-errors
Browse files Browse the repository at this point in the history
Add error output for compiler errors within the browser.
  • Loading branch information
davidt committed Mar 7, 2016
2 parents f2366b5 + 3b5c5a2 commit 452f97f
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 13 deletions.
12 changes: 12 additions & 0 deletions docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,18 @@ Defaults to ``True``

this only work when PIPELINE_ENABLED is False.

``SHOW_ERRORS_INLINE``
......................

``True`` if errors compiling CSS/JavaScript files should be shown inline at
the top of the browser window, or ``False`` if they should trigger exceptions
(the older behavior).

This only applies when compiling through the ``{% stylesheet %}`` or
``{% javascript %}`` template tags. It won't impact ``collectstatic``.

Defaults to ``settings.DEBUG``.

``CSS_COMPRESSOR``
..................

Expand Down
9 changes: 6 additions & 3 deletions pipeline/compilers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
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
from django.utils.six import string_types, text_type

from pipeline.conf import settings
from pipeline.exceptions import CompilerError
Expand Down Expand Up @@ -125,7 +125,9 @@ def execute_command(self, command, cwd=None, stdout_captured=None):
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))
"{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:
Expand All @@ -134,7 +136,8 @@ def execute_command(self, command, cwd=None, stdout_captured=None):
print(stderr)
except OSError as e:
stdout_captured = None # Don't save erroneous result.
raise CompilerError(e)
raise CompilerError(e, command=argument_list,
error_output=text_type(e))
finally:
# Decide what to do with captured stdout.
if stdout:
Expand Down
2 changes: 2 additions & 0 deletions pipeline/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
'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': [],
Expand Down
6 changes: 5 additions & 1 deletion pipeline/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ class PackageNotFound(PipelineException):


class CompilerError(PipelineException):
pass
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):
Expand Down
4 changes: 2 additions & 2 deletions pipeline/jinja2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def package_css(self, package_name, *args, **kwargs):
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, 'css')
return self.render_compressed(package, package_name, 'css')

def render_css(self, package, path):
template_name = package.template_name or "pipeline/css.jinja"
Expand All @@ -55,7 +55,7 @@ def package_js(self, package_name, *args, **kwargs):
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, 'js')
return self.render_compressed(package, package_name, 'js')

def render_js(self, package, path):
template_name = package.template_name or "pipeline/js.jinja"
Expand Down
31 changes: 31 additions & 0 deletions pipeline/templates/pipeline/compile_error.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<div id="django-pipeline-error-{{package_name}}" class="django-pipeline-error"
style="display: none; border: 2px #DD0000 solid; margin: 1em; padding: 1em; background: white;">
<h1>Error compiling {{package_type}} package "{{package_name}}"</h1>
<p><strong>Command:</strong></p>
<pre style="white-space: pre-wrap;">{{command}}</pre>
<p><strong>Errors:</strong></p>
<pre style="white-space: pre-wrap;">{{errors}}</pre>
</div>

<script>
document.addEventListener('readystatechange', function() {
var el,
container;

if (document.readyState !== 'interactive') {
return;
}

el = document.getElementById('django-pipeline-error-{{package_name}}');
container = document.getElementById('django-pipeline-errors');

if (!container) {
container = document.createElement('div');
container.id = 'django-pipeline-errors';
document.body.insertBefore(container, document.body.firstChild);
}

container.appendChild(el);
el.style.display = 'block';
});
</script>
39 changes: 34 additions & 5 deletions pipeline/templatetags/pipeline.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
from __future__ import unicode_literals

import logging
import subprocess

from django.contrib.staticfiles.storage import staticfiles_storage

from django import template
from django.template.base import VariableDoesNotExist
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

Expand Down Expand Up @@ -51,7 +53,7 @@ def render(self, context):
except VariableDoesNotExist:
pass

def render_compressed(self, package, package_type):
def render_compressed(self, package, package_name, package_type):
if settings.PIPELINE_ENABLED:
method = getattr(self, "render_{0}".format(package_type))
return method(package, package.output_filename)
Expand All @@ -61,10 +63,29 @@ def render_compressed(self, package, package_type):

packager = Packager()
method = getattr(self, "render_individual_{0}".format(package_type))
paths = packager.compile(package.paths)

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', Context({
'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):
Expand All @@ -79,7 +100,7 @@ def render(self, context):
except PackageNotFound:
logger.warn("Package %r is unknown. Check PIPELINE_CSS in your settings.", package_name)
return '' # fail silently, do not return anything if an invalid group is specified
return self.render_compressed(package, 'css')
return self.render_compressed(package, package_name, 'css')

def render_css(self, package, path):
template_name = package.template_name or "pipeline/css.html"
Expand All @@ -94,6 +115,10 @@ 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):
Expand All @@ -108,7 +133,7 @@ def render(self, context):
except PackageNotFound:
logger.warn("Package %r is unknown. Check PIPELINE_JS in your settings.", package_name)
return '' # fail silently, do not return anything if an invalid group is specified
return self.render_compressed(package, 'js')
return self.render_compressed(package, package_name, 'js')

def render_js(self, package, path):
template_name = package.template_name or "pipeline/js.html"
Expand All @@ -132,6 +157,10 @@ def render_individual_js(self, package, paths, templates=None):
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):
Expand Down
18 changes: 16 additions & 2 deletions tests/tests/test_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,16 @@ def setUp(self):
self.compiler = Compiler()

def test_compile(self):
self.assertRaises(CompilerError, self.compiler.compile, [_('pipeline/js/dummy.coffee')])
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()
Expand All @@ -170,7 +179,12 @@ def setUp(self):
self.compiler = Compiler()

def test_compile(self):
self.assertRaises(CompilerError, self.compiler.compile, [_('pipeline/js/dummy.coffee')])
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()
Expand Down

0 comments on commit 452f97f

Please sign in to comment.