Source code for optimus.pages.builder

# -*- coding: utf-8 -*-
import logging
import io
import os

from jinja2 import Environment as Jinja2Environment
from jinja2 import FileSystemLoader
from webassets.ext.jinja2 import AssetsExtension

# Optional babel import
try:
    from babel import support as babel_support
except ImportError:
    babel_support = None

from optimus import __version__
from optimus.pages.registry import PageRegistry
from optimus.exceptions import ViewImproperlyConfigured


[docs]class PageBuilder(object): """ Builder class to init Jinja2 environment and build given pages. Arguments: settings (conf.model.SettingsModel): Settings registry instance. Keyword Arguments: jinja_env (jinja2.Jinja2Environment): Jinja2 environment. Default is ``None``. assets_env (webassets.Environment): Webasset environment. Default is ``None``. dry_run (boolean): Enable dry run mode. Default is ``False``. Attributes: logger (logging.Logger): Optimus logger. settings (conf.model.SettingsModel): Settings registry instance. jinja_env (jinja2.Jinja2Environment): Jinja2 environment. Default is ``None``. assets_env (webassets.Environment): Webasset environment. Default is ``None``. internationalized (boolean): Indicate it internationalization is enabled. Will be automatically set to ``True`` if Jinja environment enable the i18n extension. translations (dict): Dictionnary of translation catalog indexed on language identifier. registry (optimus.pages.registry.PageRegistry): Registry of all knowed page from scanning. dry_run (boolean): Dry run mode. """ def __init__(self, settings, jinja_env=None, assets_env=None, dry_run=False): self.logger = logging.getLogger("optimus") self.settings = settings self.assets_env = assets_env self.internationalized = False self.translations = {} self.jinja_env = jinja_env or self.get_environnement(assets_env) self.jinja_env.globals.update(self.get_globals()) self.logger.debug("PageBuilder initialized") self.registry = PageRegistry() self.dry_run = dry_run
[docs] def get_environnement(self, assets_env=None): """ Init and configure Jinja environment. Automatically enable some extensions and link possible asset environment. Keyword Arguments: assets_env (webassets.Environment): Webasset environment. Default is ``None``. If empty, webassets will not be available from page templates. Returns: jinja2.Jinja2Environment: Configured Jinja2 environment. """ exts = [] self.logger.debug( ("No Jinja2 environment given, initializing a " "new environment") ) # It the assets environment is given, active the Jinja extension to # use webassets if self.assets_env is not None: exts.append(AssetsExtension) # Enabled Jinja extensions for ext in self.settings.JINJA_EXTENSIONS: exts.append(ext) # Active i18n behaviors if i18n extension is loaded and Babel has been # imported if "jinja2.ext.i18n" in exts and babel_support is not None: self.internationalized = True self.logger.debug("'i18n' enabled") # Boot Jinja environment env = Jinja2Environment( loader=FileSystemLoader(self.settings.TEMPLATES_DIR), extensions=exts ) # Enable Jinja filters for name, module in self.settings.JINJA_FILTERS.items(): env.filters[name] = module if assets_env: env.assets_environment = assets_env return env
[docs] def serialize_settings(self): """ Get and return valid settings variables. Valid settings means only variable names full uppercase. Returns: dict: Settings variables. """ items = {} for item in dir(self.settings): if not item.startswith("_") and item.isupper(): items[item] = getattr(self.settings, item) return items
[docs] def get_globals(self): """ Get global context variables. Returns: dict: Shortcuts to some common settings like ``SITE`` options, static urls, Optimus version and finally all settings contained in ``_SETTINGS``. """ domain_prefix = "http://{}" if self.settings.HTTPS_ENABLED: domain_prefix = "https://{}" return { "debug": self.settings.DEBUG, "SITE": { "name": self.settings.SITE_NAME, "domain": self.settings.SITE_DOMAIN, "web_url": domain_prefix.format(self.settings.SITE_DOMAIN), }, "STATIC_URL": self.settings.STATIC_URL, "_SETTINGS": self.serialize_settings(), "OPTIMUS": __version__, }
[docs] def get_translation_for_item(self, page_item): """ Try to load the translations for the page language if any, then install it in Jinja2. It does not reload a language translations if a previous page has allready loaded it. Arguments: page_item (optimus.pages.views.PageViewBase): Page instance which its language identifier will be used to search for translation catalog. Returns: babel.support.Translations: Translations object to give to Jinja i18n extension. """ if not babel_support: return None # Get page language object lang = page_item.get_lang() # Dont load again allredy registered catalog if lang.code not in self.translations: msg = " - Loading translations for locale: {} - {}" self.logger.debug(msg.format(lang.code, lang)) self.translations[lang.code] = babel_support.Translations.load( self.settings.LOCALES_DIR, lang.code, "messages" ) # Install it in the Jinja env self.jinja_env.install_gettext_translations( self.translations[lang.code], newstyle=False ) return self.translations[lang.code]
[docs] def scan_item(self, page_item): """ Scan given page to retrieve template dependancies. Possibly connect settings to page instance if not allready done. Arguments: page_item (optimus.pages.views.PageViewBase): Page instance. Returns: string: All used templates from given page. """ # Connect stored settings to page if not allready set try: page_item.settings except ViewImproperlyConfigured: page_item.settings = self.settings msg = " Scanning page: {}" self.logger.info(msg.format(page_item.get_destination())) return page_item.introspect(self.jinja_env)
[docs] def scan_bulk(self, page_list): """ Scan all given pages to set their dependancies TODO: Implement a 'settings' kwarg to pass to scan_item to connect settings object to view. Arguments: page_list (list): List of page instances. Returns: list: Every template name involved in scanned page instances. """ self.logger.info("Starting page builds") if not page_list: self.logger.warning( ("Page scanning skipped as there are no " "registered pages") ) return None knowed = set([]) for page in page_list: found = self.scan_item(page) self.registry.add_page(page, found) knowed.update(found) return knowed
[docs] def build_item(self, page_item): """ Build given page. Possibly connect settings to page instance if not allready done. Arguments: page_item (optimus.pages.views.PageViewBase): Page instance. Returns: string: Destination path from builded page. """ # Connect stored settings to page if not allready set try: page_item.settings except ViewImproperlyConfigured: page_item.settings = self.settings msg = " Building page: {}" self.logger.info(msg.format(page_item.get_destination())) # Optional i18n if self.internationalized: self.get_translation_for_item(page_item) # Template render content = page_item.render(self.jinja_env) destination_path = os.path.join( self.settings.PUBLISH_DIR, page_item.get_destination() ) # Creating destination path if needed destination_dir, destination_file = os.path.split(destination_path) if not os.path.exists(destination_dir): msg = " - Creating new directory : {}" self.logger.debug(msg.format(destination_dir)) if not self.dry_run: os.makedirs(destination_dir) # Write it self.logger.debug(" - Writing to: {}".format(destination_path)) if not self.dry_run: with io.open(destination_path, "w") as fp: fp.write(content) return destination_path
[docs] def build_bulk(self, page_list): """ Build all given pages. Return all the effective builded pages Arguments: page_list (list): List of page instances. Returns: list: List of destination paths from builded pages. """ self.logger.info("Starting page builds") if not page_list: self.logger.warning( ("Page management skipped as there are no " "registered pages") ) return None builded = [] for page in page_list: builded.append(self.build_item(page)) return builded