Source code for hyde.ext.plugins.structure

# -*- coding: utf-8 -*-
"""
Plugins related to structure
"""

from hyde._compat import reduce
from hyde.ext.plugins.meta import Metadata
from hyde.plugin import Plugin
from hyde.site import Resource
from hyde.util import pairwalk

from fswrap import File, Folder

import os
from fnmatch import fnmatch
import operator


#
# Folder Flattening
#

[docs]class FlattenerPlugin(Plugin): """ The plugin class for flattening nested folders. """ def __init__(self, site): super(FlattenerPlugin, self).__init__(site) def begin_site(self): """ Finds all the folders that need flattening and changes the relative deploy path of all resources in those folders. """ items = [] try: items = self.site.config.flattener.items except AttributeError: pass for item in items: node = None target = '' try: node = self.site.content.node_from_relative_path(item.source) target = Folder(item.target) except AttributeError: continue if node: for resource in node.walk_resources(): target_path = target.child(resource.name) self.logger.debug( 'Flattening resource path [%s] to [%s]' % (resource, target_path)) resource.relative_deploy_path = target_path for child in node.walk(): child.relative_deploy_path = target.path
# # Combine # class CombinePlugin(Plugin): """ To use this combine, the following configuration should be added to meta data:: combine: sort: false #Optional. Defaults to true. root: content/media #Optional. Path must be relative to content folder - default current folder recurse: true #Optional. Default false. files: - ns1.*.js - ns2.*.js where: top remove: yes `files` is a list of resources (or just a resource) that should be combined. Globbing is performed. `where` indicate where the combination should be done. This could be `top` or `bottom` of the file. `remove` tell if we should remove resources that have been combined into the resource. """ def __init__(self, site): super(CombinePlugin, self).__init__(site) def _combined(self, resource): """ Return the list of resources to combine to build this one. """ try: config = resource.meta.combine except AttributeError: return [] # Not a combined resource try: files = config.files except AttributeError: raise AttributeError("No resources to combine for [%s]" % resource) if type(files) is str: files = [files] # Grab resources to combine # select site root try: root = self.site.content.node_from_relative_path( resource.meta.combine.root) except AttributeError: root = resource.node # select walker try: recurse = resource.meta.combine.recurse except AttributeError: recurse = False walker = root.walk_resources() if recurse else root.resources # Must we sort? try: sort = resource.meta.combine.sort except AttributeError: sort = True if sort: resources = sorted([r for r in walker if any(fnmatch(r.name, f) for f in files)], key=operator.attrgetter('name')) else: resources = [(f, r) for r in walker for f in files if fnmatch(r.name, f)] resources = [r[1] for f in files for r in resources if f in r] if not resources: self.logger.debug("No resources to combine for [%s]" % resource) return [] return resources def begin_site(self): """ Initialize the plugin and search for the combined resources """ for node in self.site.content.walk(): for resource in node.resources: resources = self._combined(resource) if not resources: continue # Build depends if not hasattr(resource, 'depends'): resource.depends = [] resource.depends.extend( [r.relative_path for r in resources if r.relative_path not in resource.depends]) # Remove combined resources if needed if hasattr(resource.meta.combine, "remove") and \ resource.meta.combine.remove: for r in resources: self.logger.debug( "Resource [%s] removed because combined" % r) r.is_processable = False def begin_text_resource(self, resource, text): """ When generating a resource, add combined file if needed. """ resources = self._combined(resource) if not resources: return where = "bottom" try: where = resource.meta.combine.where except AttributeError: pass if where not in ["top", "bottom"]: raise ValueError("%r should be either `top` or `bottom`" % where) self.logger.debug( "Combining %d resources for [%s]" % (len(resources), resource)) if where == "top": return "".join([r.source.read_all() for r in resources] + [text]) else: return "".join([text] + [r.source.read_all() for r in resources]) # # Pagination # class Page: def __init__(self, posts, number): self.posts = posts self.number = number class Paginator: """ Iterates resources which have pages associated with them. """ file_pattern = 'page$PAGE/$FILE$EXT' def __init__(self, settings): self.sorter = getattr(settings, 'sorter', None) self.size = getattr(settings, 'size', 10) self.file_pattern = getattr( settings, 'file_pattern', self.file_pattern) def _relative_url(self, source_path, number, basename, ext): """ Create a new URL for a new page. The first page keeps the same name; the subsequent pages are named according to file_pattern. """ path = File(source_path) if number != 1: filename = self.file_pattern.replace('$PAGE', str(number)) \ .replace('$FILE', basename) \ .replace('$EXT', ext) path = path.parent.child(os.path.normpath(filename)) return path def _new_resource(self, base_resource, node, page_number): """ Create a new resource as a copy of a base_resource, with a page of resources associated with it. """ res = Resource(base_resource.source_file, node) res.node.meta = Metadata(node.meta) res.meta = Metadata(base_resource.meta, res.node.meta) brs = base_resource.source_file path = self._relative_url(base_resource.relative_path, page_number, brs.name_without_extension, brs.extension) res.set_relative_deploy_path(path) return res @staticmethod def _attach_page_to_resource(page, resource): """ Hook up a page and a resource. """ resource.page = page page.resource = resource @staticmethod def _add_dependencies_to_resource(dependencies, resource): """ Add a bunch of resources as dependencies to another resource. """ if not hasattr(resource, 'depends'): resource.depends = [] resource.depends.extend([dep.relative_path for dep in dependencies if dep.relative_path not in resource.depends]) def _walk_pages_in_node(self, node): """ Segregate each resource into a page. """ walker = 'walk_resources' if self.sorter: walker = 'walk_resources_sorted_by_%s' % self.sorter walker = getattr(node, walker, getattr(node, 'walk_resources')) posts = list(walker()) number = 1 while posts: yield Page(posts[:self.size], number) posts = posts[self.size:] number += 1 def walk_paged_resources(self, node, resource): """ Group the resources and return the new page resources. """ added_resources = [] pages = list(self._walk_pages_in_node(node)) resource.pages = pages if pages: deps = reduce(list.__add__, [page.posts for page in pages], []) Paginator._attach_page_to_resource(pages[0], resource) Paginator._add_dependencies_to_resource(deps, resource) for page in pages[1:]: # make new resource new_resource = self._new_resource(resource, node, page.number) Paginator._attach_page_to_resource(page, new_resource) new_resource.depends = resource.depends added_resources.append(new_resource) new_resource.pages = pages for prev, next in pairwalk(pages): next.previous = prev prev.next = next return added_resources class PaginatorPlugin(Plugin): """ Paginator plugin. Configuration: in a resource's metadata: paginator: sorter: time size: 5 file_pattern: page$PAGE/$FILE$EXT # optional then in the resource's content: {% for res in resource.page.posts %} {% refer to res.relative_path as post %} {{ post }} {% endfor %} {{ resource.page.previous }} {{ resource.page.next }} """ def __init__(self, site): super(PaginatorPlugin, self).__init__(site) def begin_site(self): for node in self.site.content.walk(): added_resources = [] paged_resources = (res for res in node.resources if hasattr(res, "meta") and hasattr(res.meta, 'paginator')) for resource in paged_resources: paginator = Paginator(resource.meta.paginator) added_resources += paginator.walk_paged_resources( node, resource) node.resources += added_resources