Source code for hyde.ext.plugins.css

# -*- coding: utf-8 -*-
"""
CSS plugins
"""

from hyde._compat import str
from hyde.plugin import CLTransformer, Plugin
from hyde.exceptions import HydeException

import os
import re
import subprocess
import sys

from fswrap import File

#
# Less CSS
#


[docs]class LessCSSPlugin(CLTransformer): """ The plugin class for less css """ def __init__(self, site): super(LessCSSPlugin, self).__init__(site) self.import_finder = \ re.compile('^\\s*@import\s+(?:\'|\")([^\'\"]*)(?:\'|\")\s*\;\s*$', re.MULTILINE) @property def executable_name(self): return "lessc" def _should_parse_resource(self, resource): """ Check user defined """ return resource.source_file.kind == 'less' and \ getattr(resource, 'meta', {}).get('parse', True) def _should_replace_imports(self, resource): return getattr(resource, 'meta', {}).get('uses_template', True) def begin_site(self): """ Find all the less css files and set their relative deploy path. """ for resource in self.site.content.walk_resources(): if self._should_parse_resource(resource): new_name = resource.source_file.name_without_extension + ".css" target_folder = File(resource.relative_deploy_path).parent resource.relative_deploy_path = target_folder.child(new_name) def begin_text_resource(self, resource, text): """ Replace @import statements with {% include %} statements. """ if not self._should_parse_resource(resource) or \ not self._should_replace_imports(resource): return text def import_to_include(match): if not match.lastindex: return '' path = match.groups(1)[0] afile = File(resource.source_file.parent.child(path)) if len(afile.kind.strip()) == 0: afile = File(afile.path + '.less') ref = self.site.content.resource_from_path(afile.path) if not ref: raise HydeException( "Cannot import from path [%s]" % afile.path) ref.is_processable = False return self.template.get_include_statement(ref.relative_path) text = self.import_finder.sub(import_to_include, text) return text @property def plugin_name(self): """ The name of the plugin. """ return "less" def text_resource_complete(self, resource, text): """ Save the file to a temporary place and run less compiler. Read the generated file and return the text as output. Set the target path to have a css extension. """ if not self._should_parse_resource(resource): return supported = [ "verbose", ("silent", "s"), ("compress", "x"), "O0", "O1", "O2", "include-path=" ] less = self.app source = File.make_temp(text) target = File.make_temp('') args = [str(less)] args.extend(self.process_args(supported)) args.extend([str(source), str(target)]) try: self.call_app(args) except subprocess.CalledProcessError: HydeException.reraise( "Cannot process %s. Error occurred when " "processing [%s]" % (self.app.name, resource.source_file), sys.exc_info()) return target.read_all()
# # Stylus CSS # class StylusPlugin(CLTransformer): """ The plugin class for stylus css """ def __init__(self, site): super(StylusPlugin, self).__init__(site) self.import_finder = \ re.compile('^\\s*@import\s+(?:\'|\")([^\'\"]*)(?:\'|\")\s*\;?\s*$', re.MULTILINE) def begin_site(self): """ Find all the styl files and set their relative deploy path. """ for resource in self.site.content.walk_resources(): if resource.source_file.kind == 'styl': new_name = resource.source_file.name_without_extension + ".css" target_folder = File(resource.relative_deploy_path).parent resource.relative_deploy_path = target_folder.child(new_name) def begin_text_resource(self, resource, text): """ Replace @import statements with {% include %} statements. """ if not resource.source_file.kind == 'styl': return def import_to_include(match): """ Converts a css import statement to include statement. """ if not match.lastindex: return '' path = match.groups(1)[0] first_child = resource.source_file.parent.child(path) afile = File(File(first_child).fully_expanded_path) if len(afile.kind.strip()) == 0: afile = File(afile.path + '.styl') ref = self.site.content.resource_from_path(afile.path) if not ref: try: include = self.settings.args.include except AttributeError: include = False if not include: raise HydeException( "Cannot import from path [%s]" % afile.path) else: ref.is_processable = False return "\n" + \ self.template.get_include_statement(ref.relative_path) + \ "\n" return '@import "' + path + '"\n' text = self.import_finder.sub(import_to_include, text) return text @property def defaults(self): """ Returns `compress` if not in development mode. """ try: mode = self.site.config.mode except AttributeError: mode = "production" defaults = {"compress": ""} if mode.startswith('dev'): defaults = {} return defaults @property def plugin_name(self): """ The name of the plugin. """ return "stylus" def text_resource_complete(self, resource, text): """ Save the file to a temporary place and run stylus compiler. Read the generated file and return the text as output. Set the target path to have a css extension. """ if not resource.source_file.kind == 'styl': return stylus = self.app source = File.make_temp(text.strip()) supported = [("compress", "c"), ("include", "I")] args = [str(stylus)] args.extend(self.process_args(supported)) args.append(str(source)) try: self.call_app(args) except subprocess.CalledProcessError: HydeException.reraise( "Cannot process %s. Error occurred when " "processing [%s]" % (stylus.name, resource.source_file), sys.exc_info()) target = File(source.path + '.css') return target.read_all() # # Clever CSS # class CleverCSSPlugin(Plugin): """ The plugin class for CleverCSS """ def __init__(self, site): super(CleverCSSPlugin, self).__init__(site) try: import clevercss except ImportError as e: raise HydeException('Unable to import CleverCSS: ' + e.message) else: self.clevercss = clevercss def _should_parse_resource(self, resource): """ Check user defined """ return resource.source_file.kind == 'ccss' and \ getattr(resource, 'meta', {}).get('parse', True) def _should_replace_imports(self, resource): return getattr(resource, 'meta', {}).get('uses_template', True) def begin_site(self): """ Find all the clevercss files and set their relative deploy path. """ for resource in self.site.content.walk_resources(): if self._should_parse_resource(resource): new_name = resource.source_file.name_without_extension + ".css" target_folder = File(resource.relative_deploy_path).parent resource.relative_deploy_path = target_folder.child(new_name) def begin_text_resource(self, resource, text): """ Replace @import statements with {% include %} statements. """ if not self._should_parse_resource(resource) or \ not self._should_replace_imports(resource): return text import_finder = re.compile( '^\\s*@import\s+(?:\'|\")([^\'\"]*)(?:\'|\")\s*\;\s*$', re.MULTILINE) def import_to_include(match): if not match.lastindex: return '' path = match.groups(1)[0] afile = File(resource.source_file.parent.child(path)) if len(afile.kind.strip()) == 0: afile = File(afile.path + '.ccss') ref = self.site.content.resource_from_path(afile.path) if not ref: raise HydeException( "Cannot import from path [%s]" % afile.path) ref.is_processable = False return self.template.get_include_statement(ref.relative_path) text = import_finder.sub(import_to_include, text) return text def text_resource_complete(self, resource, text): """ Run clevercss compiler on text. """ if not self._should_parse_resource(resource): return return self.clevercss.convert(text, self.settings) # # Sassy CSS # class SassyCSSPlugin(Plugin): """ The plugin class for SassyCSS """ def __init__(self, site): super(SassyCSSPlugin, self).__init__(site) try: import scss except ImportError as e: raise HydeException('Unable to import pyScss: ' + e.message) else: self.scss = scss def _should_parse_resource(self, resource): """ Check user defined """ return resource.source_file.kind == 'scss' and \ getattr(resource, 'meta', {}).get('parse', True) @property def options(self): """ Returns options depending on development mode """ try: mode = self.site.config.mode except AttributeError: mode = "production" debug = mode.startswith('dev') opts = {'compress': not debug, 'debug_info': debug} site_opts = self.settings.get('options', {}) opts.update(site_opts) return opts @property def vars(self): """ Returns scss variables. """ return self.settings.get('vars', {}) @property def includes(self): """ Returns scss load paths. """ return self.settings.get('includes', []) def begin_site(self): """ Find all the sassycss files and set their relative deploy path. """ self.scss.STATIC_URL = self.site.content_url('/') self.scss.STATIC_ROOT = self.site.config.content_root_path.path self.scss.ASSETS_URL = self.site.media_url('/') self.scss.ASSETS_ROOT = self.site.config.deploy_root_path.child( self.site.config.media_root) for resource in self.site.content.walk_resources(): if self._should_parse_resource(resource): new_name = resource.source_file.name_without_extension + ".css" target_folder = File(resource.relative_deploy_path).parent resource.relative_deploy_path = target_folder.child(new_name) def text_resource_complete(self, resource, text): """ Run sassycss compiler on text. """ if not self._should_parse_resource(resource): return includes = [resource.node.path] + self.includes includes = [path.rstrip(os.sep) + os.sep for path in includes] options = self.options if 'load_paths' not in options: options['load_paths'] = [] options['load_paths'].extend(includes) scss = self.scss.Scss(scss_opts=options, scss_vars=self.vars) return scss.compile(text) # # Sass CSS # class SassPlugin(Plugin): """ The plugin class for LibSass """ def __init__(self, site): super(SassPlugin, self).__init__(site) try: import sass except ImportError as e: raise HydeException('Unable to import libsass: ' + e.message) else: self.sass = sass self.resources = [] def _should_parse_resource(self, resource): """ Check user defined """ files = self.site.config.get("sass", {}).get("files", []) return resource.source_file.kind == 'scss' and \ resource.relative_path in files @property def options(self): """ Returns options depending on development mode """ try: mode = self.site.config.mode except AttributeError: mode = "production" if 'sass' in self.site.config and \ 'output_style' in self.site.config.sass: output_style = self.site.config.sass.output_style else: debug = mode.startswith('dev') output_style = 'compressed' if not debug else 'nested' opts = {'output_style': output_style} site_opts = self.settings.get('options', {}) opts.update(site_opts) return opts @property def includes(self): """ Returns scss load paths. """ return self.settings.get('includes', []) def begin_site(self): """ Find all the sassycss files and set their relative deploy path. """ for resource in self.site.content.walk_resources(): if self._should_parse_resource(resource): new_name = resource.source_file.name_without_extension + ".css" target_folder = File(resource.relative_deploy_path).parent resource.relative_deploy_path = target_folder.child(new_name) self.resources.append(resource.relative_path) def text_resource_complete(self, resource, text): """ Run sassycss compiler on text. """ if resource.relative_path not in self.resources: return includes = [resource.node.path] + self.includes includes = [path.rstrip(os.sep) + os.sep for path in includes] options = self.options if 'include_paths' not in options: options['include_paths'] = [] options['include_paths'].extend(includes) self.logger.error(resource) try: return self.sass.compile(string=text, **options) except Exception as exc: self.logger.error(exc) raise