from __future__ import with_statement
import os
from webassets.filter import ExternalTool
from webassets.utils import working_directory
class Less(ExternalTool):
"""Converts `less `_ markup to real CSS.
This depends on the NodeJS implementation of less, installable via npm.
To use the old Ruby-based version (implemented in the 1.x Ruby gem), see
:class:`~.less_ruby.Less`.
*Supported configuration options*:
LESS_BIN (binary)
Path to the less executable used to compile source files. By default,
the filter will attempt to run ``lessc`` via the system path.
LESS_LINE_NUMBERS (line_numbers)
Outputs filename and line numbers. Can be either 'comments', which
will output the debug info within comments, 'mediaquery' that will
output the information within a fake media query which is compatible
with the SASSPath to the less executable used to compile source files.
LESS_RUN_IN_DEBUG (run_in_debug)
By default, the filter will compile in debug mode. Since the less
compiler is written in Javascript and capable of running in the
browser, you can set this to ``False`` to have your original less
source files served (see below).
LESS_PATHS (paths)
Add include paths for less command line.
It should be a list of paths relatives to Environment.directory or absolute paths.
Order matters as less will pick the first file found in path order.
LESS_AS_OUTPUT (boolean)
By default, this works as an "input filter", meaning ``less`` is
called for each source file in the bundle. This is because the
path of the source file is required so that @import directives
within the Less file can be correctly resolved.
However, it is possible to use this filter as an "output filter",
meaning the source files will first be concatenated, and then the
Less filter is applied in one go. This can provide a speedup for
bigger projects.
.. admonition:: Compiling less in the browser
less is an interesting case because it is written in Javascript and
capable of running in the browser. While for performance reason you
should prebuild your stylesheets in production, while developing you
may be interested in serving the original less files to the client,
and have less compile them in the browser.
To do so, you first need to make sure the less filter is not applied
when :attr:`Environment.debug` is ``True``. You can do so via an
option::
env.config['less_run_in_debug'] = False
Second, in order for the less to identify the less source files as
needing to be compiled, they have to be referenced with a
``rel="stylesheet/less"`` attribute. One way to do this is to use the
:attr:`Bundle.extra` dictionary, which works well with the template
tags that webassets provides for some template languages::
less_bundle = Bundle(
'**/*.less',
filters='less',
extra={'rel': 'stylesheet/less' if env.debug else 'stylesheet'}
)
Then, for example in a Jinja2 template, you would write::
{% assets less_bundle %}
{% endassets %}
With this, the ```` tag will sport the correct ``rel`` value both
in development and in production.
Finally, you need to include the less compiler::
if env.debug:
js_bundle.contents += 'http://lesscss.googlecode.com/files/less-1.3.0.min.js'
"""
name = 'less'
options = {
'less': ('binary', 'LESS_BIN'),
'run_in_debug': 'LESS_RUN_IN_DEBUG',
'line_numbers': 'LESS_LINE_NUMBERS',
'extra_args': 'LESS_EXTRA_ARGS',
'paths': 'LESS_PATHS',
'as_output': 'LESS_AS_OUTPUT'
}
max_debug_level = None
def setup(self):
super(Less, self).setup()
if self.run_in_debug is False:
# Disable running in debug mode for this instance.
self.max_debug_level = False
def resolve_source(self, path):
return self.ctx.resolver.resolve_source(self.ctx, path)
def _apply_less(self, in_, out, source_path=None, **kw):
# Set working directory to the source file so that includes are found
args = self.parse_binary(self.less or 'lessc')
if self.line_numbers:
args.append('--line-numbers=%s' % self.line_numbers)
if self.paths:
paths = [
path if os.path.isabs(path) else self.resolve_source(path)
for path in self.paths
]
args.append('--include-path={0}'.format(os.pathsep.join(paths)))
if self.extra_args:
args.extend(self.extra_args)
args.append('-')
if source_path:
with working_directory(filename=source_path):
self.subprocess(args, out, in_)
else:
self.subprocess(args, out, in_)
def input(self, _in, out, source_path, output_path, **kw):
if self.as_output:
out.write(_in.read())
else:
self._apply_less(_in, out, source_path)
def output(self, _in, out, **kwargs):
if not self.as_output:
out.write(_in.read())
else:
self._apply_less(_in, out)