Source code for engineer.themes.core

# coding=utf-8
import logging

from brownie.caching import memoize, cached_property
from jinja2.loaders import FileSystemLoader
from path import path
from webassets import Bundle, Environment as AssetsEnvironment
from webassets.loaders import YAMLLoader
import yaml

from engineer.conf import settings
from engineer.exceptions import ThemeNotFoundException
from engineer.util import get_class, mirror_folder, ensure_exists, urljoin, wrap_list

__author__ = 'Tyler Butler <tyler@tylerbutler.com>'


# noinspection PyNoneFunctionAssignment
# noinspection PyPep8Naming
[docs]class Theme(object): """ Creates a new theme object based on the contents of *theme_root_path*. """ def __init__(self, theme_root_path, **kwargs): self.logger = logging.getLogger('engineer.themes.Theme') self.root_path = path(theme_root_path) self.name = kwargs.get('name') self.id = kwargs.get('id', self.name.lower().replace(' ', '_')) self.description = kwargs.get('description', None) self.author = kwargs.get('author', None) self.website = kwargs.get('website', None) self.license = kwargs.get('license', None) self.self_contained = kwargs.get('self_contained', True) self.static_root = path(kwargs.get('static_root', self.root_path / 'static/')).abspath() self.template_root = path(kwargs.get('template_root', self.root_path / 'templates')).abspath() self.template_dirs = [self.template_root] self.use_precompiled_styles = kwargs.get('use_precompiled_styles', True) if (self.root_path / 'bundles.yaml').exists(): self.bundle_files = {'bundles.yaml'} else: self.bundle_files = set() self.bundle_files.update(wrap_list(kwargs.get('bundles', []))) # set up mappings for any additional content self.content_mappings = {} if 'copy_content' in kwargs: for item, target in iter(kwargs['copy_content']): item_path = path(item) source = path(self.root_path / item_path).abspath() if target is None: target = item_path self.content_mappings[source] = target if 'template_dirs' in kwargs: self.template_dirs.extend([path(self.root_path / t).abspath() for t in kwargs['template_dirs']]) # set the default theme settings values default_settings = kwargs.get('settings', None) if default_settings: for k, v in default_settings.iteritems(): setattr(self, k, v) # update the theme settings based on anything passed in via the site settings for k, v in settings.THEME_SETTINGS.iteritems(): setattr(self, k, v) @property def STATICFILE_DIR(self): return self.static_root @property def TEMPLATE_DIR(self): return self.template_root @property def template_loader(self): return FileSystemLoader(self.template_dirs) @cached_property def assets_environment(self): # because we're using the append_path function to add paths for the source files, the # directory argument will be used as the output path assets_env = AssetsEnvironment(directory=(settings.OUTPUT_STATIC_DIR / 'bundled'), url=urljoin(settings.STATIC_URL, 'bundled'), # cache=self.ENGINEER.WEBASSETS_CACHE_DIR, # TODO: this seems broken in webassets debug='merge' if settings.DEBUG else False) assets_env.config['less_bin'] = settings.LESS_PREPROCESSOR # register the global bundles assets_env.append_path(settings.ENGINEER.LIB_DIR, url=urljoin(settings.STATIC_URL, 'lib')) global_bundles = YAMLLoader(settings.ENGINEER.STATIC_DIR / 'engineer/lib/global_bundles.yaml').load_bundles() self._register_bundles(assets_env, global_bundles) # register code style bundles assets_env.append_path(settings.ENGINEER.THEMES_DIR / '_shared/code_styles', url=urljoin(settings.STATIC_URL, 'code')) self._register_bundles(assets_env, ThemeManager.code_style_bundles) # register the local bundles for bundle_file in self.bundle_files: bundle_yaml = self.root_path / bundle_file if bundle_yaml.exists(): assets_env.append_path(bundle_yaml.dirname().abspath()) loader = YAMLLoader(bundle_yaml.abspath()) local_bundles = loader.load_bundles(assets_env) self._register_bundles(assets_env, local_bundles) else: self.logger.warning("Cannot find bundle file %s for theme %s." % (bundle_file, self.id)) return assets_env @staticmethod def _register_bundles(assets_env, bundle_iterable): for name, bundle in bundle_iterable.iteritems(): assets_env.register(name, bundle) def theme_path(self, template): if (self.template_root / template).abspath().exists(): return str(self.template_root / template) else: return template def copy_content(self, output_path): # Copy theme static content to output dir try: s = self.static_root.abspath() if s.exists(): t = path(output_path).abspath() ensure_exists(t) # noinspection PyUnboundLocalVariable mirror_folder(s, t) except ThemeNotFoundException as e: self.logger.critical(e.message) exit() def copy_related_content(self, output_path): if self.content_mappings: for s, t in self.content_mappings.iteritems(): t = path(output_path / t).abspath() if s.isdir(): mirror_folder(s, t) else: s.copy(ensure_exists(t)) def copy_all_content(self, output_dir): self.copy_content(output_dir) self.copy_related_content(output_dir) @staticmethod def from_yaml(yaml_file): with open(yaml_file, mode='rb') as the_file: yaml_doc = yaml.load(the_file.read()) theme = Theme(path(yaml_file).dirname(), **yaml_doc) return theme def __str__(self): return unicode(self).encode('utf-8') def __unicode__(self): return self.name
class ThemeManager(object): @classmethod @memoize def themes(cls): themes = [] for f in settings.THEME_FINDERS: finder = get_class(f) themes.extend(finder.get_themes()) # noinspection PyTypeChecker return dict([t.id, t] for t in themes) @classmethod @memoize def themes_by_finder(cls): themes = {} for f in settings.THEME_FINDERS: finder = get_class(f) themes[f] = finder.get_themes() return themes @classmethod @memoize def current_theme(cls): theme = ThemeManager.themes().get(settings.THEME) if theme is not None: return theme else: raise ThemeNotFoundException("Theme with id '%s' cannot be found." % settings.THEME) @staticmethod @memoize def theme_path(template): return path(ThemeManager.current_theme().template_root) / template # noinspection PyShadowingBuiltins @staticmethod @memoize def theme(id): if id not in ThemeManager.themes(): raise ThemeNotFoundException("Theme with id '%s' cannot be found." % id) else: return ThemeManager.themes()[id] _codestyle_list = (settings.ENGINEER.THEMES_DIR / '_shared/code_styles/').files('*.css') code_style_bundles = dict([('codestyle_%s' % name.namebase, Bundle(name, output=('codestyle_%s' % name.namebase) + '.%(version)s.css')) for name in _codestyle_list])