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)