示例#1
0
文件: core.py 项目: spaetz/volt
class Plugin(object):

    """Plugin base class.

    Volt plugins are subclasses of Plugin that perform a set of operations
    to Unit objects of a given engine. They are executed after all
    Engines finish parsing their units and before any output files are
    written. Plugin execution is handled by the Generator object in
    volt.gen.

    During a Generator run, Volt tries first to look up a given plugin
    in the plugins directory in the project's root folder. Failing that,
    Volt will try to load the plugin from volt.plugins.

    Default settings for a Plugin object should be stored as a Config object
    set as a class attribute with the name DEFAULTS. Another class attribute
    named USER_CONF_ENTRY may also be defined. This tells the Plugin which
    Config object in the user's voltconf.py will be consolidated with the
    default configurations in DEFAULTS. Finally, all Plugin subclasses must
    implement a run method, which is the entry point for plugin execution
    by the Generator class.

    """

    __metaclass__ = abc.ABCMeta

    DEFAULTS = Config()

    USER_CONF_ENTRY = None

    def __init__(self):
        """Initializes Plugin."""

        self.config = Config(self.DEFAULTS)

    def prime(self):
        """Consolidates default plugin Config and user-defined Config."""

        # only override defaults if USER_CONF_ENTRY is defined
        if self.USER_CONF_ENTRY is not None:
            # get user config object
            conf_name = os.path.splitext(os.path.basename(CONFIG.VOLT.USER_CONF))[0]
            voltconf = path_import(conf_name, CONFIG.VOLT.ROOT_DIR)

            # use default Config if the user does not list any
            try:
                user_config = getattr(voltconf, self.USER_CONF_ENTRY)
            except AttributeError:
                user_config = Config()

            # to ensure proper Config consolidation
            if not isinstance(user_config, Config):
                raise TypeError("User Config object '%s' must be a Config instance." % self.USER_CONF_ENTRY)
            else:
                self.config.update(user_config)

    @abc.abstractmethod
    def run(self):
        """Runs the plugin."""
示例#2
0
文件: core.py 项目: spaetz/volt
    def __init__(self):
        """Initializes Plugin."""

        self.config = Config(self.DEFAULTS)
示例#3
0
文件: core.py 项目: nithinpb/volt
 def __init__(self):
     self.config = Config(self.DEFAULTS)
     self.logger.debug('created: %s' % type(self).__name__)
     self._templates = {}
     # attributes below are placeholders for template access later on
     self.widgets = {}
