Source code for ievv_opensource.utils.ievvbuildstatic.config

import json
import logging
import os
import shutil
import time
from collections import OrderedDict

from django.apps import apps
from ievv_opensource.utils.ievvbuildstatic import filepath
from ievv_opensource.utils.ievvbuildstatic.installers.yarn import YarnInstaller
from ievv_opensource.utils.ievvbuildstatic.watcher import WatchConfigPool
from ievv_opensource.utils.logmixin import LogMixin, Logger

from . import docbuilders


[docs]class App(LogMixin): """ Configures how ``ievv buildstatic`` should build the static files for a Django app. """ def __init__(self, appname, version, plugins, sourcefolder='staticsources', destinationfolder='static', keep_temporary_files=False, installers_config=None, docbuilder_classes=None, default_skipgroups=None, default_includegroups=None): """ Parameters: appname: Django app label (I.E.: ``myproject.myapp``). plugins: Zero or more :class:`ievv_opensource.utils.ievvbuild.pluginbase.Plugin` objects. sourcefolder: The folder relative to the app root folder where static sources (I.E.: less, coffescript, ... sources) are located. Defaults to ``staticsources``. """ self.apps = None self.version = version self.appname = appname self.sourcefolder = sourcefolder self.destinationfolder = destinationfolder self.installers = {} self.plugins = [] self.docbuilder_classes = docbuilder_classes or self.get_default_docbuilder_classes() self.keep_temporary_files = keep_temporary_files self.default_skipgroups = default_skipgroups or [] self.default_includegroups = default_includegroups or [] self.installers_config = self._make_installers_config( installers_config_overrides=installers_config) for plugin in plugins: self.add_plugin(plugin) def _make_installers_config(self, installers_config_overrides): installers_config = { 'npm': { 'installer_class': YarnInstaller }, } installers_config_overrides = installers_config_overrides or {} for alias, config in installers_config_overrides.items(): overrides = installers_config_overrides.get(alias, {}) if alias not in installers_config: installers_config[alias] = {} installers_config[alias].update(overrides) return installers_config def get_options_classes(self): options_classes = [] for installer_config in self.installers_config.values(): options_classes.append(installer_config['installer_class']) for plugin in self.plugins: options_classes.append(plugin.__class__) return options_classes
[docs] def add_plugin(self, plugin): """ Add a :class:`ievv_opensource.utils.ievvbuildstatic.lessbuild.Plugin`. """ plugin.app = self self.plugins.append(plugin)
def iterplugins(self, skipgroups=None, includegroups=None): skipgroups = skipgroups or self.default_skipgroups includegroups = includegroups or self.default_includegroups skipgroups = {group for group in skipgroups if group not in includegroups} for plugin in self.plugins: if includegroups and plugin.group not in includegroups: continue if plugin.group and plugin.group in skipgroups: continue yield plugin
[docs] def run(self, skipgroups=None, includegroups=None): """ Run :meth:`ievv_opensource.utils.ievvbuildstatic.pluginbase.Plugin.run` for all plugins within the app. """ for plugin in self.iterplugins(skipgroups=skipgroups, includegroups=includegroups): plugin.runwrapper()
def _make_json_appconfig_dict(self): return { 'appname': self.appname, 'version': self.version, 'sourcefolder': self.get_source_path(), 'destinationfolder': self.get_destination_path(), 'keep_temporary_files': self.keep_temporary_files, 'is_in_production_mode': self.apps.is_in_production_mode() } def _get_json_appconfig_path(self): return self.get_source_path('ievv_buildstatic.appconfig.json') def _save_json_appconfig(self, appconfig_dict): config_path = self._get_json_appconfig_path() self.get_logger().debug('Creating {config_path}'.format(config_path=config_path)) open(config_path, 'w').write( json.dumps(appconfig_dict, indent=2, sort_keys=True) ) def add_pluginconfig_to_json_config(self, plugin_name, config_dict): appconfig_dict = json.loads(open(self._get_json_appconfig_path(), 'r').read()) appconfig_dict[plugin_name] = config_dict self._save_json_appconfig(appconfig_dict=appconfig_dict)
[docs] def install(self, skipgroups=None, includegroups=None): """ Run :meth:`ievv_opensource.utils.ievvbuildstatic.pluginbase.Plugin.install` for all plugins within the app. """ self._save_json_appconfig(appconfig_dict=self._make_json_appconfig_dict()) for alias in self.installers_config.keys(): installer = self.get_installer(alias=alias) installer.initialize() for plugin in self.iterplugins(skipgroups=skipgroups, includegroups=includegroups): plugin.install() for installer in self.installers.values(): installer.install() for plugin in self.iterplugins(skipgroups=skipgroups, includegroups=includegroups): plugin.post_install()
[docs] def get_app_config(self): """ Get the AppConfig for the Django app. """ if not hasattr(self, '_app_config'): self._app_config = apps.get_app_config(self.appname) return self._app_config
[docs] def get_appfolder(self): """ Get the absolute path to the Django app root folder. """ return self.get_app_config().path
[docs] def get_app_path(self, apprelative_path): """ Returns the path to the directory joined with the given ``apprelative_path``. """ return os.path.join(self.get_appfolder(), apprelative_path)
def _relative_or_absolute_path_to_absolute_path(self, relative_path_root, pathlist): if len(pathlist) == 1 and isinstance(pathlist[0], filepath.FilePathInterface): return pathlist[0].abspath else: if pathlist: return os.path.join(relative_path_root, *pathlist) else: return relative_path_root
[docs] def get_source_path(self, *path): """ Returns the absolute path to a folder within the source folder of this app or another app. Examples: Get the source path for a coffeescript file:: self.get_source_path('mylib', 'app.coffee') Getting the path of a source file within another app using a :class:`ievv_opensource.utils.ievvbuildstatic.filepath.SourcePath` object (a subclass of :class:`ievv_opensource.utils.ievvbuildstatic.filepath.FilePathInterface`) as the path:: self.get_source_path( ievvbuildstatic.filepath.SourcePath('myotherapp', 'scripts', 'typescript', 'app.ts')) Args: *path: Zero or more strings to specify a path relative to the source folder of this app - same format as :func:`os.path.join`. A single :class:`ievv_opensource.utils.ievvbuildstatic.filepath.FilePathInterface` object to specify an absolute path. """ sourcefolder = os.path.join(self.get_app_path(self.sourcefolder), self.appname) return self._relative_or_absolute_path_to_absolute_path(relative_path_root=sourcefolder, pathlist=path)
def make_source_relative_path(self, *path): return os.path.relpath(self.get_source_path(*path), start=self.get_source_path())
[docs] def get_destination_path(self, *path, **kwargs): """ Returns the absolute path to a folder within the destination folder of this app or another app. Examples: Get the destination path for a coffeescript file - extension is changed from ``.coffee`` to ``.js``:: self.get_destination_path('mylib', 'app.coffee', new_extension='.js') Getting the path of a destination file within another app using a :class:`ievv_opensource.utils.ievvbuildstatic.filepath.SourcePath` object (a subclass of :class:`ievv_opensource.utils.ievvbuildstatic.filepath.FilePathInterface`) as the path:: self.get_destination_path( ievvbuildstatic.filepath.DestinationPath( 'myotherapp', '1.1.0', 'scripts', 'typescript', 'app.ts'), new_extension='.js') Args: path: Path relative to the source folder. Same format as ``os.path.join()``. A single :class:`ievv_opensource.utils.ievvbuildstatic.filepath.FilePathInterface` object to specify an absolute path. new_extension: A new extension to give the destination path. See example below. """ new_extension = kwargs.get('new_extension', None) destinationfolder = os.path.join( self.get_app_path(self.destinationfolder), self.appname, self.version) absolute_path = self._relative_or_absolute_path_to_absolute_path(relative_path_root=destinationfolder, pathlist=path) if new_extension is not None: path, extension = os.path.splitext(absolute_path) absolute_path = '{}{}'.format(path, new_extension) return absolute_path
[docs] def watch(self, skipgroups=None, includegroups=None): """ Start a watcher thread for each plugin. """ watchconfigs = [] for plugin in self.iterplugins(skipgroups=skipgroups, includegroups=includegroups): watchconfig = plugin.watch() if watchconfig: watchconfigs.append(watchconfig) return watchconfigs
def iterinstallers(self): return self.installers.values()
[docs] def get_installer(self, alias): """ Get an instance of the installer configured with the provided ``alias``. Parameters: alias: A subclass of . Returns: ievv_opensource.utils.ievvbuildstatic.installers.base.AbstractInstaller: An instance of the requested installer. """ installer_class = self.installers_config[alias]['installer_class'] if installer_class.name not in self.installers: installer = installer_class(app=self) self.installers[installer_class.name] = installer return self.installers[installer_class.name]
[docs] def get_logger_name(self): return '{}.{}'.format(self.apps.get_logger_name(), self.appname)
def get_loglevel(self): return self.apps.loglevel def get_temporary_build_directory_path(self, *path): return self.get_source_path('ievvbuildstatic_temporary_build_directory', *path)
[docs] def make_temporary_build_directory(self, name): """ Make a temporary directory that you can use for building something. Returns: str: The absolute path of the new directory. """ self._delete_temporary_build_directory(name) temporary_directory_path = self.get_temporary_build_directory_path(name) os.makedirs(temporary_directory_path) return temporary_directory_path
def _delete_temporary_build_directory(self, name): """ Delete a temporary directory created with :meth:`.make_temporary_build_directory`. """ temporary_directory_path = self.get_temporary_build_directory_path(name) if os.path.exists(temporary_directory_path): shutil.rmtree(temporary_directory_path) base_temporary_directory = self.get_temporary_build_directory_path() if os.path.exists(base_temporary_directory) and len(os.listdir(base_temporary_directory)) == 0: shutil.rmtree(base_temporary_directory) def get_default_docbuilder_classes(self): return [ docbuilders.npm_docbuilder.NpmDocBuilder ] def get_docbuilder(self): for docbuilder_class in self.docbuilder_classes: docbuilder = docbuilder_class(app=self) if docbuilder.is_available(): return docbuilder return None def build_docs(self, output_directory): docbuilder = self.get_docbuilder() if docbuilder: docbuilder.build_docs(output_directory=os.path.join(output_directory, self.appname)) else: self.get_logger().debug( 'No docbuilders found for {appname}. Tried the following docbuilders: {docbuilders}'.format( appname=self.appname, docbuilders=', '.join( docbuilder_class.name for docbuilder_class in self.docbuilder_classes) )) def add_deferred_success(self, message): self.apps.add_deferred_success('[{}] {}'.format(self.appname, message)) def add_deferred_warning(self, message): self.apps.add_deferred_warning('[{}] {}'.format(self.appname, message))
[docs]class Apps(LogMixin): """ Basically a list around :class:`.App` objects. """ MODE_DEVELOP = 'develop' MODE_PRODUCTION = 'production' def __init__(self, *apps, **kwargs): """ Parameters: apps: :class:`.App` objects to add initially. Uses :meth:`.add_app` to add the apps. """ self.apps = OrderedDict() self.loglevel = Logger.DEBUG self.command_error_message = None self.help_header = kwargs.pop('help_header', None) self.mode = self.MODE_DEVELOP self._warnings = [] self._successes = [] self.options = {} for app in apps: self.add_app(app) def get_loglevel(self): return self.loglevel def get_command_error_message(self): return self.command_error_message
[docs] def add_app(self, app): """ Add an :class:`.App`. """ app.apps = self self.apps[app.appname] = app
[docs] def get_app(self, appname): """ Get app by appname. """ return self.apps[appname]
[docs] def install(self, appnames=None, skipgroups=None, includegroups=None): """ Run :meth:`ievv_opensource.utils.ievvbuildstatic.pluginbase.Plugin.install` for all plugins within all :class:`apps <.App>`. """ for app in self.iterapps(appnames=appnames): app.install(skipgroups=skipgroups, includegroups=includegroups)
def log_help_header(self): if self.help_header: self.get_logger().infobox(self.help_header) def validate_appnames(self, appnames): for appname in appnames: if appname not in self.apps: raise ValueError('Invalid appname: {}'.format(appname))
[docs] def iterapps(self, appnames=None): """ Get an interator over the apps. """ for app in self.apps.values(): include = not appnames or app.appname in appnames if include: yield app
[docs] def run(self, appnames=None, skipgroups=None, includegroups=None): """ Run :meth:`ievv_opensource.utils.ievvbuildstatic.pluginbase.Plugin.run` for all plugins within all :class:`apps <.App>`. """ for app in self.iterapps(appnames=appnames): app.run(skipgroups=skipgroups, includegroups=includegroups) self.log_deferred_messages()
[docs] def watch(self, appnames=None, skipgroups=None, includegroups=None): """ Start watcher threads for all folders that at least one :class:`plugin <ievv_opensource.utils.ievvbuildstatic.pluginbase.Plugin>` within any of the :class:`apps <.App>` has configured to be watched for changes. Blocks until ``CTRL-c`` is pressed. """ watchconfigpool = WatchConfigPool() for app in self.iterapps(appnames=appnames): watchconfigpool.extend(app.watch(skipgroups=skipgroups, includegroups=includegroups)) all_observers = watchconfigpool.watch() try: while True: time.sleep(1) except KeyboardInterrupt: for observer in all_observers: observer.stop() for observer in all_observers: observer.join()
def build_docs(self, output_directory, appnames=None): for app in self.iterapps(appnames=appnames): app.build_docs(output_directory=output_directory)
[docs] def get_logger_name(self): return 'ievvbuildstatic'
def __configure_shlogger(self, loglevel, handler): shlogger = logging.getLogger('sh.command') shlogger.setLevel(loglevel) shlogger.addHandler(handler) shlogger.propagate = False def configure_logging(self, loglevel=None, shlibrary_loglevel=logging.WARNING, command_error_message=None): handler = logging.StreamHandler() self.__configure_shlogger(loglevel=shlibrary_loglevel, handler=handler) self.loglevel = loglevel self.command_error_message = command_error_message def set_development_mode(self): self.mode = self.MODE_DEVELOP def set_production_mode(self): self.mode = self.MODE_PRODUCTION def is_in_production_mode(self): return self.mode == self.MODE_PRODUCTION def add_deferred_warning(self, message): self._warnings.append(message) def add_deferred_success(self, message): self._successes.append(message) def log_deferred_messages(self): if self._successes: self.get_logger().success('Success messages:') for message in set(self._successes): self.get_logger().success('- {}'.format(message)) if self._warnings: self.get_logger().warning('Warnings:') for message in set(self._warnings): self.get_logger().warning('- {}'.format(message)) self._successes = [] self._warnings = [] def add_cli_arguments(self, parser): options_classes = set() for app in self.iterapps(): options_classes.update(app.get_options_classes()) for option_class in options_classes: option_class.add_cli_arguments(parser=parser) def set_options(self, options): self.options = options