From 0019e1821ede0bfe5dd0d4f834fdc9b433158601 Mon Sep 17 00:00:00 2001 From: Lakshmi Vyasarajan Date: Tue, 30 Apr 2013 14:46:33 +0530 Subject: [PATCH] Retrofit merged plugins. Merge the huge plugin structural refactoring and retrofit changes from the past. --- hyde/ext/plugins/coffee.py | 55 -------- hyde/ext/plugins/css.py | 2 +- hyde/ext/plugins/grouper.py | 213 ------------------------------- hyde/ext/plugins/images.py | 59 +++++++++ hyde/ext/plugins/jpegoptim.py | 58 --------- hyde/ext/plugins/jpegtran.py | 66 ---------- hyde/ext/plugins/js.py | 96 ++++++++++++++ hyde/ext/plugins/meta.py | 13 +- hyde/ext/plugins/optipng.py | 71 ----------- hyde/ext/plugins/paginator.py | 155 ---------------------- hyde/ext/plugins/requirejs.py | 57 --------- hyde/ext/plugins/sorter.py | 129 ------------------- hyde/ext/plugins/structure.py | 37 +++--- hyde/ext/plugins/tagger.py | 210 ------------------------------ hyde/plugin.py | 31 ++++- hyde/tests/ext/test_grouper.py | 9 +- hyde/tests/ext/test_requirejs.py | 2 +- hyde/tests/ext/test_sorter.py | 8 +- 18 files changed, 217 insertions(+), 1054 deletions(-) delete mode 100644 hyde/ext/plugins/coffee.py delete mode 100644 hyde/ext/plugins/grouper.py delete mode 100644 hyde/ext/plugins/jpegoptim.py delete mode 100644 hyde/ext/plugins/jpegtran.py delete mode 100644 hyde/ext/plugins/optipng.py delete mode 100644 hyde/ext/plugins/paginator.py delete mode 100644 hyde/ext/plugins/requirejs.py delete mode 100644 hyde/ext/plugins/sorter.py delete mode 100644 hyde/ext/plugins/tagger.py diff --git a/hyde/ext/plugins/coffee.py b/hyde/ext/plugins/coffee.py deleted file mode 100644 index adad40f..0000000 --- a/hyde/ext/plugins/coffee.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Coffee plugin -""" - -import traceback - -from hyde.plugin import CLTransformer -from hyde.fs import File - -class CoffeePlugin(CLTransformer): - """ - The plugin class for Coffeescript - """ - - def __init__(self, site): - super(CoffeePlugin, self).__init__(site) - - @property - def executable_name(self): - return "coffee" - - @property - def plugin_name(self): - """ - The name of the plugin. - """ - return "Coffee" - - def begin_site(self): - """ - Find all the coffee files and set their relative deploy path. - """ - for resource in self.site.content.walk_resources(): - if resource.source_file.kind == 'coffee': - new_name = resource.source_file.name_without_extension + ".js" - target_folder = File(resource.relative_deploy_path).parent - resource.relative_deploy_path = target_folder.child(new_name) - - def text_resource_complete(self, resource, text): - """ - Save the file to a temporary place and run the Coffee - compiler. Read the generated file and return the text as - output. - """ - - if not resource.source_file.kind == 'coffee': - return - - coffee = self.app - source = File.make_temp(text) - target = File.make_temp('') - args = [unicode(coffee)] - args.extend(["-c", "-p", unicode(source)]) - return self.call_app(args) diff --git a/hyde/ext/plugins/css.py b/hyde/ext/plugins/css.py index 4913a31..5c0fecc 100644 --- a/hyde/ext/plugins/css.py +++ b/hyde/ext/plugins/css.py @@ -221,7 +221,7 @@ class StylusPlugin(CLTransformer): args.append(unicode(source)) try: self.call_app(args) - except subprocess.CalledProcessError, e: + except subprocess.CalledProcessError: raise self.template.exception_class( "Cannot process %s. Error occurred when " "processing [%s]" % (stylus.name, resource.source_file)) diff --git a/hyde/ext/plugins/grouper.py b/hyde/ext/plugins/grouper.py deleted file mode 100644 index 4b807a5..0000000 --- a/hyde/ext/plugins/grouper.py +++ /dev/null @@ -1,213 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Contains classes and utilities related to grouping -resources and nodes in hyde. -""" -from hyde.model import Expando -from hyde.plugin import Plugin -from hyde.site import Node, Resource -from hyde.util import add_method, add_property, pairwalk - -from collections import namedtuple - - -Grouper = namedtuple('Grouper', 'group resources') - -class Group(Expando): - """ - A wrapper class for groups. Adds methods for - grouping resources. - """ - - def __init__(self, grouping, parent=None): - self.name = 'groups' - self.parent = parent - self.root = self - self.root = parent.root if parent else self - self.groups = [] - self.sorter = getattr(grouping, 'sorter', None) - if hasattr(parent, 'sorter'): - self.sorter = parent.sorter - super(Group, self).__init__(grouping) - - add_method(Node, - 'walk_%s_groups' % self.name, - Group.walk_groups_in_node, - group=self) - add_method(Node, - 'walk_resources_grouped_by_%s' % self.name, - Group.walk_resources, - group=self) - add_property(Resource, - '%s_group' % self.name, - Group.get_resource_group, - group=self) - add_method(Resource, - 'walk_%s_groups' % self.name, - Group.walk_resource_groups, - group=self) - - def set_expando(self, key, value): - """ - If the key is groups, creates group objects instead of - regular expando objects. - """ - if key == "groups": - self.groups = [Group(group, parent=self) for group in value] - else: - return super(Group, self).set_expando(key, value) - - @staticmethod - def get_resource_group(resource, group): - """ - This method gets attached to the resource object. - Returns group and its ancestors that the resource - belongs to, in that order. - """ - try: - group_name = getattr(resource.meta, group.root.name) - except AttributeError: - group_name = None - - return next((g for g in group.walk_groups() - if g.name == group_name), None) \ - if group_name \ - else None - - @staticmethod - def walk_resource_groups(resource, group): - """ - This method gets attached to the resource object. - Returns group and its ancestors that the resource - belongs to, in that order. - """ - try: - group_name = getattr(resource.meta, group.root.name) - except AttributeError: - group_name = None - if group_name: - for g in group.walk_groups(): - if g.name == group_name: - return reversed(list(g.walk_hierarchy())) - return [] - - @staticmethod - def walk_resources(node, group): - """ - The method that gets attached to the node - object for walking the resources in the node - that belong to this group. - """ - for group in group.walk_groups(): - for resource in group.walk_resources_in_node(node): - yield resource - - @staticmethod - def walk_groups_in_node(node, group): - """ - The method that gets attached to the node - object for walking the groups in the node. - """ - walker = group.walk_groups() - for g in walker: - lister = g.walk_resources_in_node(node) - yield Grouper(group=g, resources=lister) - - def walk_hierarchy(self): - """ - Walks the group hierarchy starting from - this group. - """ - g = self - yield g - while g.parent: - yield g.parent - g = g.parent - - def walk_groups(self): - """ - Walks the groups in the current group - """ - yield self - for group in self.groups: - for child in group.walk_groups(): - yield child - - def walk_resources_in_node(self, node): - """ - Walks the resources in the given node - sorted based on sorter configuration in this - group. - """ - walker = 'walk_resources' - if hasattr(self, 'sorter') and self.sorter: - walker = 'walk_resources_sorted_by_' + self.sorter - walker = getattr(node, walker, getattr(node, 'walk_resources')) - for resource in walker(): - try: - group_value = getattr(resource.meta, self.root.name) - except AttributeError: - continue - if group_value == self.name: - yield resource - -class GrouperPlugin(Plugin): - """ - Grouper plugin for hyde. Adds the ability to do - group resources and nodes in an arbitrary - hierarchy. - - Configuration example - --------------------- - #yaml - sorter: - kind: - atts: source.kind - grouper: - hyde: - # Categorizes the nodes and resources - # based on the groups specified here. - # The node and resource should be tagged - # with the categories in their metadata - sorter: kind # A reference to the sorter - description: Articles about hyde - groups: - - - name: announcements - description: Hyde release announcements - - - name: making of - description: Articles about hyde design decisions - - - name: tips and tricks - description: > - Helpful snippets and tweaks to - make hyde more awesome. - """ - def __init__(self, site): - super(GrouperPlugin, self).__init__(site) - - def begin_site(self): - """ - Initialize plugin. Add the specified groups to the - site context variable. - """ - config = self.site.config - if not hasattr(config, 'grouper'): - return - if not hasattr(self.site, 'grouper'): - self.site.grouper = {} - - for name, grouping in self.site.config.grouper.__dict__.items(): - grouping.name = name - prev_att = 'prev_in_%s' % name - next_att = 'next_in_%s' % name - setattr(Resource, prev_att, None) - setattr(Resource, next_att, None) - self.site.grouper[name] = Group(grouping) - walker = Group.walk_resources( - self.site.content, self.site.grouper[name]) - - for prev, next in pairwalk(walker): - setattr(next, prev_att, prev) - setattr(prev, next_att, next) diff --git a/hyde/ext/plugins/images.py b/hyde/ext/plugins/images.py index b947b42..f09d80e 100644 --- a/hyde/ext/plugins/images.py +++ b/hyde/ext/plugins/images.py @@ -416,6 +416,65 @@ class JPEGOptimPlugin(CLTransformer): self.call_app(args) +class JPEGTranPlugin(CLTransformer): + """ + Almost like jpegoptim except it uses jpegtran. jpegtran allows to make + progressive JPEG. Unfortunately, it only does lossless compression. If + you want both, you need to combine this plugin with jpegoptim one. + """ + + def __init__(self, site): + super(JPEGTranPlugin, self).__init__(site) + + @property + def plugin_name(self): + """ + The name of the plugin. + """ + return "jpegtran" + + def option_prefix(self, option): + return "-" + + def binary_resource_complete(self, resource): + """ + If the site is in development mode, just return. + Otherwise, run jpegtran to compress the jpg file. + """ + + try: + mode = self.site.config.mode + except AttributeError: + mode = "production" + + if not resource.source_file.kind == 'jpg': + return + + if mode.startswith('dev'): + self.logger.debug("Skipping jpegtran in development mode.") + return + + supported = [ + "optimize", + "progressive", + "restart", + "arithmetic", + "perfect", + "copy", + ] + source = File(self.site.config.deploy_root_path.child( + resource.relative_deploy_path)) + target = File.make_temp('') + jpegtran = self.app + args = [unicode(jpegtran)] + args.extend(self.process_args(supported)) + args.extend(["-outfile", unicode(target), unicode(source)]) + self.call_app(args) + target.copy_to(source) + target.delete() + + + # # PNG Optimization # diff --git a/hyde/ext/plugins/jpegoptim.py b/hyde/ext/plugins/jpegoptim.py deleted file mode 100644 index b623f10..0000000 --- a/hyde/ext/plugins/jpegoptim.py +++ /dev/null @@ -1,58 +0,0 @@ -# -*- coding: utf-8 -*- -""" -jpegoptim plugin -""" - -from hyde.plugin import CLTransformer - -from fswrap import File - -class JPEGOptimPlugin(CLTransformer): - """ - The plugin class for JPEGOptim - """ - - def __init__(self, site): - super(JPEGOptimPlugin, self).__init__(site) - - @property - def plugin_name(self): - """ - The name of the plugin. - """ - return "jpegoptim" - - def binary_resource_complete(self, resource): - """ - If the site is in development mode, just return. - Otherwise, run jpegoptim to compress the jpg file. - """ - - try: - mode = self.site.config.mode - except AttributeError: - mode = "production" - - if not resource.source_file.kind == 'jpg': - return - - if mode.startswith('dev'): - self.logger.debug("Skipping jpegoptim in development mode.") - return - - supported = [ - "force", - "max=", - "strip-all", - "strip-com", - "strip-exif", - "strip-iptc", - "strip-icc", - ] - target = File(self.site.config.deploy_root_path.child( - resource.relative_deploy_path)) - jpegoptim = self.app - args = [unicode(jpegoptim)] - args.extend(self.process_args(supported)) - args.extend(["-q", unicode(target)]) - self.call_app(args) diff --git a/hyde/ext/plugins/jpegtran.py b/hyde/ext/plugins/jpegtran.py deleted file mode 100644 index 52d366c..0000000 --- a/hyde/ext/plugins/jpegtran.py +++ /dev/null @@ -1,66 +0,0 @@ -# -*- coding: utf-8 -*- -""" -jpegtran plugin - -Almost like jpegoptim except it uses jpegtran. jpegtran allows to make -progressive JPEG. Unfortunately, it only does lossless compression. If -you want both, you need to combine this plugin with jpegoptim one. -""" - -from hyde.plugin import CLTransformer -from hyde.fs import File - -class JPEGTranPlugin(CLTransformer): - """ - The plugin class for JPEGTran - """ - - def __init__(self, site): - super(JPEGTranPlugin, self).__init__(site) - - @property - def plugin_name(self): - """ - The name of the plugin. - """ - return "jpegtran" - - def option_prefix(self, option): - return "-" - - def binary_resource_complete(self, resource): - """ - If the site is in development mode, just return. - Otherwise, run jpegtran to compress the jpg file. - """ - - try: - mode = self.site.config.mode - except AttributeError: - mode = "production" - - if not resource.source_file.kind == 'jpg': - return - - if mode.startswith('dev'): - self.logger.debug("Skipping jpegtran in development mode.") - return - - supported = [ - "optimize", - "progressive", - "restart", - "arithmetic", - "perfect", - "copy", - ] - source = File(self.site.config.deploy_root_path.child( - resource.relative_deploy_path)) - target = File.make_temp('') - jpegtran = self.app - args = [unicode(jpegtran)] - args.extend(self.process_args(supported)) - args.extend(["-outfile", unicode(target), unicode(source)]) - self.call_app(args) - target.copy_to(source) - target.delete() diff --git a/hyde/ext/plugins/js.py b/hyde/ext/plugins/js.py index c23593f..69a57c9 100644 --- a/hyde/ext/plugins/js.py +++ b/hyde/ext/plugins/js.py @@ -2,6 +2,7 @@ """ JavaScript plugins """ +import subprocess from hyde.plugin import CLTransformer @@ -80,3 +81,98 @@ class UglifyPlugin(CLTransformer): out = target.read_all() return out +class RequireJSPlugin(CLTransformer): + """ + requirejs plugin + + Calls r.js optimizer in order to proces javascript files, + bundle them into one single file and compress it. + + The call to r.js is being made with options -o and out. Example: + + r.js -o rjs.conf out=app.js + + whereas rjs.conf is the require.js configuration file pointing + to the main javascript file as well as passing options to r.js. + The bundled and compressed result is written to 'app.js' file + within the deployment structure. + + Please see the homepage of requirejs for usage details. + """ + def __init__(self, site): + super(RequireJSPlugin, self).__init__(site) + + @property + def executable_name(self): + return "r.js" + + def begin_site(self): + for resource in self.site.content.walk_resources(): + if resource.source_file.name == "rjs.conf": + new_name = "app.js" + target_folder = File(resource.relative_deploy_path).parent + resource.relative_deploy_path = target_folder.child(new_name) + + def text_resource_complete(self, resource, text): + if not resource.source_file.name == 'rjs.conf': + return + + rjs = self.app + target = File.make_temp('') + args = [unicode(rjs)] + args.extend(['-o', unicode(resource), ("out=" + target.fully_expanded_path)]) + + try: + self.call_app(args) + except subprocess.CalledProcessError: + raise self.template.exception_class( + "Cannot process %s. Error occurred when " + "processing [%s]" % (self.app.name, resource.source_file)) + + return target.read_all() + + +class CoffeePlugin(CLTransformer): + """ + The plugin class for Coffeescript + """ + + def __init__(self, site): + super(CoffeePlugin, self).__init__(site) + + @property + def executable_name(self): + return "coffee" + + @property + def plugin_name(self): + """ + The name of the plugin. + """ + return "Coffee" + + def begin_site(self): + """ + Find all the coffee files and set their relative deploy path. + """ + for resource in self.site.content.walk_resources(): + if resource.source_file.kind == 'coffee': + new_name = resource.source_file.name_without_extension + ".js" + target_folder = File(resource.relative_deploy_path).parent + resource.relative_deploy_path = target_folder.child(new_name) + + def text_resource_complete(self, resource, text): + """ + Save the file to a temporary place and run the Coffee + compiler. Read the generated file and return the text as + output. + """ + + if not resource.source_file.kind == 'coffee': + return + + coffee = self.app + source = File.make_temp(text) + args = [unicode(coffee)] + args.extend(["-c", "-p", unicode(source)]) + return self.call_app(args) diff --git a/hyde/ext/plugins/meta.py b/hyde/ext/plugins/meta.py index b99918e..967b40c 100644 --- a/hyde/ext/plugins/meta.py +++ b/hyde/ext/plugins/meta.py @@ -3,16 +3,19 @@ Contains classes and utilities related to meta data in hyde. """ -import re from collections import namedtuple -from operator import attrgetter -from itertools import ifilter from functools import partial +from itertools import ifilter +from operator import attrgetter +import re + from hyde.model import Expando from hyde.plugin import Plugin -from hyde.fs import File, Folder from hyde.site import Node, Resource from hyde.util import add_method, add_property, pairwalk + + +from fswrap import File, Folder import yaml @@ -441,7 +444,7 @@ def attributes_checker(item, attributes=None): Checks if the given list of attributes exist. """ try: - x = attrgetter(*attributes)(item) + attrgetter(*attributes)(item) return True except AttributeError: return False diff --git a/hyde/ext/plugins/optipng.py b/hyde/ext/plugins/optipng.py deleted file mode 100644 index 0c517c8..0000000 --- a/hyde/ext/plugins/optipng.py +++ /dev/null @@ -1,71 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OPTIPNG plugin -""" - -from hyde.plugin import CLTransformer - -from fswrap import File - -class OptiPNGPlugin(CLTransformer): - """ - The plugin class for OptiPNG - """ - - def __init__(self, site): - super(OptiPNGPlugin, self).__init__(site) - - @property - def plugin_name(self): - """ - The name of the plugin. - """ - return "optipng" - - def option_prefix(self, option): - return "-" - - def binary_resource_complete(self, resource): - """ - If the site is in development mode, just return. - Otherwise, run optipng to compress the png file. - """ - - try: - mode = self.site.config.mode - except AttributeError: - mode = "production" - - if not resource.source_file.kind == 'png': - return - - if mode.startswith('dev'): - self.logger.debug("Skipping optipng in development mode.") - return - - supported = [ - "o", - "fix", - "force", - "preserve", - "quiet", - "log", - "f", - "i", - "zc", - "zm", - "zs", - "zw", - "full", - "nb", - "nc", - "np", - "nz" - ] - target = File(self.site.config.deploy_root_path.child( - resource.relative_deploy_path)) - optipng = self.app - args = [unicode(optipng)] - args.extend(self.process_args(supported)) - args.extend([unicode(target)]) - self.call_app(args) diff --git a/hyde/ext/plugins/paginator.py b/hyde/ext/plugins/paginator.py deleted file mode 100644 index c6623fa..0000000 --- a/hyde/ext/plugins/paginator.py +++ /dev/null @@ -1,155 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Paginator plugin. Groups a sorted set of resources into pages and supplies -each page to a copy of the original resource. -""" - -import os - -from hyde.plugin import Plugin -from hyde.site import Resource -from hyde.util import pairwalk -from hyde.ext.plugins.meta import Metadata - -from fswrap import File - -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) - - path = self._relative_url(base_resource.relative_path, - page_number, - base_resource.source_file.name_without_extension, - base_resource.source_file.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)) - 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) - - 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.url 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, 'paginator')) - for resource in paged_resources: - paginator = Paginator(resource.meta.paginator) - added_resources += paginator.walk_paged_resources(node, resource) - - node.resources += added_resources diff --git a/hyde/ext/plugins/requirejs.py b/hyde/ext/plugins/requirejs.py deleted file mode 100644 index 4edc738..0000000 --- a/hyde/ext/plugins/requirejs.py +++ /dev/null @@ -1,57 +0,0 @@ -# -*- coding: utf-8 -*- -""" -requirejs plugin - -Calls r.js optimizer in order to proces javascript files, -bundle them into one single file and compress it. - -The call to r.js is being made with options -o and out. Example: - - r.js -o rjs.conf out=app.js - -whereas rjs.conf is the require.js configuration file pointing -to the main javascript file as well as passing options to r.js. -The bundled and compressed result is written to 'app.js' file -within the deployment structure. - -Please see the homepage of requirejs for usage details. -""" - -from hyde.plugin import CLTransformer - -from fswrap import File -import subprocess - -class RequireJSPlugin(CLTransformer): - - def __init__(self, site): - super(RequireJSPlugin, self).__init__(site) - - @property - def executable_name(self): - return "r.js" - - def begin_site(self): - for resource in self.site.content.walk_resources(): - if resource.source_file.name == "rjs.conf": - new_name = "app.js" - target_folder = File(resource.relative_deploy_path).parent - resource.relative_deploy_path = target_folder.child(new_name) - - def text_resource_complete(self, resource, text): - if not resource.source_file.name == 'rjs.conf': - return - - rjs = self.app - target = File.make_temp('') - args = [unicode(rjs)] - args.extend(['-o', unicode(resource), ("out=" + target.fully_expanded_path)]) - - try: - self.call_app(args) - except subprocess.CalledProcessError: - raise self.template.exception_class( - "Cannot process %s. Error occurred when " - "processing [%s]" % (self.app.name, resource.source_file)) - - return target.read_all() diff --git a/hyde/ext/plugins/sorter.py b/hyde/ext/plugins/sorter.py deleted file mode 100644 index b73ce9d..0000000 --- a/hyde/ext/plugins/sorter.py +++ /dev/null @@ -1,129 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Contains classes and utilities related to sorting -resources and nodes in hyde. -""" -from hyde.plugin import Plugin -from hyde.site import Node, Resource -from hyde.util import add_method, pairwalk - -from itertools import ifilter -from functools import partial -from operator import attrgetter - -def filter_method(item, settings=None): - """ - Returns true if all the filters in the - given settings evaluate to True. - """ - all_match = True - default_filters = {} - filters = {} - if hasattr(settings, 'filters'): - filters.update(default_filters) - filters.update(settings.filters.__dict__) - - for field, value in filters.items(): - try: - res = attrgetter(field)(item) - except: - res = None - if res != value: - all_match = False - break - return all_match - -def attributes_checker(item, attributes=None): - """ - Checks if the given list of attributes exist. - """ - try: - attrgetter(*attributes)(item) - return True - except AttributeError: - return False - -def sort_method(node, settings=None): - """ - Sorts the resources in the given node based on the - given settings. - """ - attr = 'name' - if settings and hasattr(settings, 'attr') and settings.attr: - attr = settings.attr - reverse = False - if settings and hasattr(settings, 'reverse'): - reverse = settings.reverse - if not isinstance(attr, list): - attr = [attr] - filter_ = partial(filter_method, settings=settings) - - excluder_ = partial(attributes_checker, attributes=attr) - - resources = ifilter(lambda x: excluder_(x) and filter_(x), - node.walk_resources()) - return sorted(resources, - key=attrgetter(*attr), - reverse=reverse) - - -class SorterPlugin(Plugin): - """ - Sorter plugin for hyde. Adds the ability to do - sophisticated sorting by expanding the site objects - to support prebuilt sorting methods. These methods - can be used in the templates directly. - - Configuration example - --------------------- - #yaml - - sorter: - kind: - # Sorts by this attribute name - # Uses `attrgetter` on the resource object - attr: source_file.kind - - # The filters to be used before sorting - # This can be used to remove all the items - # that do not apply. For example, - # filtering non html content - filters: - source_file.kind: html - is_processable: True - meta.is_listable: True - """ - - def __init__(self, site): - super(SorterPlugin, self).__init__(site) - - def begin_site(self): - """ - Initialize plugin. Add a sort and match method - for every configuration mentioned in site settings - """ - - config = self.site.config - if not hasattr(config, 'sorter'): - return - - for name, settings in config.sorter.__dict__.items(): - sort_method_name = 'walk_resources_sorted_by_%s' % name - self.logger.debug("Adding sort methods for [%s]" % name) - add_method(Node, sort_method_name, sort_method, settings=settings) - match_method_name = 'is_%s' % name - add_method(Resource, match_method_name, filter_method, settings) - - prev_att = 'prev_by_%s' % name - next_att = 'next_by_%s' % name - - setattr(Resource, prev_att, None) - setattr(Resource, next_att, None) - - walker = getattr(self.site.content, - sort_method_name, - self.site.content.walk_resources) - for prev, next in pairwalk(walker()): - setattr(prev, next_att, next) - setattr(next, prev_att, prev) - diff --git a/hyde/ext/plugins/structure.py b/hyde/ext/plugins/structure.py index 41a4fd6..8fb7bac 100644 --- a/hyde/ext/plugins/structure.py +++ b/hyde/ext/plugins/structure.py @@ -3,13 +3,13 @@ Plugins related to structure """ +from hyde.ext.plugins.meta import Metadata from hyde.plugin import Plugin - -from fswrap import File, Folder - from hyde.site import Resource from hyde.util import pairwalk +from fswrap import File, Folder + import os from fnmatch import fnmatch import operator @@ -225,6 +225,8 @@ class Paginator: 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) path = self._relative_url(base_resource.relative_path, page_number, base_resource.source_file.name_without_extension, @@ -272,20 +274,21 @@ class Paginator: """ added_resources = [] pages = list(self._walk_pages_in_node(node)) - 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) - - for prev, next in pairwalk(pages): - next.previous = prev - prev.next = next + 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) + + for prev, next in pairwalk(pages): + next.previous = prev + prev.next = next return added_resources diff --git a/hyde/ext/plugins/tagger.py b/hyde/ext/plugins/tagger.py deleted file mode 100644 index 3130fd0..0000000 --- a/hyde/ext/plugins/tagger.py +++ /dev/null @@ -1,210 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Contains classes and utilities related to tagging -resources in hyde. -""" -from hyde.model import Expando -from hyde.plugin import Plugin -from hyde.site import Node -from hyde.util import add_method - -from operator import attrgetter - -from fswrap import File, Folder - -class Tag(Expando): - """ - A simple object that represents a tag. - """ - - def __init__(self, name): - """ - Initialize the tag with a name. - """ - self.name = name - self.resources = [] - - def __repr__(self): - return self.name - - def __str__(self): - return self.name - - -def get_tagger_sort_method(site): - config = site.config - content = site.content - walker = 'walk_resources' - sorter = None - try: - sorter = attrgetter('tagger.sorter')(config) - walker = walker + '_sorted_by_%s' % sorter - except AttributeError: - pass - - try: - walker = getattr(content, walker) - except AttributeError: - raise self.template.exception_class( - "Cannot find the sorter: %s" % sorter) - return walker - -def walk_resources_tagged_with(node, tag): - tags = set(unicode(tag).split('+')) - walker = get_tagger_sort_method(node.site) - for resource in walker(): - try: - taglist = set(attrgetter("meta.tags")(resource)) - except AttributeError: - continue - if tags <= taglist: - yield resource - -class TaggerPlugin(Plugin): - """ - Tagger plugin for hyde. Adds the ability to do tag resources and search - based on the tags. - - Configuration example - --------------------- - #yaml - sorter: - kind: - atts: source.kind - tagger: - sorter: kind # How to sort the resources in a tag - archives: - blog: - template: tagged_posts.j2 - source: blog - target: blog/tags - archive_extension: html - """ - def __init__(self, site): - super(TaggerPlugin, self).__init__(site) - - def begin_site(self): - """ - Initialize plugin. Add tag to the site context variable - and methods for walking tagged resources. - """ - self.logger.debug("Adding tags from metadata") - config = self.site.config - content = self.site.content - tags = {} - add_method(Node, - 'walk_resources_tagged_with', walk_resources_tagged_with) - walker = get_tagger_sort_method(self.site) - for resource in walker(): - self._process_tags_in_resource(resource, tags) - self._process_tag_metadata(tags) - self.site.tagger = Expando(dict(tags=tags)) - self._generate_archives() - - def _process_tag_metadata(self, tags): - """ - Parses and adds metadata to the tagger object, if the tagger - configuration contains metadata. - """ - try: - tag_meta = self.site.config.tagger.tags.to_dict() - except AttributeError: - tag_meta = {} - - for tagname, meta in tag_meta.iteritems(): - # Don't allow name and resources in meta - if 'resources' in meta: - del(meta['resources']) - if 'name' in meta: - del(meta['name']) - if tagname in tags: - tags[tagname].update(meta) - - def _process_tags_in_resource(self, resource, tags): - """ - Reads the tags associated with this resource and - adds them to the tag list if needed. - """ - try: - taglist = attrgetter("meta.tags")(resource) - except AttributeError: - return - - for tagname in taglist: - if not tagname in tags: - tag = Tag(tagname) - tags[tagname] = tag - tag.resources.append(resource) - add_method(Node, - 'walk_resources_tagged_with_%s' % tagname, - walk_resources_tagged_with, - tag=tag) - else: - tags[tagname].resources.append(resource) - if not hasattr(resource, 'tags'): - setattr(resource, 'tags', []) - resource.tags.append(tags[tagname]) - - def _generate_archives(self): - """ - Generates archives if the configuration demands. - """ - archive_config = None - - try: - archive_config = attrgetter("tagger.archives")(self.site.config) - except AttributeError: - return - - self.logger.debug("Generating archives for tags") - - for name, config in archive_config.to_dict().iteritems(): - self._create_tag_archive(config) - - - def _create_tag_archive(self, config): - """ - Generates archives for each tag based on the given configuration. - """ - if not 'template' in config: - raise self.template.exception_class( - "No Template specified in tagger configuration.") - content = self.site.content.source_folder - source = Folder(config.get('source', '')) - target = content.child_folder(config.get('target', 'tags')) - if not target.exists: - target.make() - - # Write meta data for the configuration - meta = config.get('meta', {}) - meta_text = u'' - if meta: - import yaml - meta_text = yaml.dump(meta, default_flow_style=False) - - extension = config.get('extension', 'html') - template = config['template'] - - archive_text = u""" ---- -extends: false -%(meta)s ---- - -{%% set tag = site.tagger.tags['%(tag)s'] %%} -{%% set source = site.content.node_from_relative_path('%(node)s') %%} -{%% set walker = source['walk_resources_tagged_with_%(tag)s'] %%} -{%% extends "%(template)s" %%} -""" - for tagname, tag in self.site.tagger.tags.to_dict().iteritems(): - tag_data = { - "tag": tagname, - "node": source.name, - "template": template, - "meta": meta_text - } - text = archive_text % tag_data - archive_file = File(target.child("%s.%s" % (tagname, extension))) - archive_file.delete() - archive_file.write(text.strip()) - self.site.content.add_resource(archive_file) diff --git a/hyde/plugin.py b/hyde/plugin.py index 03e9eda..326761d 100644 --- a/hyde/plugin.py +++ b/hyde/plugin.py @@ -19,6 +19,31 @@ from fswrap import File logger = getLoggerWithNullHandler('hyde.engine') +# Plugins have been reorganized. Map old plugin paths to new. +PLUGINS_OLD_AND_NEW = { + "hyde.ext.plugins.less.LessCSSPlugin" : "hyde.ext.plugins.css.LessCSSPlugin", + "hyde.ext.plugins.stylus.StylusPlugin" : "hyde.ext.plugins.css.StylusPlugin", + "hyde.ext.plugins.jpegoptim.JPEGOptimPlugin" : "hyde.ext.plugins.images.JPEGOptimPlugin", + "hyde.ext.plugins.optipng.OptiPNGPlugin" : "hyde.ext.plugins.images.OptiPNGPlugin", + "hyde.ext.plugins.jpegtran.JPEGTranPlugin" : "hyde.ext.plugins.images.JPEGTranPlugin", + "hyde.ext.plugins.uglify.UglifyPlugin": "hyde.ext.plugins.js.UglifyPlugin", + "hyde.ext.plugins.requirejs.RequireJSPlugin": "hyde.ext.plugins.js.RequireJSPlugin", + "hyde.ext.plugins.coffee.CoffeePlugin": "hyde.ext.plugins.js.CoffeePlugin", + "hyde.ext.plugins.sorter.SorterPlugin": "hyde.ext.plugins.meta.SorterPlugin", + "hyde.ext.plugins.grouper.GrouperPlugin": "hyde.ext.plugins.meta.GrouperPlugin", + "hyde.ext.plugins.tagger.TaggerPlugin": "hyde.ext.plugins.meta.TaggerPlugin", + "hyde.ext.plugins.auto_extend.AutoExtendPlugin": "hyde.ext.plugins.meta.AutoExtendPlugin", + "hyde.ext.plugins.folders.FlattenerPlugin": "hyde.ext.plugins.structure.FlattenerPlugin", + "hyde.ext.plugins.combine.CombinePlugin": "hyde.ext.plugins.structure.CombinePlugin", + "hyde.ext.plugins.paginator.PaginatorPlugin": "hyde.ext.plugins.structure.PaginatorPlugin", + "hyde.ext.plugins.blockdown.BlockdownPlugin": "hyde.ext.plugins.text.BlockdownPlugin", + "hyde.ext.plugins.markings.MarkingsPlugin": "hyde.ext.plugins.text.MarkingsPlugin", + "hyde.ext.plugins.markings.ReferencePlugin": "hyde.ext.plugins.text.ReferencePlugin", + "hyde.ext.plugins.syntext.SyntextPlugin": "hyde.ext.plugins.text.SyntextPlugin", + "hyde.ext.plugins.textlinks.TextlinksPlugin": "hyde.ext.plugins.text.TextlinksPlugin", + "hyde.ext.plugins.git.GitDatesPlugin": "hyde.ext.plugins.vcs.GitDatesPlugin" +} + class PluginProxy(object): """ A proxy class to raise events in registered plugins @@ -261,7 +286,11 @@ class Plugin(object): Loads plugins based on the configuration. Assigns the plugins to 'site.plugins' """ - site.plugins = [load_python_object(name)(site) + def load_plugin(name): + plugin_name = PLUGINS_OLD_AND_NEW.get(name, name) + return load_python_object(plugin_name)(site) + + site.plugins = [load_plugin(name) for name in site.config.plugins] @staticmethod diff --git a/hyde/tests/ext/test_grouper.py b/hyde/tests/ext/test_grouper.py index 80d3735..9184cb6 100644 --- a/hyde/tests/ext/test_grouper.py +++ b/hyde/tests/ext/test_grouper.py @@ -4,14 +4,7 @@ Use nose `$ pip install nose` `$ nosetests` """ -from hyde.ext.plugins.meta import MetaPlugin -from hyde.ext.plugins.meta import SorterPlugin -<<<<<<< HEAD -from hyde.ext.plugins.grouper import GrouperPlugin -======= -from hyde.ext.plugins.meta import GrouperPlugin -from hyde.fs import File, Folder ->>>>>>> Move the grouper plugin into meta module. +from hyde.ext.plugins.meta import GrouperPlugin, MetaPlugin, SorterPlugin from hyde.generator import Generator from hyde.site import Site from hyde.model import Config, Expando diff --git a/hyde/tests/ext/test_requirejs.py b/hyde/tests/ext/test_requirejs.py index 5b4e5eb..8bdeecd 100644 --- a/hyde/tests/ext/test_requirejs.py +++ b/hyde/tests/ext/test_requirejs.py @@ -24,7 +24,7 @@ class TestRequireJS(object): def test_can_execute_rjs(self): s = Site(TEST_SITE) - s.config.plugins = ['hyde.ext.plugins.requirejs.RequireJSPlugin'] + s.config.plugins = ['hyde.ext.plugins.js.RequireJSPlugin'] source = TEST_SITE.child('content/media/js/rjs.conf') target = File(Folder(s.config.deploy_root_path).child('media/js/app.js')) gen = Generator(s) diff --git a/hyde/tests/ext/test_sorter.py b/hyde/tests/ext/test_sorter.py index 43e476e..685f335 100644 --- a/hyde/tests/ext/test_sorter.py +++ b/hyde/tests/ext/test_sorter.py @@ -4,13 +4,7 @@ Use nose `$ pip install nose` `$ nosetests` """ -from hyde.ext.plugins.meta import MetaPlugin -<<<<<<< HEAD -from hyde.ext.plugins.sorter import SorterPlugin -======= -from hyde.ext.plugins.meta import SorterPlugin -from hyde.fs import File, Folder ->>>>>>> Move the sorter plugin into the meta module. +from hyde.ext.plugins.meta import MetaPlugin, SorterPlugin from hyde.generator import Generator from hyde.site import Site from hyde.model import Config, Expando