class CompassConnectorFilter(Filter): name = 'compassconnector' max_debug_level = None options = { 'compass': ('binary', 'COMPASS_BIN'), 'plugins': option('COMPASS_PLUGINS', type=list), 'vendor_path': ('relative path', 'VENDOR_PATH'), 'imports': option('COMPASS_IMPORTS', type=list), } depends = None def find_dependencies(self): return self.depends def unique(self): hash_func(self.plugins) def input(self, in_, out, **kwargs): h = connector.Handler(self.env, in_, out, self.plugins if self.plugins else {}, self.imports if self.imports else [], kwargs["source"]) h.vendor_path = self.vendor_path if not self.compass: raise FilterError("Compass bin path is not set") h.start(self.compass) self.depends = h.deps
class Browserify(ExternalTool): """Use Browserify to bundle assets. Requires the Browserify executable to be available externally. You can install it using `Node Package Manager <http://npmjs.org/>`_:: $ npm install -g browserify Supported configuration options: BROWSERIFY_BIN The path to the Browserify binary. If not set, assumes ``browserify`` is in the system path. BROWSERIFY_TRANSFORMS A list of Browserify transforms to use. Each transform will be included via Browserify's command-line ``--transform`` argument. If you need to pass arguments to a transform, use a list: ``['babelify', '--stage', '0']`` BROWSERIFY_EXTRA_ARGS A list of any additional command-line arguments. """ name = 'browserify' max_debug_level = None options = { 'binary': 'BROWSERIFY_BIN', 'transforms': option('BROWSERIFY_TRANSFORMS', type=list), 'extra_args': option('BROWSERIFY_EXTRA_ARGS', type=list) } def input(self, infile, outfile, **kwargs): args = [self.binary or 'browserify'] for transform in self.transforms or []: if isinstance(transform, (list, tuple)): args.extend(('--transform', '[')) args.extend(transform) args.append(']') else: args.extend(('--transform', transform)) if self.extra_args: args.extend(self.extra_args) args.append(kwargs['source_path']) self.subprocess(args, outfile, infile)
class Rollup(ExternalTool): """Use Rollup to bundle assets. Requires the Rollup executable to be available externally. You can install it using `Node Package Manager <http://npmjs.org/>`_:: $ npm install -g rollup Supported configuration options: ROLLUP_BIN The path to the Rollup binary. If not set, assumes ``rollup`` is in the system path. ROLLUP_EXTRA_ARGS A list of any additional command-line arguments. """ name = 'rollup' max_debug_level = None options = { 'binary': 'ROLLUP_BIN', 'extra_args': option('ROLLUP_EXTRA_ARGS', type=list) } def input(self, infile, outfile, **kwargs): args = [self.binary or 'rollup'] if self.extra_args: args.extend(self.extra_args) args.append(kwargs['source_path']) self.subprocess(args, outfile, infile)
class Stylus(Filter): """Converts `Stylus <http://learnboost.github.com/stylus/>`_ markup to CSS. Requires the Stylus executable to be available externally. You can install it using the `Node Package Manager <http://npmjs.org/>`_:: $ npm install stylus Supported configuration options: STYLUS_BIN The path to the Stylus binary. If not set, assumes ``stylus`` is in the system path. STYLUS_PLUGINS A Python list of Stylus plugins to use. Each plugin will be included via Stylus's command-line ``--use`` argument. STYLUS_EXTRA_ARGS A Python list of any additional command-line arguments. """ name = 'stylus' options = { 'stylus': 'STYLUS_BIN', 'plugins': option('STYLUS_PLUGINS', type=list), 'extra_args': option('STYLUS_EXTRA_ARGS', type=list), } max_debug_level = None def input(self, _in, out, **kwargs): args = [self.stylus or 'stylus'] for plugin in self.plugins or []: args.extend(('--use', plugin)) if self.extra_args: args.extend(self.extra_args) PIPE = subprocess.PIPE proc = subprocess.Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE) stdout, stderr = proc.communicate(_in.read()) if proc.returncode != 0: raise FilterError('%s: subprocess had error: stderr=%s, code=%s', self.name, stderr, proc.returncode) out.write(stdout)
class Stylus(ExternalTool): """Converts `Stylus <http://learnboost.github.com/stylus/>`_ markup to CSS. Requires the Stylus executable to be available externally. You can install it using the `Node Package Manager <http://npmjs.org/>`_:: $ npm install -g stylus Supported configuration options: STYLUS_BIN The path to the Stylus binary. If not set, assumes ``stylus`` is in the system path. STYLUS_PLUGINS A Python list of Stylus plugins to use. Each plugin will be included via Stylus's command-line ``--use`` argument. STYLUS_EXTRA_ARGS A Python list of any additional command-line arguments. STYLUS_EXTRA_PATHS A Python list of any additional import paths. """ name = 'stylus' options = { 'stylus': 'STYLUS_BIN', 'plugins': option('STYLUS_PLUGINS', type=list), 'extra_args': option('STYLUS_EXTRA_ARGS', type=list), 'extra_paths': option('STYLUS_EXTRA_PATHS', type=list), } max_debug_level = None def input(self, _in, out, **kwargs): args = [self.stylus or 'stylus'] source_dir = os.path.dirname(kwargs['source_path']) paths = [source_dir] + (self.extra_paths or []) for path in paths: args.extend(('--include', path)) for plugin in self.plugins or []: args.extend(('--use', plugin)) if self.extra_args: args.extend(self.extra_args) self.subprocess(args, out, _in)
class CompileLess(Filter): name = 'lessc' max_debug_level = None options = {'less_includes': option('LIBSASS_INCLUDES', type=list)} def input(self, _in, out, **kw): options = {'paths': []} if self.less_includes: options['paths'].extend(self.less_includes) if 'source_path' in kw: options['paths'].append(os.path.dirname(kw['source_path'])) src = dukpy.less_compile(_in.read(), options=options) out.write(src)
class Compass(Filter): """Converts `Compass <http://compass-style.org/>`_ .sass files to CSS. Requires at least version 0.10. To compile a standard Compass project, you only need to have to compile your main ``screen.sass``, ``print.sass`` and ``ie.sass`` files. All the partials that you include will be handled by Compass. If you want to combine the filter with other CSS filters, make sure this one runs first. Supported configuration options: COMPASS_BIN The path to the Compass binary. If not set, the filter will try to run ``compass`` as if it's in the system path. COMPASS_PLUGINS Compass plugins to use. This is equivalent to the ``--require`` command line option of the Compass. and expects a Python list object of Ruby libraries to load. COMPASS_CONFIG An optional dictionary of Compass `configuration options <http://compass-style.org/help/documentation/configuration-reference/>`_. The values are emitted as strings, and paths are relative to the Environment's ``directory`` by default; include a ``project_path`` entry to override this. The ``sourcemap`` option has a caveat. A file called _.css.map is created by Compass in the tempdir (where _.scss is the original asset), which is then moved into the output_path directory. Since the tempdir is created one level down from the output path, the relative links in the sourcemap should correctly map. This file, however, will not be versioned, and thus this option should ideally only be used locally for development and not in production with a caching service as the _.css.map file will not be invalidated. """ name = 'compass' max_debug_level = None options = { 'compass': ('binary', 'COMPASS_BIN'), 'plugins': option('COMPASS_PLUGINS', type=list), 'config': 'COMPASS_CONFIG', } def open(self, out, source_path, **kw): """Compass currently doesn't take data from stdin, and doesn't allow us accessing the result from stdout either. Also, there's a bunch of other issues we need to work around: - compass doesn't support given an explict output file, only a "--css-dir" output directory. We have to "guess" the filename that will be created in that directory. - The output filename used is based on the input filename, and simply cutting of the length of the "sass_dir" (and changing the file extension). That is, compass expects the input filename to always be inside the "sass_dir" (which defaults to ./src), and if this is not the case, the output filename will be gibberish (missing characters in front). See: https://github.com/chriseppstein/compass/issues/304 We fix this by setting the proper --sass-dir option. - Compass insists on creating a .sass-cache folder in the current working directory, and unlike the sass executable, there doesn't seem to be a way to disable it. The workaround is to set the working directory to our temp directory, so that the cache folder will be deleted at the end. """ # Create temp folder one dir below output_path so sources in # sourcemap are correct. This will be in the project folder, # and as such, while exteremly unlikely, this could interfere # with existing files and directories. tempout_dir = path.normpath( path.join(path.dirname(kw['output_path']), '../') ) tempout = tempfile.mkdtemp(dir=tempout_dir) # Temporarily move to "tempout", so .sass-cache will be created there old_wd = os.getcwd() os.chdir(tempout) try: # Make sure to use normpath() to not cause trouble with # compass' simplistic path handling, where it just assumes # source_path is within sassdir, and cuts off the length of # sassdir from the input file. sassdir = path.normpath(path.dirname(source_path)) source_path = path.normpath(source_path) # Compass offers some helpers like image-url(), which need # information about the urls under which media files will be # available. This is hard for two reasons: First, the options in # question aren't supported on the command line, so we need to write # a temporary config file. Secondly, they assume defined and # separate directories for "images", "stylesheets" etc., something # webassets knows nothing of: we don't support the user defining # such directories. Because we traditionally had this # filter point all type-specific directories to the root media # directory, we will define the paths to match this. In other # words, in Compass, both inline-image("img/test.png) and # image-url("img/test.png") will find the same file, and assume it # to be {env.directory}/img/test.png. # However, this partly negates the purpose of an utility like # image-url() in the first place - you not having to hard code # the location of your images. So we allow direct modification of # the configuration file via the COMPASS_CONFIG setting (see # tickets #36 and #125). # # Note that there is also the --relative-assets option, which we # can't use because it calculates an actual relative path between # the image and the css output file, the latter being in a # temporary directory in our case. config = CompassConfig( project_path=self.ctx.directory, http_path=self.ctx.url, http_images_dir='', http_stylesheets_dir='', http_fonts_dir='', http_javascripts_dir='', images_dir='', output_style=':expanded', ) # Update with the custom config dictionary, if any. if self.config: config.update(self.config) config_file = path.join(tempout, '.config.rb') f = open(config_file, 'w') try: f.write(config.to_string()) f.flush() finally: f.close() command = [self.compass or 'compass', 'compile'] for plugin in self.plugins or []: command.extend(('--require', plugin)) command.extend(['--sass-dir', sassdir, '--css-dir', tempout, '--config', config_file, '--quiet', '--boring', source_path]) proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, # shell: necessary on windows to execute # ruby files, but doesn't work on linux. shell=(os.name == 'nt')) stdout, stderr = proc.communicate() # compass seems to always write a utf8 header? to stderr, so # make sure to not fail just because there's something there. if proc.returncode != 0: raise FilterError(('compass: subprocess had error: stderr=%s, '+ 'stdout=%s, returncode=%s') % ( stderr, stdout, proc.returncode)) guessed_outputfilename = path.splitext(path.basename(source_path))[0] guessed_outputfilepath = path.join(tempout, guessed_outputfilename) output_file = open("%s.css" % guessed_outputfilepath, encoding='utf-8') if config.get('sourcemap'): sourcemap_file = open("%s.css.map" % guessed_outputfilepath) sourcemap_output_filepath = path.join( path.dirname(kw['output_path']), path.basename(sourcemap_file.name) ) if not path.exists(path.dirname(sourcemap_output_filepath)): os.mkdir(path.dirname(sourcemap_output_filepath)) sourcemap_output_file = open(sourcemap_output_filepath, 'w') sourcemap_output_file.write(sourcemap_file.read()) sourcemap_file.close() try: contents = output_file.read() out.write(contents) finally: output_file.close() finally: # Restore previous working dir os.chdir(old_wd) # Clean up the temp dir shutil.rmtree(tempout)
def option__deepcopy__(value, memo): """Custom deepcopy implementation for ``option`` class.""" return option(copy.deepcopy(value[0]), copy.deepcopy(value[1]), copy.deepcopy(value[2]))
class Compass(Filter): """Converts `Compass <http://compass-style.org/>`_ .sass files to CSS. Requires at least version 0.10. To compile a standard Compass project, you only need to have to compile your main ``screen.sass``, ``print.sass`` and ``ie.sass`` files. All the partials that you include will be handled by Compass. If you want to combine the filter with other CSS filters, make sure this one runs first. Supported configuration options: COMPASS_BIN The path to the Compass binary. If not set, the filter will try to run ``compass`` as if it's in the system path. COMPASS_PLUGINS Compass plugins to use. This is equivalent to the ``--require`` command line option of the Compass. and expects a Python list object of Ruby libraries to load. """ name = 'compass' max_debug_level = None options = { 'compass': ('binary', 'COMPASS_BIN'), 'plugins': option('COMPASS_PLUGINS', type=list) } def open(self, out, source_path, **kw): """Compass currently doesn't take data from stdin, and doesn't allow us accessing the result from stdout either. Also, there's a bunch of other issues we need to work around: - compass doesn't support given an explict output file, only a "--css-dir" output directory. We have to "guess" the filename that will be created in that directory. - The output filename used is based on the input filename, and simply cutting of the length of the "sass_dir" (and changing the file extension). That is, compass expects the input filename to always be inside the "sass_dir" (which defaults to ./src), and if this is not the case, the output filename will be gibberish (missing characters in front). See: https://github.com/chriseppstein/compass/issues/304 We fix this by setting the proper --sass-dir option. - Compass insists on creating a .sass-cache folder in the current working directory, and unlike the sass executable, there doesn't seem to be a way to disable it. The workaround is to set the working directory to our temp directory, so that the cache folder will be deleted at the end. """ tempout = tempfile.mkdtemp() # Temporarily move to "tempout", so .sass-cache will be created there old_wd = os.getcwdu() os.chdir(tempout) try: # Make sure to use normpath() to not cause trouble with # compass' simplistic path handling, where it just assumes # source_path is within sassdir, and cuts off the length of # sassdir from the input file. sassdir = path.normpath(path.dirname(source_path)) source_path = path.normpath(source_path) # Compass offers some helpers like image-url(), which need # information about the urls under which media files will be # available. This is hard for two reasons: First, the options in # question aren't supported on the command line, so we need to write # a temporary config file. Secondly, the assume a defined and # separate directories for "images", "stylesheets" etc., something # webassets knows nothing of: we don't support the user defining # something such directories. Because we traditionally had this # filter point all type-specific directories to the root media # directory, we will define the paths to match this. In other # words, in Compass, both inline-image("img/test.png) and # image-url("img/test.png") will find the same file, and assume it # to be {env.directory}/img/test.png. # However, this partly negates the purpose of an utiility like # image-url() in the first place - you not having to hard code # the location of your images. So a possiblity for the future # might be adding options that allow changing this behavior (see # ticket #36). # # Note that is also the --relative-assets option, which we can't # use because it calculates an actual relative path between the # image and the css output file, the latter being in a temporary # directory in our case. config_file = path.join(tempout, '.config.rb') f = open(config_file, 'w') try: f.write(""" http_path = "%s" http_images_dir = "" http_stylesheets_dir = "" http_fonts_dir = "" http_javascripts_dir = "" """ % self.env.url) f.flush() finally: f.close() command = [self.compass or 'compass', 'compile'] for plugin in self.plugins or []: command.extend(('--require', plugin)) command.extend([ '--sass-dir', sassdir, '--css-dir', tempout, '--image-dir', self.env.directory, '--config', config_file, '--quiet', '--boring', '--output-style', 'expanded', source_path ]) proc = subprocess.Popen( command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, # shell: necessary on windows to execute # ruby files, but doesn't work on linux. shell=(os.name == 'nt')) stdout, stderr = proc.communicate() # compass seems to always write a utf8 header? to stderr, so # make sure to not fail just because there's something there. if proc.returncode != 0: raise FilterError( ('compass: subprocess had error: stderr=%s, ' + 'stdout=%s, returncode=%s') % (stderr, stdout, proc.returncode)) guessed_outputfile = \ path.join(tempout, path.splitext(path.basename(source_path))[0]) f = open("%s.css" % guessed_outputfile) try: out.write(f.read()) finally: f.close() finally: # Restore previous working dir os.chdir(old_wd) # Clean up the temp dir shutil.rmtree(tempout)