示例#4
0
文件: core.py 项目: nithinpb/volt
class Engine(LoggableMixin):

    """Base Volt Engine class.

    Engine is the core component of Volt that performs initial processing
    of each unit. This base engine class does not perform any processing by
    itself, but provides convenient unit processing methods for the
    subclassing engine.

    Any subclass of Engine must create a 'units' property and override the
    dispatch method. Optionally, the preprocess method may be overridden if any
    unit processing prior to plugin run needs to be performed.

    """

    __metaclass__ = abc.ABCMeta

    DEFAULTS = Config()

    def __init__(self):
        self.config = Config(self.DEFAULTS)
        self.logger.debug('created: %s' % type(self).__name__)
        self._templates = {}
        # attributes below are placeholders for template access later on
        self.widgets = {}

    def preprocess(self):
        """Performs initial processing of units before plugins are run."""
        pass

    @abc.abstractmethod
    def dispatch(self):
        """Performs final processing after all plugins are run."""

    @abc.abstractmethod
    def units(self):
        """Units of the engine."""

    def prime(self):
        """Consolidates default engine Config and user-defined Config.

        In addition to consolidating Config values, this method also sets
        the values of CONTENT_DIR, and *_TEMPLATE to absolute directory paths.

        """
        # get user config object
        conf_name = os.path.splitext(os.path.basename(CONFIG.VOLT.USER_CONF))[0]
        user_conf = path_import(conf_name, CONFIG.VOLT.ROOT_DIR)

        # custom engines must define an entry name for the user's voltconf
        if not hasattr (self, 'USER_CONF_ENTRY'):
            message = "%s must define a %s value as a class attribute." % \
                    (type(self).__name__, 'USER_CONF_ENTRY')
            self.logger.error(message)

        # use default config if the user does not specify any
        try:
            user_config = getattr(user_conf, self.USER_CONF_ENTRY)
        except AttributeError:
            user_config = Config()

        # to ensure proper Config consolidation
        if not isinstance(user_config, Config):
            message = "User Config object '%s' must be a Config instance." % \
                    self.USER_CONF_ENTRY
            self.logger.error(message)
            raise TypeError(message)
        else:
            self.config.update(user_config)

        # check attributes that must exist
        for attr in _REQUIRED_ENGINE_CONFIG:
            try:
                getattr(self.config, attr)
            except AttributeError:
                message = "%s Config '%s' value is undefined." % \
                        (type(self).__name__, attr)
                self.logger.error(message)
                self.logger.debug(format_exc())
                raise

        # set engine config paths to absolute paths
        self.config.CONTENT_DIR = os.path.join(CONFIG.VOLT.CONTENT_DIR, \
                self.config.CONTENT_DIR)
        for template in [x for x in self.config.keys() if x.endswith('_TEMPLATE')]:
                self.config[template] = os.path.join(CONFIG.VOLT.TEMPLATE_DIR, \
                        self.config[template])

    def chain_units(self):
        """Sets the previous and next permalink attributes of each unit."""
        chain_item_permalinks(self.units)
        self.logger.debug('done: chaining units')

    def sort_units(self):
        """Sorts a list of units according to the given header field name."""
        sort_key = self.config.SORT_KEY
        reversed = sort_key.startswith('-')
        sort_key = sort_key.strip('-')
        try:
            self.units.sort(key=lambda x: getattr(x, sort_key), reverse=reversed)
        except AttributeError:
            message = "Sort key '%s' not present in all units." % sort_key
            self.logger.error(message)
            self.logger.debug(format_exc())
            raise

        self.logger.debug("done: sorting units based on '%s'" % self.config.SORT_KEY)

    @cachedproperty
    def paginations(self):
        """Paginations of engine units in a dictionary.

        The computation will expand the supplied patterns according to the values
        present in all units. For example, if the pattern is '{time:%Y}' and
        there are five units with a datetime.year attribute 2010 and another
        five with 2011, create_paginations will return a dictionary with one key
        pointing to a list containing paginations for 'time/2010' and
        'time/2011'. The number of actual paginations vary, depending on how
        many units are in one pagination.

        """
        # check attributes that must exist
        for attr in _REQUIRED_ENGINE_PAGINATIONS:
            try:
                getattr(self.config, attr)
            except AttributeError:
                message = "%s Config '%s' value is undefined." % \
                        (type(self).__name__, attr)
                self.logger.error(message)
                self.logger.debug(format_exc())
                raise

        base_url = self.config.URL.strip('/')
        units_per_pagination = self.config.UNITS_PER_PAGINATION
        pagination_patterns = self.config.PAGINATIONS

        # create_paginations operates on self.units
        units = self.units
        if not units:
            warnings.warn("%s has no units to paginate." % type(self).__name__, \
                    EmptyUnitsWarning)
            # exit function if there's no units to process
            return {}

        paginator_map = {
                'all': self._paginate_all,
                'str': self._paginate_single,
                'int': self._paginate_single,
                'float': self._paginate_single,
                'list': self._paginate_multiple,
                'tuple': self._paginate_multiple,
                'datetime': self._paginate_datetime,
        }

        paginations = {}
        for pattern in pagination_patterns:

            perm_tokens = re.findall(_RE_PERMALINK, pattern.strip('/') + '/')
            base_permalist = [base_url] + perm_tokens

            # only the last token is allowed to be enclosed in '{}'
            for token in base_permalist[:-1]:
                if '{%s}' % token[1:-1] == token:
                    message = "Pagination pattern %s is invalid." % pattern
                    self.logger.error(message)
                    raise ValueError(message)

            # determine which paginate method to use based on field type
            last_token = base_permalist[-1]
            field = last_token[1:-1]
            if '{%s}' % field != last_token:
                field_type = 'all'
            else:
                sample = getattr(units[0], field.split(':')[0])
                field_type = sample.__class__.__name__

            try:
                paginate = paginator_map[field_type]
            except KeyError:
                message = "Pagination method for '%s' has not been " \
                          "implemented." % field_type
                self.logger.error(message)
                self.logger.debug(format_exc())
                raise
            else:
                args = [field, base_permalist, units_per_pagination]
                # if pagination_patterns is a dict, then use the supplied
                # title pattern
                if isinstance(pagination_patterns, dict):
                    args.append(pagination_patterns[pattern])

                pagination_in_pattern = paginate(*args)
                key = '/'.join(base_permalist)
                paginations[key] = pagination_in_pattern

        return paginations

    def _paginate_all(self, field, base_permalist, units_per_pagination, \
            title_pattern=''):
        """Create paginations for all field values (PRIVATE)."""
        paginated = self._paginator(self.units, base_permalist, \
                units_per_pagination, title_pattern)

        self.logger.debug('created: %d %s paginations' % (len(paginated), 'all'))
        return paginated

    def _paginate_single(self, field, base_permalist, units_per_pagination, \
            title_pattern=''):
        """Create paginations for string/int/float header field values (PRIVATE)."""
        units = self.units
        str_set = set([getattr(x, field) for x in units])

        paginated = []
        for item in str_set:
            matches = [x for x in units if item == getattr(x, field)]
            base_permalist = base_permalist[:-1] + [str(item)]
            if title_pattern:
                title = title_pattern % str(item)
            else:
                title = title_pattern
            pagin = self._paginator(matches, base_permalist, \
                    units_per_pagination, title)
            paginated.extend(pagin)

        self.logger.debug('created: %d %s paginations' % (len(paginated), field))
        return paginated

    def _paginate_multiple(self, field, base_permalist, units_per_pagination, \
            title_pattern=''):
        """Create paginations for list or tuple header field values (PRIVATE)."""
        units = self.units
        item_list_per_unit = (getattr(x, field) for x in units)
        item_set = reduce(set.union, [set(x) for x in item_list_per_unit])

        paginated = []
        for item in item_set:
            matches = [x for x in units if item in getattr(x, field)]
            base_permalist = base_permalist[:-1] + [str(item)]
            if title_pattern:
                title = title_pattern % str(item)
            else:
                title = title_pattern
            pagin = self._paginator(matches, base_permalist, \
                    units_per_pagination, title)
            paginated.extend(pagin)

        self.logger.debug('created: %d %s paginations' % (len(paginated), field))
        return paginated

    def _paginate_datetime(self, field, base_permalist, \
            units_per_pagination, title_pattern=''):
        """Create paginations for datetime header field values (PRIVATE)."""
        units = self.units
        # separate the field name from the datetime formatting
        field, time_fmt = field.split(':')
        time_tokens = time_fmt.strip('/').split('/')
        unit_times = [getattr(x, field) for x in units]
        # construct set of all datetime combinations in units according to
        # the user's supplied pagination URL; e.g. if URL == '%Y/%m' and
        # there are two units with 2009/10 and one with 2010/03 then
        # time_set == set([('2009', '10), ('2010', '03'])
        time_strs = [[x.strftime(y) for x in unit_times] for y in time_tokens]
        time_set = set(zip(*time_strs))

        paginated = []
        # create placeholders for new tokens
        base_permalist = base_permalist[:-1] + [None] * len(time_tokens)
        for item in time_set:
            # get all units whose datetime values match 'item'
            matches = []
            for unit in units:
                val = getattr(unit, field)
                time_str = [[val.strftime(y)] for y in time_tokens]
                time_tuple = zip(*time_str)
                assert len(time_tuple) == 1
                if item in time_tuple:
                    matches.append(unit)

            base_permalist = base_permalist[:-(len(time_tokens))] + list(item)
            if title_pattern:
                title = getattr(matches[0], field).strftime(title_pattern)
            else:
                title = title_pattern
            pagin = self._paginator(matches, base_permalist, \
                    units_per_pagination, title)
            paginated.extend(pagin)

        self.logger.debug('created: %d %s paginations' % (len(paginated), field))
        return paginated

    def _paginator(self, units, base_permalist, units_per_pagination, title=''):
        """Create paginations from units (PRIVATE).

        units -- List of all units which will be paginated.
        base_permalist -- List of permalink tokens that will be used by all
                          paginations of the given units.
        units_per_pagination -- Number of units to show per pagination.
        title -- String to use as the pagination title.

        """
        paginations = []

        # count how many paginations we need
        is_last = len(units) % units_per_pagination != 0
        pagination_len = len(units) // units_per_pagination + int(is_last)

        # construct pagination objects for each pagination page
        for idx in range(pagination_len):
            start = idx * units_per_pagination
            if idx != pagination_len - 1:
                stop = (idx + 1) * units_per_pagination
                units_in_pagination = units[start:stop]
            else:
                units_in_pagination = units[start:]

            pagination = Pagination(units_in_pagination, idx, base_permalist, \
                    title)
            paginations.append(pagination)

        if len(paginations) > 1:
            chain_item_permalinks(paginations)
            self.logger.debug('done: chaining paginations')

        return paginations

    def write_units(self):
        """Writes units using the unit template file."""
        self._write_items(self.units, self.config.UNIT_TEMPLATE)
        self.logger.debug('written: %d %s unit(s)' % (len(self.units), \
                type(self).__name__[:-len('Engine')]))

    def write_paginations(self):
        """Writes paginations using the pagination template file."""
        for pattern in self.paginations:
            self._write_items(self.paginations[pattern], self.config.PAGINATION_TEMPLATE)
            self.logger.debug("written: '%s' pagination(s)" % pattern)

    def _write_items(self, items, template_path):
        """Writes Page objects using the given template file (PRIVATE).

        items -- List of Page objects to be written.
        template_path -- Template file name, must exist in the defined
                         template directory.

        """
        template_env = CONFIG.SITE.TEMPLATE_ENV
        template_file = os.path.basename(template_path)

        # get template from cache if it's already loaded
        if template_file not in self._templates:
            template = template_env.get_template(template_file)
            self._templates[template_file] = template
        else:
            template = self._templates[template_file]

        for item in items:
            # warn if files are overwritten
            # this indicates a duplicate post, which could result in
            # unexpected results
            if os.path.exists(item.path):
                message = "File %s already exists. Make sure there are no "\
                          "other entries leading to this file path." % item.path
                self.logger.error(message)
                raise IOError(message)
            else:
                rendered = template.render(page=item, CONFIG=CONFIG, \
                        widgets=self.widgets)
                if sys.version_info[0] < 3:
                    rendered = rendered.encode('utf-8')
                write_file(item.path, rendered)