Browse Source

Retrofit merged plugins.

Merge the huge plugin structural refactoring and retrofit changes
from the past.
main
Lakshmi Vyasarajan 11 years ago
parent
commit
0019e1821e
18 changed files with 217 additions and 1054 deletions
  1. +0
    -55
      hyde/ext/plugins/coffee.py
  2. +1
    -1
      hyde/ext/plugins/css.py
  3. +0
    -213
      hyde/ext/plugins/grouper.py
  4. +59
    -0
      hyde/ext/plugins/images.py
  5. +0
    -58
      hyde/ext/plugins/jpegoptim.py
  6. +0
    -66
      hyde/ext/plugins/jpegtran.py
  7. +96
    -0
      hyde/ext/plugins/js.py
  8. +8
    -5
      hyde/ext/plugins/meta.py
  9. +0
    -71
      hyde/ext/plugins/optipng.py
  10. +0
    -155
      hyde/ext/plugins/paginator.py
  11. +0
    -57
      hyde/ext/plugins/requirejs.py
  12. +0
    -129
      hyde/ext/plugins/sorter.py
  13. +20
    -17
      hyde/ext/plugins/structure.py
  14. +0
    -210
      hyde/ext/plugins/tagger.py
  15. +30
    -1
      hyde/plugin.py
  16. +1
    -8
      hyde/tests/ext/test_grouper.py
  17. +1
    -1
      hyde/tests/ext/test_requirejs.py
  18. +1
    -7
      hyde/tests/ext/test_sorter.py

+ 0
- 55
hyde/ext/plugins/coffee.py View File

@@ -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)

+ 1
- 1
hyde/ext/plugins/css.py View File

@@ -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))


+ 0
- 213
hyde/ext/plugins/grouper.py View File

@@ -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)

+ 59
- 0
hyde/ext/plugins/images.py View File

@@ -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
#


+ 0
- 58
hyde/ext/plugins/jpegoptim.py View File

@@ -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)

+ 0
- 66
hyde/ext/plugins/jpegtran.py View File

@@ -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()

+ 96
- 0
hyde/ext/plugins/js.py View File

@@ -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)

+ 8
- 5
hyde/ext/plugins/meta.py View File

@@ -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


+ 0
- 71
hyde/ext/plugins/optipng.py View File

@@ -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)

+ 0
- 155
hyde/ext/plugins/paginator.py View File

@@ -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

+ 0
- 57
hyde/ext/plugins/requirejs.py View File

@@ -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()

+ 0
- 129
hyde/ext/plugins/sorter.py View File

@@ -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)


+ 20
- 17
hyde/ext/plugins/structure.py View File

@@ -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



+ 0
- 210
hyde/ext/plugins/tagger.py View File

@@ -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)

+ 30
- 1
hyde/plugin.py View File

@@ -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


+ 1
- 8
hyde/tests/ext/test_grouper.py View File

@@ -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


+ 1
- 1
hyde/tests/ext/test_requirejs.py View File

@@ -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)


+ 1
- 7
hyde/tests/ext/test_sorter.py View File

@@ -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


Loading…
Cancel
Save