Browse Source

pep8

main
Jordi Llonch 9 years ago
parent
commit
089cf7af9d
62 changed files with 1187 additions and 1692 deletions
  1. +25
    -24
      hyde/engine.py
  2. +1
    -2
      hyde/exceptions.py
  3. +3
    -4
      hyde/ext/plugins/blog.py
  4. +30
    -24
      hyde/ext/plugins/css.py
  5. +12
    -11
      hyde/ext/plugins/depends.py
  6. +86
    -54
      hyde/ext/plugins/images.py
  7. +11
    -5
      hyde/ext/plugins/js.py
  8. +10
    -6
      hyde/ext/plugins/languages.py
  9. +61
    -42
      hyde/ext/plugins/meta.py
  10. +32
    -26
      hyde/ext/plugins/sphinx.py
  11. +32
    -18
      hyde/ext/plugins/structure.py
  12. +15
    -5
      hyde/ext/plugins/text.py
  13. +5
    -3
      hyde/ext/plugins/urls.py
  14. +14
    -7
      hyde/ext/plugins/vcs.py
  15. +16
    -11
      hyde/ext/publishers/dvcs.py
  16. +22
    -24
      hyde/ext/publishers/pyfs.py
  17. +23
    -24
      hyde/ext/publishers/pypi.py
  18. +3
    -1
      hyde/ext/publishers/ssh.py
  19. +160
    -117
      hyde/ext/templates/jinja.py
  20. +25
    -24
      hyde/generator.py
  21. +3
    -2
      hyde/layout.py
  22. +4
    -1
      hyde/lib/pygments/rst_directive.py
  23. +2
    -1
      hyde/main.py
  24. +18
    -13
      hyde/model.py
  25. +76
    -47
      hyde/plugin.py
  26. +8
    -4
      hyde/publisher.py
  27. +19
    -13
      hyde/server.py
  28. +23
    -19
      hyde/site.py
  29. +3
    -1
      hyde/template.py
  30. +7
    -6
      hyde/tests/ext/test_auto_extend.py
  31. +5
    -3
      hyde/tests/ext/test_blockdown.py
  32. +8
    -4
      hyde/tests/ext/test_combine.py
  33. +5
    -4
      hyde/tests/ext/test_depends.py
  34. +4
    -5
      hyde/tests/ext/test_drafts.py
  35. +3
    -4
      hyde/tests/ext/test_flattener.py
  36. +29
    -19
      hyde/tests/ext/test_grouper.py
  37. +0
    -286
      hyde/tests/ext/test_images.py
  38. +3
    -3
      hyde/tests/ext/test_less.py
  39. +3
    -3
      hyde/tests/ext/test_markings.py
  40. +10
    -10
      hyde/tests/ext/test_meta.py
  41. +5
    -4
      hyde/tests/ext/test_optipng.py
  42. +3
    -10
      hyde/tests/ext/test_paginator.py
  43. +6
    -2
      hyde/tests/ext/test_requirejs.py
  44. +3
    -5
      hyde/tests/ext/test_scss.py
  45. +0
    -366
      hyde/tests/ext/test_sorter.py
  46. +10
    -6
      hyde/tests/ext/test_stylus.py
  47. +1
    -3
      hyde/tests/ext/test_syntext.py
  48. +0
    -226
      hyde/tests/ext/test_tagger.py
  49. +4
    -6
      hyde/tests/ext/test_textlinks.py
  50. +9
    -8
      hyde/tests/ext/test_uglify.py
  51. +19
    -17
      hyde/tests/ext/test_urlcleaner.py
  52. +9
    -8
      hyde/tests/ssp/ext/banner.py
  53. +20
    -11
      hyde/tests/test_generate.py
  54. +15
    -2
      hyde/tests/test_initialize.py
  55. +56
    -32
      hyde/tests/test_jinja2template.py
  56. +4
    -0
      hyde/tests/test_layout.py
  57. +11
    -2
      hyde/tests/test_model.py
  58. +128
    -78
      hyde/tests/test_plugin.py
  59. +17
    -8
      hyde/tests/test_simple_copy.py
  60. +40
    -14
      hyde/tests/test_site.py
  61. +7
    -3
      hyde/tests/util.py
  62. +1
    -1
      hyde/util.py

+ 25
- 24
hyde/engine.py View File

@@ -32,10 +32,11 @@ class Engine(Application):
)

@command(description='hyde - a python static website generator',
epilog='Use %(prog)s {command} -h to get help on individual commands')
epilog='Use %(prog)s {command} -h to get help'
'on individual commands')
@true('-v', '--verbose', help="Show detailed information in console")
@true('-x', '--raise-exceptions', default=None,
help="Don't handle exceptions.")
help="Don't handle exceptions.")
@version('--version', version='%(prog)s ' + __version__)
@store('-s', '--sitepath', default='.', help="Location of the hyde site")
def main(self, args):
@@ -52,7 +53,7 @@ class Engine(Application):
@subcommand('create', help='Create a new hyde site.')
@store('-l', '--layout', default='basic', help='Layout for the new site')
@true('-f', '--force', default=False, dest='overwrite',
help='Overwrite the current site if it exists')
help='Overwrite the current site if it exists')
def create(self, args):
"""
The create command. Creates a new site from the template at the given
@@ -64,27 +65,27 @@ class Engine(Application):

if exists and not args.overwrite:
raise HydeException(
"The given site path [%s] already contains a hyde site."
" Use -f to overwrite." % sitepath)
"The given site path [%s] already contains a hyde site."
" Use -f to overwrite." % sitepath)
layout = Layout.find_layout(args.layout)
self.logger.info(
"Creating site at [%s] with layout [%s]" % (sitepath, layout))
if not layout or not layout.exists:
raise HydeException(
"The given layout is invalid. Please check if you have the"
" `layout` in the right place and the environment variable(%s)"
" has been setup properly if you are using custom path for"
" layouts" % HYDE_DATA)
"The given layout is invalid. Please check if you have the"
" `layout` in the right place and the environment variable(%s)"
" has been setup properly if you are using custom path for"
" layouts" % HYDE_DATA)
layout.copy_contents_to(args.sitepath)
self.logger.info("Site creation complete")

@subcommand('gen', help='Generate the site')
@store('-c', '--config-path', default='site.yaml', dest='config',
help='The configuration used to generate the site')
help='The configuration used to generate the site')
@store('-d', '--deploy-path', dest='deploy', default=None,
help='Where should the site be generated?')
help='Where should the site be generated?')
@true('-r', '--regen', dest='regen', default=False,
help='Regenerate the whole site, including unchanged files')
help='Regenerate the whole site, including unchanged files')
def gen(self, args):
"""
The generate command. Generates the site at the given
@@ -103,13 +104,13 @@ class Engine(Application):

@subcommand('serve', help='Serve the website')
@store('-a', '--address', default='localhost', dest='address',
help='The address where the website must be served from.')
help='The address where the website must be served from.')
@store('-p', '--port', type=int, default=8080, dest='port',
help='The port where the website must be served from.')
help='The port where the website must be served from.')
@store('-c', '--config-path', default='site.yaml', dest='config',
help='The configuration used to generate the site')
help='The configuration used to generate the site')
@store('-d', '--deploy-path', dest='deploy', default=None,
help='Where should the site be generated?')
help='Where should the site be generated?')
def serve(self, args):
"""
The serve command. Serves the site at the given
@@ -120,7 +121,8 @@ class Engine(Application):
site = self.make_site(sitepath, args.config, args.deploy)
from hyde.server import HydeWebServer
server = HydeWebServer(site, args.address, args.port)
self.logger.info("Starting webserver at [%s]:[%d]", args.address, args.port)
self.logger.info(
"Starting webserver at [%s]:[%d]", args.address, args.port)
try:
server.serve_forever()
except (KeyboardInterrupt, SystemExit):
@@ -131,11 +133,11 @@ class Engine(Application):

@subcommand('publish', help='Publish the website')
@store('-c', '--config-path', default='site.yaml', dest='config',
help='The configuration used to generate the site')
help='The configuration used to generate the site')
@store('-p', '--publisher', dest='publisher', default='default',
help='Points to the publisher configuration.')
help='Points to the publisher configuration.')
@store('-m', '--message', dest='message',
help='Optional message.')
help='Optional message.')
def publish(self, args):
"""
Publishes the site based on the configuration from the `target`
@@ -145,11 +147,10 @@ class Engine(Application):
site = self.make_site(sitepath, args.config)
from hyde.publisher import Publisher
publisher = Publisher.load_publisher(site,
args.publisher,
args.message)
args.publisher,
args.message)
publisher.publish()


def make_site(self, sitepath, config, deploy=None):
"""
Creates a site object from the given sitepath and the config file.
@@ -157,4 +158,4 @@ class Engine(Application):
config = Config(sitepath, config_file=config)
if deploy:
config.deploy_root = deploy
return Site(sitepath, config)
return Site(sitepath, config)

+ 1
- 2
hyde/exceptions.py View File

@@ -1,4 +1,5 @@
class HydeException(Exception):

"""
Base class for exceptions from hyde
"""
@@ -7,5 +8,3 @@ class HydeException(Exception):
def reraise(message, exc_info):
_, _, tb = exc_info
raise HydeException(message), None, tb



+ 3
- 4
hyde/ext/plugins/blog.py View File

@@ -10,13 +10,12 @@ from hyde.plugin import Plugin

class DraftsPlugin(Plugin):


def begin_site(self):

in_production = self.site.config.mode.startswith('prod')
if not in_production:
self.logger.info(
'Generating draft posts as the site is not in production mode.')
self.logger.info('Generating draft posts as the site is'
'not in production mode.')
return

for resource in self.site.content.walk_resources():
@@ -33,4 +32,4 @@ class DraftsPlugin(Plugin):

self.logger.info(
'%s is%s draft' % (resource,
'' if is_draft else ' not'))
'' if is_draft else ' not'))

+ 30
- 24
hyde/ext/plugins/css.py View File

@@ -18,7 +18,9 @@ from fswrap import File
# Less CSS
#


class LessCSSPlugin(CLTransformer):

"""
The plugin class for less css
"""
@@ -29,7 +31,6 @@ class LessCSSPlugin(CLTransformer):
re.compile('^\\s*@import\s+(?:\'|\")([^\'\"]*)(?:\'|\")\s*\;\s*$',
re.MULTILINE)


@property
def executable_name(self):
return "lessc"
@@ -39,7 +40,7 @@ class LessCSSPlugin(CLTransformer):
Check user defined
"""
return resource.source_file.kind == 'less' and \
getattr(resource, 'meta', {}).get('parse', True)
getattr(resource, 'meta', {}).get('parse', True)

def _should_replace_imports(self, resource):
return getattr(resource, 'meta', {}).get('uses_template', True)
@@ -72,13 +73,13 @@ class LessCSSPlugin(CLTransformer):
afile = File(afile.path + '.less')
ref = self.site.content.resource_from_path(afile.path)
if not ref:
raise HydeException("Cannot import from path [%s]" % afile.path)
raise HydeException(
"Cannot import from path [%s]" % afile.path)
ref.is_processable = False
return self.template.get_include_statement(ref.relative_path)
text = self.import_finder.sub(import_to_include, text)
return text


@property
def plugin_name(self):
"""
@@ -114,10 +115,10 @@ class LessCSSPlugin(CLTransformer):
try:
self.call_app(args)
except subprocess.CalledProcessError:
HydeException.reraise(
"Cannot process %s. Error occurred when "
"processing [%s]" % (self.app.name, resource.source_file),
sys.exc_info())
HydeException.reraise(
"Cannot process %s. Error occurred when "
"processing [%s]" % (self.app.name, resource.source_file),
sys.exc_info())

return target.read_all()

@@ -127,6 +128,7 @@ class LessCSSPlugin(CLTransformer):
#

class StylusPlugin(CLTransformer):

"""
The plugin class for stylus css
"""
@@ -162,7 +164,8 @@ class StylusPlugin(CLTransformer):
if not match.lastindex:
return ''
path = match.groups(1)[0]
afile = File(File(resource.source_file.parent.child(path)).fully_expanded_path)
first_child = resource.source_file.parent.child(path)
afile = File(File(first_child).fully_expanded_path)
if len(afile.kind.strip()) == 0:
afile = File(afile.path + '.styl')

@@ -179,8 +182,8 @@ class StylusPlugin(CLTransformer):
else:
ref.is_processable = False
return "\n" + \
self.template.get_include_statement(ref.relative_path) + \
"\n"
self.template.get_include_statement(ref.relative_path) + \
"\n"
return '@import "' + path + '"\n'

text = self.import_finder.sub(import_to_include, text)
@@ -196,7 +199,7 @@ class StylusPlugin(CLTransformer):
except AttributeError:
mode = "production"

defaults = {"compress":""}
defaults = {"compress": ""}
if mode.startswith('dev'):
defaults = {}
return defaults
@@ -227,9 +230,9 @@ class StylusPlugin(CLTransformer):
self.call_app(args)
except subprocess.CalledProcessError:
HydeException.reraise(
"Cannot process %s. Error occurred when "
"processing [%s]" % (stylus.name, resource.source_file),
sys.exc_info())
"Cannot process %s. Error occurred when "
"processing [%s]" % (stylus.name, resource.source_file),
sys.exc_info())
target = File(source.path + '.css')
return target.read_all()

@@ -239,6 +242,7 @@ class StylusPlugin(CLTransformer):
#

class CleverCSSPlugin(Plugin):

"""
The plugin class for CleverCSS
"""
@@ -257,7 +261,7 @@ class CleverCSSPlugin(Plugin):
Check user defined
"""
return resource.source_file.kind == 'ccss' and \
getattr(resource, 'meta', {}).get('parse', True)
getattr(resource, 'meta', {}).get('parse', True)

def _should_replace_imports(self, resource):
return getattr(resource, 'meta', {}).get('uses_template', True)
@@ -282,8 +286,8 @@ class CleverCSSPlugin(Plugin):
return text

import_finder = re.compile(
'^\\s*@import\s+(?:\'|\")([^\'\"]*)(?:\'|\")\s*\;\s*$',
re.MULTILINE)
'^\\s*@import\s+(?:\'|\")([^\'\"]*)(?:\'|\")\s*\;\s*$',
re.MULTILINE)

def import_to_include(match):
if not match.lastindex:
@@ -294,7 +298,8 @@ class CleverCSSPlugin(Plugin):
afile = File(afile.path + '.ccss')
ref = self.site.content.resource_from_path(afile.path)
if not ref:
raise HydeException("Cannot import from path [%s]" % afile.path)
raise HydeException(
"Cannot import from path [%s]" % afile.path)
ref.is_processable = False
return self.template.get_include_statement(ref.relative_path)
text = import_finder.sub(import_to_include, text)
@@ -313,7 +318,9 @@ class CleverCSSPlugin(Plugin):
# Sassy CSS
#


class SassyCSSPlugin(Plugin):

"""
The plugin class for SassyCSS
"""
@@ -332,7 +339,7 @@ class SassyCSSPlugin(Plugin):
Check user defined
"""
return resource.source_file.kind == 'scss' and \
getattr(resource, 'meta', {}).get('parse', True)
getattr(resource, 'meta', {}).get('parse', True)

@property
def options(self):
@@ -364,7 +371,6 @@ class SassyCSSPlugin(Plugin):
"""
return self.settings.get('includes', [])


def begin_site(self):
"""
Find all the sassycss files and set their relative deploy path.
@@ -373,7 +379,7 @@ class SassyCSSPlugin(Plugin):
self.scss.STATIC_ROOT = self.site.config.content_root_path.path
self.scss.ASSETS_URL = self.site.media_url('/')
self.scss.ASSETS_ROOT = self.site.config.deploy_root_path.child(
self.site.config.media_root)
self.site.config.media_root)

for resource in self.site.content.walk_resources():
if self._should_parse_resource(resource):
@@ -391,8 +397,8 @@ class SassyCSSPlugin(Plugin):
includes = [resource.node.path] + self.includes
includes = [path.rstrip(os.sep) + os.sep for path in includes]
options = self.options
if not 'load_paths' in options:
if 'load_paths' not in options:
options['load_paths'] = []
options['load_paths'].extend(includes)
scss = self.scss.Scss(scss_opts=options, scss_vars=self.vars )
scss = self.scss.Scss(scss_opts=options, scss_vars=self.vars)
return scss.compile(text)

+ 12
- 11
hyde/ext/plugins/depends.py View File

@@ -7,7 +7,9 @@ Depends plugin

from hyde.plugin import Plugin


class DependsPlugin(Plugin):

"""
The plugin class setting explicit dependencies.
"""
@@ -16,15 +18,14 @@ class DependsPlugin(Plugin):
super(DependsPlugin, self).__init__(site)

def begin_site(self):
"""
Initialize dependencies.

Go through all the nodes and resources to initialize
dependencies at each level.
"""
for resource in self.site.content.walk_resources():
self._update_resource(resource)
"""
Initialize dependencies.

Go through all the nodes and resources to initialize
dependencies at each level.
"""
for resource in self.site.content.walk_resources():
self._update_resource(resource)

def _update_resource(self, resource):
"""
@@ -53,7 +54,7 @@ class DependsPlugin(Plugin):

for dep in depends:
resource.depends.append(dep.format(node=resource.node,
resource=resource,
site=self.site,
context=self.site.context))
resource=resource,
site=self.site,
context=self.site.context))
resource.depends = list(set(resource.depends))

+ 86
- 54
hyde/ext/plugins/images.py View File

@@ -39,6 +39,7 @@ class PILPlugin(Plugin):


class ImageSizerPlugin(PILPlugin):

"""
Each HTML page is modified to add width and height for images if
they are not already specified.
@@ -50,26 +51,28 @@ class ImageSizerPlugin(PILPlugin):
super(ImageSizerPlugin, self).__init__(site)
self.cache = {}


def _handle_img(self, resource, src, width, height):
"""Determine what should be added to an img tag"""
if height is not None and width is not None:
return "" # Nothing
if src is None:
self.logger.warn("[%s] has an img tag without src attribute" % resource)
self.logger.warn(
"[%s] has an img tag without src attribute" % resource)
return "" # Nothing
if src not in self.cache:
if src.startswith(self.site.config.media_url):
path = src[len(self.site.config.media_url):].lstrip("/")
path = self.site.config.media_root_path.child(path)
image = self.site.content.resource_from_relative_deploy_path(path)
image = self.site.content.resource_from_relative_deploy_path(
path)
elif re.match(r'([a-z]+://|//).*', src):
# Not a local link
return "" # Nothing
elif src.startswith("/"):
# Absolute resource
path = src.lstrip("/")
image = self.site.content.resource_from_relative_deploy_path(path)
image = self.site.content.resource_from_relative_deploy_path(
path)
else:
# Relative resource
path = resource.node.source_folder.child(src)
@@ -80,7 +83,7 @@ class ImageSizerPlugin(PILPlugin):
return "" # Nothing
if image.source_file.kind not in ['png', 'jpg', 'jpeg', 'gif']:
self.logger.warn(
"[%s] has an img tag not linking to an image" % resource)
"[%s] has an img tag not linking to an image" % resource)
return "" # Nothing
# Now, get the size of the image
try:
@@ -96,9 +99,9 @@ class ImageSizerPlugin(PILPlugin):
if new_width is None or new_height is None:
return "" # Nothing
if width is not None:
return 'height="%d" ' % (int(width)*new_height/new_width)
return 'height="%d" ' % (int(width) * new_height / new_width)
elif height is not None:
return 'width="%d" ' % (int(height)*new_width/new_height)
return 'width="%d" ' % (int(height) * new_width / new_height)
return 'height="%d" width="%d" ' % (new_height, new_width)

def text_resource_complete(self, resource, text):
@@ -151,7 +154,7 @@ class ImageSizerPlugin(PILPlugin):
continue
attr = None
for tag in tags:
if text[pos:(pos+len(tag)+1)] == ("%s=" % tag):
if text[pos:(pos + len(tag) + 1)] == ("%s=" % tag):
attr = tag
pos = pos + len(tag) + 1
break
@@ -177,12 +180,13 @@ class ImageSizerPlugin(PILPlugin):

return text


def scale_aspect(a, b1, b2):
from math import ceil
"""
from math import ceil
"""
Scales a by b2/b1 rounding up to nearest integer
"""
return int(ceil(a * b2 / float(b1)))
return int(ceil(a * b2 / float(b1)))


def thumb_scale_size(orig_width, orig_height, width, height):
@@ -197,7 +201,7 @@ def thumb_scale_size(orig_width, orig_height, width, height):
width = scale_aspect(orig_width, orig_height, height)
elif height is None:
height = scale_aspect(orig_height, orig_width, width)
elif orig_width*height >= orig_height*width:
elif orig_width * height >= orig_height * width:
width = scale_aspect(orig_width, orig_height, height)
else:
height = scale_aspect(orig_height, orig_width, width)
@@ -208,7 +212,9 @@ def thumb_scale_size(orig_width, orig_height, width, height):
# Image Thumbnails
#


class ImageThumbnailsPlugin(PILPlugin):

"""
Provide a function to get thumbnail for any image resource.

@@ -239,11 +245,11 @@ class ImageThumbnailsPlugin(PILPlugin):
prefix: thumbs4_
include:
- '*.jpg'
which means - make four thumbnails from every picture with different prefixes
and sizes
which means - make four thumbnails from every picture with different
prefixes and sizes

It is only valid to specify either width/height or larger/smaller, but not to
mix the two types.
It is only valid to specify either width/height or larger/smaller, but
not to mix the two types.

If larger/smaller are specified, then the orientation (i.e., landscape or
portrait) is preserved while thumbnailing.
@@ -256,7 +262,8 @@ class ImageThumbnailsPlugin(PILPlugin):
def __init__(self, site):
super(ImageThumbnailsPlugin, self).__init__(site)

def thumb(self, resource, width, height, prefix, crop_type, preserve_orientation=False):
def thumb(self, resource, width, height, prefix, crop_type,
preserve_orientation=False):
"""
Generate a thumbnail for the given image
"""
@@ -268,14 +275,17 @@ class ImageThumbnailsPlugin(PILPlugin):
# for simple maintenance but keep original deploy path to preserve
# naming logic in generated site
path = os.path.join(".thumbnails",
os.path.dirname(resource.get_relative_deploy_path()),
os.path.dirname(
resource.get_relative_deploy_path()),
"%s%s" % (prefix, name))
target = resource.site.config.content_root_path.child_file(path)
res = self.site.content.add_resource(target)
res.set_relative_deploy_path(res.get_relative_deploy_path().replace('.thumbnails/', '', 1))
res.set_relative_deploy_path(
res.get_relative_deploy_path().replace('.thumbnails/', '', 1))

target.parent.make()
if os.path.exists(target.path) and os.path.getmtime(resource.path) <= os.path.getmtime(target.path):
if (os.path.exists(target.path) and os.path.getmtime(resource.path) <=
os.path.getmtime(target.path)):
return
self.logger.debug("Making thumbnail for [%s]" % resource)

@@ -285,17 +295,18 @@ class ImageThumbnailsPlugin(PILPlugin):
format = im.format

if preserve_orientation and im.size[1] > im.size[0]:
width, height = height, width
width, height = height, width

resize_width, resize_height = thumb_scale_size(im.size[0], im.size[1], width, height)
resize_width, resize_height = thumb_scale_size(
im.size[0], im.size[1], width, height)

self.logger.debug("Resize to: %d,%d" % (resize_width, resize_height))
im = im.resize((resize_width, resize_height), self.Image.ANTIALIAS)
if width is not None and height is not None:
shiftx = shifty = 0
if crop_type == "center":
shiftx = (im.size[0] - width)/2
shifty = (im.size[1] - height)/2
shiftx = (im.size[0] - width) / 2
shifty = (im.size[1] - height) / 2
elif crop_type == "bottomright":
shiftx = (im.size[0] - width)
shifty = (im.size[1] - height)
@@ -304,22 +315,23 @@ class ImageThumbnailsPlugin(PILPlugin):

options = dict(optimize=True)
if format == "JPEG":
options['quality'] = 75
options['quality'] = 75

im.save(target.path, **options)

def begin_site(self):
"""
Find any image resource that should be thumbnailed and call thumb on it.
Find any image resource that should be thumbnailed and call thumb
on it.
"""
# Grab default values from config
config = self.site.config
defaults = { "width": None,
"height": None,
"larger": None,
"smaller": None,
"crop_type": "topleft",
"prefix": 'thumb_'}
defaults = {"width": None,
"height": None,
"larger": None,
"smaller": None,
"crop_type": "topleft",
"prefix": 'thumb_'}
if hasattr(config, 'thumbnails'):
defaults.update(config.thumbnails)

@@ -327,45 +339,64 @@ class ImageThumbnailsPlugin(PILPlugin):
if hasattr(node, 'meta') and hasattr(node.meta, 'thumbnails'):
for th in node.meta.thumbnails:
if not hasattr(th, 'include'):
self.logger.error("Include is not set for node [%s]" % node)
self.logger.error(
"Include is not set for node [%s]" % node)
continue
include = th.include
prefix = th.prefix if hasattr(th, 'prefix') else defaults['prefix']
height = th.height if hasattr(th, 'height') else defaults['height']
width = th.width if hasattr(th, 'width') else defaults['width']
larger = th.larger if hasattr(th, 'larger') else defaults['larger']
smaller = th.smaller if hasattr(th, 'smaller') else defaults['smaller']
crop_type = th.crop_type if hasattr(th, 'crop_type') else defaults['crop_type']
prefix = th.prefix if hasattr(
th, 'prefix') else defaults['prefix']
height = th.height if hasattr(
th, 'height') else defaults['height']
width = th.width if hasattr(
th, 'width') else defaults['width']
larger = th.larger if hasattr(
th, 'larger') else defaults['larger']
smaller = th.smaller if hasattr(
th, 'smaller') else defaults['smaller']
crop_type = th.crop_type if hasattr(
th, 'crop_type') else defaults['crop_type']
if crop_type not in ["topleft", "center", "bottomright"]:
self.logger.error("Unknown crop_type defined for node [%s]" % node)
self.logger.error(
"Unknown crop_type defined for node [%s]" % node)
continue
if width is None and height is None and larger is None and smaller is None:
self.logger.error("At least one of width, height, larger, or smaller must be set for node [%s]" % node)
if (width is None and height is None and larger is None and
smaller is None):
self.logger.error(
"At least one of width, height, larger, or smaller"
"must be set for node [%s]" % node)
continue

if ((larger is not None or smaller is not None) and
(width is not None or height is not None)):
self.logger.error("It is not valid to specify both one of width/height and one of larger/smaller for node [%s]" % node)
(width is not None or height is not None)):
self.logger.error(
"It is not valid to specify both one of"
"width/height and one of larger/smaller"
"for node [%s]" % node)
continue

if larger is None and smaller is None:
preserve_orientation = False
dim1, dim2 = width, height
preserve_orientation = False
dim1, dim2 = width, height
else:
preserve_orientation = True
dim1, dim2 = larger, smaller
preserve_orientation = True
dim1, dim2 = larger, smaller

match_includes = lambda s: any([glob.fnmatch.fnmatch(s, inc) for inc in include])
match_includes = lambda s: any(
[glob.fnmatch.fnmatch(s, inc) for inc in include])

for resource in node.resources:
if match_includes(resource.path):
self.thumb(resource, dim1, dim2, prefix, crop_type, preserve_orientation)
self.thumb(
resource, dim1, dim2, prefix, crop_type,
preserve_orientation)

#
# JPEG Optimization
#


class JPEGOptimPlugin(CLTransformer):

"""
The plugin class for JPEGOptim
"""
@@ -408,7 +439,7 @@ class JPEGOptimPlugin(CLTransformer):
"strip-icc",
]
target = File(self.site.config.deploy_root_path.child(
resource.relative_deploy_path))
resource.relative_deploy_path))
jpegoptim = self.app
args = [unicode(jpegoptim)]
args.extend(self.process_args(supported))
@@ -417,6 +448,7 @@ class JPEGOptimPlugin(CLTransformer):


class JPEGTranPlugin(CLTransformer):

"""
Almost like jpegoptim except it uses jpegtran. jpegtran allows to make
progressive JPEG. Unfortunately, it only does lossless compression. If
@@ -463,7 +495,7 @@ class JPEGTranPlugin(CLTransformer):
"copy",
]
source = File(self.site.config.deploy_root_path.child(
resource.relative_deploy_path))
resource.relative_deploy_path))
target = File.make_temp('')
jpegtran = self.app
args = [unicode(jpegtran)]
@@ -474,12 +506,12 @@ class JPEGTranPlugin(CLTransformer):
target.delete()



#
# PNG Optimization
#

class OptiPNGPlugin(CLTransformer):

"""
The plugin class for OptiPNG
"""
@@ -535,7 +567,7 @@ class OptiPNGPlugin(CLTransformer):
"nz"
]
target = File(self.site.config.deploy_root_path.child(
resource.relative_deploy_path))
resource.relative_deploy_path))
optipng = self.app
args = [unicode(optipng)]
args.extend(self.process_args(supported))


+ 11
- 5
hyde/ext/plugins/js.py View File

@@ -16,6 +16,7 @@ from fswrap import File
#

class UglifyPlugin(CLTransformer):

"""
The plugin class for Uglify JS
"""
@@ -85,7 +86,9 @@ class UglifyPlugin(CLTransformer):
out = target.read_all()
return out


class RequireJSPlugin(CLTransformer):

"""
requirejs plugin

@@ -103,6 +106,7 @@ class RequireJSPlugin(CLTransformer):

Please see the homepage of requirejs for usage details.
"""

def __init__(self, site):
super(RequireJSPlugin, self).__init__(site)

@@ -124,20 +128,22 @@ class RequireJSPlugin(CLTransformer):
rjs = self.app
target = File.make_temp('')
args = [unicode(rjs)]
args.extend(['-o', unicode(resource), ("out=" + target.fully_expanded_path)])
args.extend(
['-o', unicode(resource), ("out=" + target.fully_expanded_path)])

try:
self.call_app(args)
except subprocess.CalledProcessError:
HydeException.reraise(
"Cannot process %s. Error occurred when "
"processing [%s]" % (self.app.name, resource.source_file),
sys.exc_info())
HydeException.reraise(
"Cannot process %s. Error occurred when "
"processing [%s]" % (self.app.name, resource.source_file),
sys.exc_info())

return target.read_all()


class CoffeePlugin(CLTransformer):

"""
The plugin class for Coffeescript
"""


+ 10
- 6
hyde/ext/plugins/languages.py View File

@@ -5,7 +5,9 @@ Contains classes to help manage multi-language pages.

from hyde.plugin import Plugin


class LanguagePlugin(Plugin):

"""
Each page should be tagged with a language using `language` meta
data. Each page should also have an UUID stored in `uuid` meta
@@ -36,7 +38,8 @@ class LanguagePlugin(Plugin):

def __init__(self, site):
super(LanguagePlugin, self).__init__(site)
self.languages = {} # Associate a UUID to the list of resources available
# Associate a UUID to the list of resources available
self.languages = {}

def begin_site(self):
"""
@@ -60,8 +63,9 @@ class LanguagePlugin(Plugin):
resource.translations = \
[r for r in resources
if r.meta.language != language]
translations = ",".join([t.meta.language for t in resource.translations])
self.logger.debug(
"Adding translations for resource [%s] from %s to %s" % (resource,
language,
translations))
translations = ",".join(
[t.meta.language for t in resource.translations])
self.logger.debug("Adding translations for resource"
"[%s] from %s to %s" % (resource,
language,
translations))

+ 61
- 42
hyde/ext/plugins/meta.py View File

@@ -26,6 +26,7 @@ import yaml
#

class Metadata(Expando):

"""
Container class for yaml meta data.
"""
@@ -49,6 +50,7 @@ class Metadata(Expando):


class MetaPlugin(Plugin):

"""
Metadata plugin for hyde. Loads meta data in the following order:

@@ -66,8 +68,8 @@ class MetaPlugin(Plugin):
def __init__(self, site):
super(MetaPlugin, self).__init__(site)
self.yaml_finder = re.compile(
r"^\s*(?:---|===)\s*\n((?:.|\n)+?)\n\s*(?:---|===)\s*\n*",
re.MULTILINE)
r"^\s*(?:---|===)\s*\n((?:.|\n)+?)\n\s*(?:---|===)\s*\n*",
re.MULTILINE)

def begin_site(self):
"""
@@ -88,7 +90,8 @@ class MetaPlugin(Plugin):
if not hasattr(resource, 'meta'):
resource.meta = Metadata({}, node.meta)
if resource.source_file.is_text and not resource.simple_copy:
self.__read_resource__(resource, resource.source_file.read_all())
self.__read_resource__(
resource, resource.source_file.read_all())

def __read_resource__(self, resource, text):
"""
@@ -96,7 +99,8 @@ class MetaPlugin(Plugin):
the resource. Load meta data by looking for the marker.
Once loaded, remove the meta area from the text.
"""
self.logger.debug("Trying to load metadata from resource [%s]" % resource)
self.logger.debug(
"Trying to load metadata from resource [%s]" % resource)
match = re.match(self.yaml_finder, text)
if not match:
self.logger.debug("No metadata found in resource [%s]" % resource)
@@ -113,7 +117,7 @@ class MetaPlugin(Plugin):
resource.meta.update(data)
self.__update_standard_attributes__(resource)
self.logger.debug("Successfully loaded metadata from resource [%s]"
% resource)
% resource)
return text or ' '

def __update_standard_attributes__(self, obj):
@@ -165,6 +169,7 @@ class MetaPlugin(Plugin):
#

class AutoExtendPlugin(Plugin):

"""
The plugin class for extending templates using metadata.
"""
@@ -204,9 +209,9 @@ class AutoExtendPlugin(Plugin):
extended_text += '\n'
if block:
extended_text += ('%s\n%s\n%s' %
(self.t_block_open_tag(block),
text,
self.t_block_close_tag(block)))
(self.t_block_open_tag(block),
text,
self.t_block_close_tag(block)))
else:
extended_text += text
return extended_text
@@ -218,6 +223,7 @@ class AutoExtendPlugin(Plugin):
#

class Tag(Expando):

"""
A simple object that represents a tag.
"""
@@ -255,6 +261,7 @@ def get_tagger_sort_method(site):
sys.exc_info())
return walker


def walk_resources_tagged_with(node, tag):
tags = set(unicode(tag).split('+'))
walker = get_tagger_sort_method(node.site)
@@ -266,7 +273,9 @@ def walk_resources_tagged_with(node, tag):
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.
@@ -286,6 +295,7 @@ class TaggerPlugin(Plugin):
target: blog/tags
archive_extension: html
"""

def __init__(self, site):
super(TaggerPlugin, self).__init__(site)

@@ -295,11 +305,13 @@ class TaggerPlugin(Plugin):
and methods for walking tagged resources.
"""
self.logger.debug("Adding tags from metadata")
config = self.site.config
content = self.site.content
# *F841 local variable 'config' is assigned to but never used
# config = self.site.config
# *F841 local variable 'content' is assigned to but never used
# content = self.site.content
tags = {}
add_method(Node,
'walk_resources_tagged_with', walk_resources_tagged_with)
'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)
@@ -337,14 +349,14 @@ class TaggerPlugin(Plugin):
return

for tagname in taglist:
if not tagname in tags:
if tagname not 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)
'walk_resources_tagged_with_%s' % tagname,
walk_resources_tagged_with,
tag=tag)
else:
tags[tagname].resources.append(resource)
if not hasattr(resource, 'tags'):
@@ -367,13 +379,13 @@ class TaggerPlugin(Plugin):
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 HydeException("No Template specified in tagger configuration.")
if 'template' not in config:
raise HydeException(
"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'))
@@ -441,15 +453,17 @@ def filter_method(item, settings=None):
break
return all_match


def attributes_checker(item, attributes=None):
"""
Checks if the given list of attributes exist.
"""
try:
attrgetter(*attributes)(item)
return True
attrgetter(*attributes)(item)
return True
except AttributeError:
return False
return False


def sort_method(node, settings=None):
"""
@@ -471,11 +485,12 @@ def sort_method(node, settings=None):
resources = ifilter(lambda x: excluder_(x) and filter_(x),
node.walk_resources())
return sorted(resources,
key=attrgetter(*attr),
reverse=reverse)
key=attrgetter(*attr),
reverse=reverse)


class SorterPlugin(Plugin):

"""
Sorter plugin for hyde. Adds the ability to do
sophisticated sorting by expanding the site objects
@@ -529,8 +544,8 @@ class SorterPlugin(Plugin):
setattr(Resource, next_att, None)

walker = getattr(self.site.content,
sort_method_name,
self.site.content.walk_resources)
sort_method_name,
self.site.content.walk_resources)
first, last = None, None
for prev, next in pairwalk(walker()):
if not first:
@@ -555,7 +570,9 @@ class SorterPlugin(Plugin):

Grouper = namedtuple('Grouper', 'group resources')


class Group(Expando):

"""
A wrapper class for groups. Adds methods for
grouping resources.
@@ -573,21 +590,21 @@ class Group(Expando):
super(Group, self).__init__(grouping)

add_method(Node,
'walk_%s_groups' % self.name,
Group.walk_groups_in_node,
group=self)
'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)
'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)
'%s_group' % self.name,
Group.get_resource_group,
group=self)
add_method(Resource,
'walk_%s_groups' % self.name,
Group.walk_resource_groups,
group=self)
'walk_%s_groups' % self.name,
Group.walk_resource_groups,
group=self)

def set_expando(self, key, value):
"""
@@ -612,9 +629,9 @@ class Group(Expando):
group_name = None

return next((g for g in group.walk_groups()
if g.name == group_name), None) \
if group_name \
else None
if g.name == group_name), None) \
if group_name \
else None

@staticmethod
def walk_resource_groups(resource, group):
@@ -693,7 +710,9 @@ class Group(Expando):
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
@@ -726,6 +745,7 @@ class GrouperPlugin(Plugin):
Helpful snippets and tweaks to
make hyde more awesome.
"""

def __init__(self, site):
super(GrouperPlugin, self).__init__(site)

@@ -748,9 +768,8 @@ class GrouperPlugin(Plugin):
setattr(Resource, next_att, None)
self.site.grouper[name] = Group(grouping)
walker = Group.walk_resources(
self.site.content, self.site.grouper[name])
self.site.content, self.site.grouper[name])

for prev, next in pairwalk(walker):
setattr(next, prev_att, prev)
setattr(prev, next_att, next)


+ 32
- 26
hyde/ext/plugins/sphinx.py View File

@@ -63,6 +63,7 @@ except ImportError:


class SphinxPlugin(Plugin):

"""The plugin class for rendering sphinx-generated documentation."""

def __init__(self, site):
@@ -93,7 +94,7 @@ class SphinxPlugin(Plugin):
else:
for name in dir(user_settings):
if not name.startswith("_"):
setattr(settings,name,getattr(user_settings,name))
setattr(settings, name, getattr(user_settings, name))
return settings

@property
@@ -109,11 +110,11 @@ class SphinxPlugin(Plugin):
conf_path = self.site.sitepath.child_folder(conf_path)
# Sphinx always execs the config file in its parent dir.
conf_file = conf_path.child("conf.py")
self._sphinx_config = {"__file__":conf_file}
self._sphinx_config = {"__file__": conf_file}
curdir = os.getcwd()
os.chdir(conf_path.path)
try:
execfile(conf_file,self._sphinx_config)
execfile(conf_file, self._sphinx_config)
finally:
os.chdir(curdir)
return self._sphinx_config
@@ -132,16 +133,17 @@ class SphinxPlugin(Plugin):
# We need to:
# * change the deploy name from .rst to .html
# * if a block_map is given, switch off default_block
suffix = self.sphinx_config.get("source_suffix",".rst")
suffix = self.sphinx_config.get("source_suffix", ".rst")
for resource in self.site.content.walk_resources():
if resource.source_file.path.endswith(suffix):
new_name = resource.source_file.name_without_extension + ".html"
new_name = resource.source_file.name_without_extension + \
".html"
target_folder = File(resource.relative_deploy_path).parent
resource.relative_deploy_path = target_folder.child(new_name)
if settings.block_map:
resource.meta.default_block = None

def begin_text_resource(self,resource,text):
def begin_text_resource(self, resource, text):
"""Event hook for processing an individual resource.

If the input resource is a sphinx input file, this method will replace
@@ -151,7 +153,7 @@ class SphinxPlugin(Plugin):
This means that if no sphinx-related resources need updating, then
we entirely avoid running sphinx.
"""
suffix = self.sphinx_config.get("source_suffix",".rst")
suffix = self.sphinx_config.get("source_suffix", ".rst")
if not resource.source_file.path.endswith(suffix):
return text
if self.sphinx_build_dir is None:
@@ -164,9 +166,9 @@ class SphinxPlugin(Plugin):
if not settings.block_map:
output.append(sphinx_output["body"])
else:
for (nm,content) in sphinx_output.iteritems():
for (nm, content) in sphinx_output.iteritems():
try:
block = getattr(settings.block_map,nm)
block = getattr(settings.block_map, nm)
except AttributeError:
pass
else:
@@ -198,41 +200,45 @@ class SphinxPlugin(Plugin):
conf_path = self.settings.conf_path
conf_path = self.site.sitepath.child_folder(conf_path)
conf_file = conf_path.child("conf.py")
logger.error("Please ensure %s is a valid sphinx config",conf_file)
logger.error(
"Please ensure %s is a valid sphinx config", conf_file)
logger.error("or set sphinx.conf_path to the directory")
logger.error("containing your sphinx conf.py")
raise
# Check that the hyde_json extension is loaded
extensions = sphinx_config.get("extensions",[])
extensions = sphinx_config.get("extensions", [])
if "hyde.ext.plugins.sphinx" not in extensions:
logger.error("The hyde_json sphinx extension is not configured.")
logger.error("Please add 'hyde.ext.plugins.sphinx' to the list")
logger.error("of extensions in your sphinx conf.py file.")
logger.info("(set sphinx.sanity_check=false to disable this check)")
logger.info(
"(set sphinx.sanity_check=false to disable this check)")
raise RuntimeError("sphinx is not configured correctly")
# Check that the master doc exists in the source tree.
master_doc = sphinx_config.get("master_doc","index")
master_doc += sphinx_config.get("source_suffix",".rst")
master_doc = os.path.join(self.site.content.path,master_doc)
master_doc = sphinx_config.get("master_doc", "index")
master_doc += sphinx_config.get("source_suffix", ".rst")
master_doc = os.path.join(self.site.content.path, master_doc)
if not os.path.exists(master_doc):
logger.error("The sphinx master document doesn't exist.")
logger.error("Please create the file %s",master_doc)
logger.error("Please create the file %s", master_doc)
logger.error("or change the 'master_doc' setting in your")
logger.error("sphinx conf.py file.")
logger.info("(set sphinx.sanity_check=false to disable this check)")
logger.info(
"(set sphinx.sanity_check=false to disable this check)")
raise RuntimeError("sphinx is not configured correctly")
# Check that I am *before* the other plugins,
# with the possible exception of MetaPlugin
for plugin in self.site.plugins:
if plugin is self:
break
if not isinstance(plugin,_MetaPlugin):
if not isinstance(plugin, _MetaPlugin):
logger.error("The sphinx plugin is installed after the")
logger.error("plugin %r.",plugin.__class__.__name__)
logger.error("plugin %r.", plugin.__class__.__name__)
logger.error("It's quite likely that this will break things.")
logger.error("Please move the sphinx plugin to the top")
logger.error("of the plugins list.")
logger.info("(sphinx.sanity_check=false to disable this check)")
logger.info(
"(sphinx.sanity_check=false to disable this check)")
raise RuntimeError("sphinx is not configured correctly")

def _run_sphinx(self):
@@ -254,7 +260,7 @@ class SphinxPlugin(Plugin):
if sphinx.main(sphinx_args) != 0:
raise RuntimeError("sphinx build failed")

def _get_sphinx_output(self,resource):
def _get_sphinx_output(self, resource):
"""Get the sphinx output for a given resource.

This returns a dict mapping block names to HTML text fragments.
@@ -263,13 +269,14 @@ class SphinxPlugin(Plugin):
related pages and so-on.
"""
relpath = File(resource.relative_path)
relpath = relpath.parent.child(relpath.name_without_extension+".fjson")
with open(self.sphinx_build_dir.child(relpath),"rb") as f:
relpath = relpath.parent.child(
relpath.name_without_extension + ".fjson")
with open(self.sphinx_build_dir.child(relpath), "rb") as f:
return json.load(f)



class HydeJSONHTMLBuilder(JSONHTMLBuilder):

"""A slightly-customised JSONHTMLBuilder, for use by Hyde.

This is a Sphinx builder that serilises the generated HTML fragments into
@@ -280,6 +287,7 @@ class HydeJSONHTMLBuilder(JSONHTMLBuilder):
work correctly once things have been processed by Hyde.
"""
name = "hyde_json"

def get_target_uri(self, docname, typ=None):
return docname + ".html"

@@ -291,5 +299,3 @@ def setup(app):
Hyde plugin. It simply registers the HydeJSONHTMLBuilder class.
"""
app.add_builder(HydeJSONHTMLBuilder)



+ 32
- 18
hyde/ext/plugins/structure.py View File

@@ -20,9 +20,11 @@ import operator
#

class FlattenerPlugin(Plugin):

"""
The plugin class for flattening nested folders.
"""

def __init__(self, site):
super(FlattenerPlugin, self).__init__(site)

@@ -50,7 +52,7 @@ class FlattenerPlugin(Plugin):
target_path = target.child(resource.name)
self.logger.debug(
'Flattening resource path [%s] to [%s]' %
(resource, target_path))
(resource, target_path))
resource.relative_deploy_path = target_path
for child in node.walk():
child.relative_deploy_path = target.path
@@ -61,12 +63,14 @@ class FlattenerPlugin(Plugin):
#

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
root: content/media #Optional. Path must be relative to content
folder - default current folder
recurse: true #Optional. Default false.
files:
- ns1.*.js
@@ -97,13 +101,14 @@ class CombinePlugin(Plugin):
except AttributeError:
raise AttributeError("No resources to combine for [%s]" % resource)
if type(files) is str:
files = [ files ]
files = [files]

# Grab resources to combine

# select site root
try:
root = self.site.content.node_from_relative_path(resource.meta.combine.root)
root = self.site.content.node_from_relative_path(
resource.meta.combine.root)
except AttributeError:
root = resource.node

@@ -122,10 +127,12 @@ class CombinePlugin(Plugin):
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'))
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 = [(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:
@@ -173,7 +180,7 @@ class CombinePlugin(Plugin):
except AttributeError:
pass

if where not in [ "top", "bottom" ]:
if where not in ["top", "bottom"]:
raise ValueError("%r should be either `top` or `bottom`" % where)

self.logger.debug(
@@ -190,11 +197,14 @@ class CombinePlugin(Plugin):
#

class Page:

def __init__(self, posts, number):
self.posts = posts
self.number = number


class Paginator:

"""
Iterates resources which have pages associated with them.
"""
@@ -204,7 +214,8 @@ class Paginator:
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)
self.file_pattern = getattr(
settings, 'file_pattern', self.file_pattern)

def _relative_url(self, source_path, number, basename, ext):
"""
@@ -214,8 +225,8 @@ class Paginator:
path = File(source_path)
if number != 1:
filename = self.file_pattern.replace('$PAGE', str(number)) \
.replace('$FILE', basename) \
.replace('$EXT', ext)
.replace('$FILE', basename) \
.replace('$EXT', ext)
path = path.parent.child(os.path.normpath(filename))
return path

@@ -227,10 +238,11 @@ class Paginator:
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,
base_resource.source_file.name_without_extension,
base_resource.source_file.extension)
page_number,
brs.name_without_extension,
brs.extension)
res.set_relative_deploy_path(path)
return res

@@ -250,7 +262,7 @@ class Paginator:
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])
if dep.relative_path not in resource.depends])

def _walk_pages_in_node(self, node):
"""
@@ -294,6 +306,7 @@ class Paginator:


class PaginatorPlugin(Plugin):

"""
Paginator plugin.

@@ -315,6 +328,7 @@ class PaginatorPlugin(Plugin):
{{ resource.page.next }}

"""

def __init__(self, site):
super(PaginatorPlugin, self).__init__(site)

@@ -322,10 +336,10 @@ class PaginatorPlugin(Plugin):
for node in self.site.content.walk():
added_resources = []
paged_resources = (res for res in node.resources
if hasattr(res.meta, 'paginator'))
if hasattr(res.meta, 'paginator'))
for resource in paged_resources:
paginator = Paginator(resource.meta.paginator)
added_resources += paginator.walk_paged_resources(node, resource)
added_resources += paginator.walk_paged_resources(
node, resource)

node.resources += added_resources


+ 15
- 5
hyde/ext/plugins/text.py View File

@@ -3,7 +3,7 @@
Text processing plugins
"""

from hyde.plugin import Plugin,TextyPlugin
from hyde.plugin import Plugin, TextyPlugin


#
@@ -11,9 +11,11 @@ from hyde.plugin import Plugin,TextyPlugin
#

class BlockdownPlugin(TextyPlugin):

"""
The plugin class for block text replacement.
"""

def __init__(self, site):
super(BlockdownPlugin, self).__init__(site)

@@ -55,9 +57,11 @@ class BlockdownPlugin(TextyPlugin):
#

class MarkingsPlugin(TextyPlugin):

"""
The plugin class for mark text replacement.
"""

def __init__(self, site):
super(MarkingsPlugin, self).__init__(site)

@@ -99,9 +103,11 @@ class MarkingsPlugin(TextyPlugin):
#

class ReferencePlugin(TextyPlugin):

"""
The plugin class for reference text replacement.
"""

def __init__(self, site):
super(ReferencePlugin, self).__init__(site)

@@ -143,9 +149,11 @@ class ReferencePlugin(TextyPlugin):
#

class SyntextPlugin(TextyPlugin):

"""
The plugin class for syntax text replacement.
"""

def __init__(self, site):
super(SyntextPlugin, self).__init__(site)

@@ -170,7 +178,6 @@ class SyntextPlugin(TextyPlugin):
"""
return '^\s*~~~+\s*$'


def get_params(self, match, start=True):
"""
~~~css~~~ will return css
@@ -199,16 +206,18 @@ class SyntextPlugin(TextyPlugin):
#

class TextlinksPlugin(Plugin):

"""
The plugin class for text link replacement.
"""

def __init__(self, site):
super(TextlinksPlugin, self).__init__(site)
import re
self.content_link = re.compile('\[\[([^\]^!][^\]]*)\]\]',
re.UNICODE|re.MULTILINE)
re.UNICODE | re.MULTILINE)
self.media_link = re.compile('\[\[\!\!([^\]]*)\]\]',
re.UNICODE|re.MULTILINE)
re.UNICODE | re.MULTILINE)

def begin_text_resource(self, resource, text):
"""
@@ -221,11 +230,12 @@ class TextlinksPlugin(Plugin):
"""
if not resource.uses_template:
return text

def replace_content(match):
return self.template.get_content_url_statement(match.groups(1)[0])

def replace_media(match):
return self.template.get_media_url_statement(match.groups(1)[0])
text = self.content_link.sub(replace_content, text)
text = self.media_link.sub(replace_media, text)
return text


+ 5
- 3
hyde/ext/plugins/urls.py View File

@@ -8,7 +8,9 @@ from hyde.site import Site
from functools import wraps
from fswrap import File


class UrlCleanerPlugin(Plugin):

"""
Url Cleaner plugin for hyde. Adds to hyde the ability to generate clean
urls.
@@ -53,13 +55,13 @@ class UrlCleanerPlugin(Plugin):
def wrapper(site, path, safe=None):
url = urlgetter(site, path, safe)
index_file_names = getattr(settings,
'index_file_names',
['index.html'])
'index_file_names',
['index.html'])
rep = File(url)
if rep.name in index_file_names:
url = rep.parent.path.rstrip('/')
if hasattr(settings, 'append_slash') and \
settings.append_slash:
settings.append_slash:
url += '/'
elif hasattr(settings, 'strip_extensions'):
if rep.kind in settings.strip_extensions:


+ 14
- 7
hyde/ext/plugins/vcs.py View File

@@ -12,9 +12,11 @@ import subprocess


class VCSDatesPlugin(Plugin):

"""
Base class for getting resource timestamps from VCS.
"""

def __init__(self, site, vcs_name='vcs'):
super(VCSDatesPlugin, self).__init__(site)
self.vcs_name = vcs_name
@@ -38,8 +40,8 @@ class VCSDatesPlugin(Plugin):

if created == "git":
created = date_created or \
datetime.utcfromtimestamp(
os.path.getctime(resource.path))
datetime.utcfromtimestamp(
os.path.getctime(resource.path))
created = created.replace(tzinfo=None)
resource.meta.created = created

@@ -48,7 +50,6 @@ class VCSDatesPlugin(Plugin):
modified = modified.replace(tzinfo=None)
resource.meta.modified = modified


def get_dates(self):
"""
Extract creation and last modification date from the vcs and include
@@ -60,7 +61,10 @@ class VCSDatesPlugin(Plugin):
#
# Git Dates
#


class GitDatesPlugin(VCSDatesPlugin):

def __init__(self, site):
super(GitDatesPlugin, self).__init__(site, 'git')

@@ -78,7 +82,8 @@ class GitDatesPlugin(VCSDatesPlugin):
]).split("\n")
commits = commits[:-1]
except subprocess.CalledProcessError:
self.logger.warning("Unable to get git history for [%s]" % resource)
self.logger.warning(
"Unable to get git history for [%s]" % resource)
commits = None

if commits:
@@ -93,6 +98,8 @@ class GitDatesPlugin(VCSDatesPlugin):
#
# Mercurial Dates
#


class MercurialDatesPlugin(VCSDatesPlugin):

def __init__(self, site):
@@ -105,12 +112,12 @@ class MercurialDatesPlugin(VCSDatesPlugin):
# Run hg log --template={date|isodatesec}
try:
commits = subprocess.check_output([
"hg", "log", "--template={date|isodatesec}\n",
resource.path]).split('\n')
"hg", "log", "--template={date|isodatesec}\n",
resource.path]).split('\n')
commits = commits[:-1]
except subprocess.CalledProcessError:
self.logger.warning("Unable to get mercurial history for [%s]"
% resource)
% resource)
commits = None

if not commits:


+ 16
- 11
hyde/ext/publishers/dvcs.py View File

@@ -8,6 +8,7 @@ from hyde.publisher import Publisher
import abc
from subprocess import Popen, PIPE


class DVCS(Publisher):
__metaclass__ = abc.ABCMeta

@@ -19,23 +20,28 @@ class DVCS(Publisher):
self.switch(self.branch)

@abc.abstractmethod
def pull(self): pass
def pull(self):
pass

@abc.abstractmethod
def push(self): pass
def push(self):
pass

@abc.abstractmethod
def commit(self, message): pass
def commit(self, message):
pass

@abc.abstractmethod
def switch(self, branch): pass
def switch(self, branch):
pass

@abc.abstractmethod
def add(self, path="."): pass
def add(self, path="."):
pass

@abc.abstractmethod
def merge(self, branch): pass
def merge(self, branch):
pass

def publish(self):
super(DVCS, self).publish()
@@ -47,8 +53,8 @@ class DVCS(Publisher):
self.push()



class Git(DVCS):

"""
Acts as a publisher to a git repository. Can be used to publish to
github pages.
@@ -56,7 +62,7 @@ class Git(DVCS):

def add(self, path="."):
cmd = Popen('git add "%s"' % path,
cwd=unicode(self.path), stdout=PIPE, shell=True)
cwd=unicode(self.path), stdout=PIPE, shell=True)
cmdresult = cmd.communicate()[0]
if cmd.returncode:
raise Exception(cmdresult)
@@ -79,7 +85,6 @@ class Git(DVCS):
if cmd.returncode:
raise Exception(cmdresult)


def commit(self, message):
cmd = Popen('git commit -a -m"%s"' % message,
cwd=unicode(self.path), stdout=PIPE, shell=True)
@@ -100,4 +105,4 @@ class Git(DVCS):
cwd=unicode(self.path), stdout=PIPE, shell=True)
cmdresult = cmd.communicate()[0]
if cmd.returncode:
raise Exception(cmdresult)
raise Exception(cmdresult)

+ 22
- 24
hyde/ext/publishers/pyfs.py View File

@@ -32,15 +32,14 @@ except ImportError:
raise



class PyFS(Publisher):

def initialize(self, settings):
self.settings = settings
self.url = settings.url
self.check_mtime = getattr(settings,"check_mtime",False)
self.check_etag = getattr(settings,"check_etag",False)
if self.check_etag and not isinstance(self.check_etag,basestring):
self.check_mtime = getattr(settings, "check_mtime", False)
self.check_etag = getattr(settings, "check_etag", False)
if self.check_etag and not isinstance(self.check_etag, basestring):
raise ValueError("check_etag must name the etag algorithm")
self.prompt_for_credentials()
self.fs = fsopendir(self.url)
@@ -58,48 +57,47 @@ class PyFS(Publisher):
def publish(self):
super(PyFS, self).publish()
deploy_fs = OSFS(self.site.config.deploy_root_path.path)
for (dirnm,local_filenms) in deploy_fs.walk():
logger.info("Making directory: %s",dirnm)
self.fs.makedir(dirnm,allow_recreate=True)
remote_fileinfos = self.fs.listdirinfo(dirnm,files_only=True)
for (dirnm, local_filenms) in deploy_fs.walk():
logger.info("Making directory: %s", dirnm)
self.fs.makedir(dirnm, allow_recreate=True)
remote_fileinfos = self.fs.listdirinfo(dirnm, files_only=True)
# Process each local file, to see if it needs updating.
for filenm in local_filenms:
filepath = pathjoin(dirnm,filenm)
filepath = pathjoin(dirnm, filenm)
# Try to find an existing remote file, to compare metadata.
for (nm,info) in remote_fileinfos:
for (nm, info) in remote_fileinfos:
if nm == filenm:
break
else:
info = {}
# Skip it if the etags match
if self.check_etag and "etag" in info:
with deploy_fs.open(filepath,"rb") as f:
with deploy_fs.open(filepath, "rb") as f:
local_etag = self._calculate_etag(f)
if info["etag"] == local_etag:
logger.info("Skipping file [etag]: %s",filepath)
logger.info("Skipping file [etag]: %s", filepath)
continue
# Skip it if the mtime is more recent remotely.
if self.check_mtime and "modified_time" in info:
local_mtime = deploy_fs.getinfo(filepath)["modified_time"]
if info["modified_time"] > local_mtime:
logger.info("Skipping file [mtime]: %s",filepath)
logger.info("Skipping file [mtime]: %s", filepath)
continue
# Upload it to the remote filesystem.
logger.info("Uploading file: %s",filepath)
with deploy_fs.open(filepath,"rb") as f:
self.fs.setcontents(filepath,f)
logger.info("Uploading file: %s", filepath)
with deploy_fs.open(filepath, "rb") as f:
self.fs.setcontents(filepath, f)
# Process each remote file, to see if it needs deleting.
for (filenm,info) in remote_fileinfos:
filepath = pathjoin(dirnm,filenm)
for (filenm, info) in remote_fileinfos:
filepath = pathjoin(dirnm, filenm)
if filenm not in local_filenms:
logger.info("Removing file: %s",filepath)
logger.info("Removing file: %s", filepath)
self.fs.remove(filepath)

def _calculate_etag(self,f):
hasher = getattr(hashlib,self.check_etag.lower())()
data = f.read(1024*64)
def _calculate_etag(self, f):
hasher = getattr(hashlib, self.check_etag.lower())()
data = f.read(1024 * 64)
while data:
hasher.update(data)
data = f.read(1024*64)
data = f.read(1024 * 64)
return hasher.hexdigest()


+ 23
- 24
hyde/ext/publishers/pypi.py View File

@@ -19,16 +19,14 @@ from commando.util import getLoggerWithNullHandler
logger = getLoggerWithNullHandler('hyde.ext.publishers.pypi')




class PyPI(Publisher):

def initialize(self, settings):
self.settings = settings
self.project = settings.project
self.url = getattr(settings,"url","https://pypi.python.org/pypi/")
self.username = getattr(settings,"username",None)
self.password = getattr(settings,"password",None)
self.url = getattr(settings, "url", "https://pypi.python.org/pypi/")
self.username = getattr(settings, "username", None)
self.password = getattr(settings, "password", None)
self.prompt_for_credentials()

def prompt_for_credentials(self):
@@ -38,12 +36,13 @@ class PyPI(Publisher):
else:
pypirc = ConfigParser.RawConfigParser()
pypirc.read([pypirc_file])
missing_errs = (ConfigParser.NoSectionError,ConfigParser.NoOptionError)
missing_errs = (
ConfigParser.NoSectionError, ConfigParser.NoOptionError)
# Try to find username in .pypirc
if self.username is None:
if pypirc is not None:
try:
self.username = pypirc.get("server-login","username")
self.username = pypirc.get("server-login", "username")
except missing_errs:
pass
# Prompt for username on command-line
@@ -54,7 +53,7 @@ class PyPI(Publisher):
if self.password is None:
if pypirc is not None:
try:
self.password = pypirc.get("server-login","password")
self.password = pypirc.get("server-login", "password")
except missing_errs:
pass
# Prompt for username on command-line
@@ -73,11 +72,11 @@ class PyPI(Publisher):
# Bundle it up into a zipfile
logger.info("building the zipfile")
root = self.site.config.deploy_root_path
zf = zipfile.ZipFile(tf,"w",zipfile.ZIP_DEFLATED)
zf = zipfile.ZipFile(tf, "w", zipfile.ZIP_DEFLATED)
try:
for item in root.walker.walk_files():
logger.info(" adding file: %s",item.path)
zf.write(item.path,item.get_relative_path(root))
logger.info(" adding file: %s", item.path)
zf.write(item.path, item.get_relative_path(root))
finally:
zf.close()
# Formulate the necessary bits for the HTTP POST.
@@ -85,12 +84,14 @@ class PyPI(Publisher):
authz = self.username + ":" + self.password
authz = "Basic " + standard_b64encode(authz)
boundary = "-----------" + os.urandom(20).encode("hex")
sep_boundary = "\r\n--" + boundary
end_boundary = "\r\n--" + boundary + "--\r\n"
# *F841 local variable 'sep_boundary' is assigned to but never used
# sep_boundary = "\r\n--" + boundary
# *F841 local variable 'end_boundary' is assigned to but never used
# end_boundary = "\r\n--" + boundary + "--\r\n"
content_type = "multipart/form-data; boundary=%s" % (boundary,)
items = ((":action","doc_upload"),("name",self.project))
items = ((":action", "doc_upload"), ("name", self.project))
body_prefix = ""
for (name,value) in items:
for (name, value) in items:
body_prefix += "--" + boundary + "\r\n"
body_prefix += "Content-Disposition: form-data; name=\""
body_prefix += name + "\"\r\n\r\n"
@@ -110,24 +111,24 @@ class PyPI(Publisher):
con.connect()
try:
con.putrequest("POST", self.url)
con.putheader("Content-Type",content_type)
con.putheader("Content-Length",str(content_length))
con.putheader("Authorization",authz)
con.putheader("Content-Type", content_type)
con.putheader("Content-Length", str(content_length))
con.putheader("Authorization", authz)
con.endheaders()
con.send(body_prefix)
tf.seek(0)
data = tf.read(1024*32)
data = tf.read(1024 * 32)
while data:
con.send(data)
data = tf.read(1024*32)
data = tf.read(1024 * 32)
con.send(body_suffix)
r = con.getresponse()
try:
# PyPI tries to redirect to the page on success.
if r.status in (200,301,):
if r.status in (200, 301,):
logger.info("success!")
else:
msg = "Upload failed: %s %s" % (r.status,r.reason,)
msg = "Upload failed: %s %s" % (r.status, r.reason,)
raise Exception(msg)
finally:
r.close()
@@ -135,5 +136,3 @@ class PyPI(Publisher):
con.close()
finally:
tf.close()



+ 3
- 1
hyde/ext/publishers/ssh.py View File

@@ -34,7 +34,9 @@ from hyde.publisher import Publisher

from subprocess import Popen, PIPE


class SSH(Publisher):

def initialize(self, settings):
self.settings = settings
self.username = settings.username
@@ -47,7 +49,7 @@ class SSH(Publisher):
command = "{command} {opts} ./ {username}{server}:{target}".format(
command=self.command,
opts=self.opts,
username=self.username+'@' if self.username else '',
username=self.username + '@' if self.username else '',
server=self.server,
target=self.target)
deploy_path = self.site.config.deploy_root_path.path


+ 160
- 117
hyde/ext/templates/jinja.py View File

@@ -29,10 +29,13 @@ from commando.util import getLoggerWithNullHandler

logger = getLoggerWithNullHandler('hyde.engine.Jinja2')


class SilentUndefined(Undefined):

"""
A redefinition of undefined that eats errors.
"""

def __getattr__(self, name):
return self

@@ -41,6 +44,7 @@ class SilentUndefined(Undefined):
def __call__(self, *args, **kwargs):
return self


@contextfunction
def media_url(context, path, safe=None):
"""
@@ -48,6 +52,7 @@ def media_url(context, path, safe=None):
"""
return context['site'].media_url(path, safe)


@contextfunction
def content_url(context, path, safe=None):
"""
@@ -55,6 +60,7 @@ def content_url(context, path, safe=None):
"""
return context['site'].content_url(path, safe)


@contextfunction
def full_url(context, path, safe=None):
"""
@@ -62,6 +68,7 @@ def full_url(context, path, safe=None):
"""
return context['site'].full_url(path, safe)


@contextfilter
def urlencode(ctx, url, safe=None):
if safe is not None:
@@ -69,16 +76,18 @@ def urlencode(ctx, url, safe=None):
else:
return quote(url.encode('utf8'))


@contextfilter
def urldecode(ctx, url):
return unquote(url).decode('utf8')


@contextfilter
def date_format(ctx, dt, fmt=None):
if not dt:
dt = datetime.now()
if not isinstance(dt, datetime) or \
not isinstance(dt, date):
not isinstance(dt, date):
logger.error("Date format called on a non date object")
return dt

@@ -93,9 +102,11 @@ def date_format(ctx, dt, fmt=None):
def islice(iterable, start=0, stop=3, step=1):
return itertools.islice(iterable, start, stop, step)


def top(iterable, count=3):
return islice(iterable, stop=count)


def xmldatetime(dt):
if not dt:
dt = datetime.now()
@@ -105,6 +116,7 @@ def xmldatetime(dt):
zprefix = tz[:3] + ":" + tz[3:]
return dt.strftime("%Y-%m-%dT%H:%M:%S") + zprefix


@environmentfilter
def asciidoc(env, value):
"""
@@ -122,9 +134,11 @@ def asciidoc(env, value):
asciidoc = AsciiDocAPI()
asciidoc.options('--no-header-footer')
result = StringIO.StringIO()
asciidoc.execute(StringIO.StringIO(output.encode('utf-8')), result, backend='html4')
asciidoc.execute(
StringIO.StringIO(output.encode('utf-8')), result, backend='html4')
return unicode(result.getvalue(), "utf-8")


@environmentfilter
def markdown(env, value):
"""
@@ -140,14 +154,15 @@ def markdown(env, value):
if hasattr(env.config, 'markdown'):
d['extensions'] = getattr(env.config.markdown, 'extensions', [])
d['extension_configs'] = getattr(env.config.markdown,
'extension_configs',
Expando({})).to_dict()
'extension_configs',
Expando({})).to_dict()
if hasattr(env.config.markdown, 'output_format'):
d['output_format'] = env.config.markdown.output_format
marked = md.Markdown(**d)

return marked.convert(output)


@environmentfilter
def restructuredtext(env, value):
"""
@@ -161,18 +176,20 @@ def restructuredtext(env, value):

highlight_source = False
if hasattr(env.config, 'restructuredtext'):
highlight_source = getattr(env.config.restructuredtext, 'highlight_source', False)
highlight_source = getattr(
env.config.restructuredtext, 'highlight_source', False)
extensions = getattr(env.config.restructuredtext, 'extensions', [])
import imp
for extension in extensions:
imp.load_module(extension, *imp.find_module(extension))

if highlight_source:
import hyde.lib.pygments.rst_directive
import hyde.lib.pygments.rst_directive # noqa

parts = publish_parts(source=value, writer_name="html")
return parts['html_body']


@environmentfilter
def syntax(env, value, lexer=None, filename=None):
"""
@@ -184,17 +201,17 @@ def syntax(env, value, lexer=None, filename=None):
from pygments import formatters
except ImportError:
logger.error(u"pygments library is required to"
" use syntax highlighting tags.")
" use syntax highlighting tags.")
raise TemplateError("Cannot load pygments")

pyg = (lexers.get_lexer_by_name(lexer)
if lexer else
lexers.guess_lexer(value))
if lexer else
lexers.guess_lexer(value))
settings = {}
if hasattr(env.config, 'syntax'):
settings = getattr(env.config.syntax,
'options',
Expando({})).to_dict()
'options',
Expando({})).to_dict()

formatter = formatters.HtmlFormatter(**settings)
code = pygments.highlight(value, pyg, formatter)
@@ -204,10 +221,13 @@ def syntax(env, value, lexer=None, filename=None):
if not getattr(env.config.syntax, 'use_figure', True):
return Markup(code)
return Markup(
'<div class="codebox"><figure class="code">%s<figcaption>%s</figcaption></figure></div>\n\n'
% (code, caption))
'<div class="codebox"><figure class="code">%s<figcaption>'
'%s</figcaption></figure></div>\n\n'
% (code, caption))


class Spaceless(Extension):

"""
Emulates the django spaceless template tag.
"""
@@ -220,10 +240,10 @@ class Spaceless(Extension):
"""
lineno = parser.stream.next().lineno
body = parser.parse_statements(['name:endspaceless'],
drop_needle=True)
drop_needle=True)
return nodes.CallBlock(
self.call_method('_render_spaceless'),
[], [], body).set_lineno(lineno)
self.call_method('_render_spaceless'),
[], [], body).set_lineno(lineno)

def _render_spaceless(self, caller=None):
"""
@@ -235,7 +255,9 @@ class Spaceless(Extension):
return ''
return re.sub(r'>\s+<', '><', unicode(caller().strip()))


class Asciidoc(Extension):

"""
A wrapper around the asciidoc filter for syntactic sugar.
"""
@@ -243,14 +265,15 @@ class Asciidoc(Extension):

def parse(self, parser):
"""
Parses the statements and defers to the callback for asciidoc processing.
Parses the statements and defers to the callback
for asciidoc processing.
"""
lineno = parser.stream.next().lineno
body = parser.parse_statements(['name:endasciidoc'], drop_needle=True)

return nodes.CallBlock(
self.call_method('_render_asciidoc'),
[], [], body).set_lineno(lineno)
self.call_method('_render_asciidoc'),
[], [], body).set_lineno(lineno)

def _render_asciidoc(self, caller=None):
"""
@@ -261,7 +284,9 @@ class Asciidoc(Extension):
output = caller().strip()
return asciidoc(self.environment, output)


class Markdown(Extension):

"""
A wrapper around the markdown filter for syntactic sugar.
"""
@@ -269,14 +294,15 @@ class Markdown(Extension):

def parse(self, parser):
"""
Parses the statements and defers to the callback for markdown processing.
Parses the statements and defers to the callback
for markdown processing.
"""
lineno = parser.stream.next().lineno
body = parser.parse_statements(['name:endmarkdown'], drop_needle=True)

return nodes.CallBlock(
self.call_method('_render_markdown'),
[], [], body).set_lineno(lineno)
self.call_method('_render_markdown'),
[], [], body).set_lineno(lineno)

def _render_markdown(self, caller=None):
"""
@@ -287,7 +313,9 @@ class Markdown(Extension):
output = caller().strip()
return markdown(self.environment, output)


class restructuredText(Extension):

"""
A wrapper around the restructuredtext filter for syntactic sugar
"""
@@ -298,10 +326,11 @@ class restructuredText(Extension):
Simply extract our content
"""
lineno = parser.stream.next().lineno
body = parser.parse_statements(['name:endrestructuredtext'], drop_needle=True)
body = parser.parse_statements(
['name:endrestructuredtext'], drop_needle=True)

return nodes.CallBlock(self.call_method('_render_rst'), [], [], body
).set_lineno(lineno)
).set_lineno(lineno)

def _render_rst(self, caller=None):
"""
@@ -312,7 +341,9 @@ class restructuredText(Extension):
output = caller().strip()
return restructuredtext(self.environment, output)


class YamlVar(Extension):

"""
An extension that converts the content between the tags
into an yaml object and sets the value in the given
@@ -330,16 +361,15 @@ class YamlVar(Extension):
var = parser.stream.expect('name').value
body = parser.parse_statements(['name:endyaml'], drop_needle=True)
return [
nodes.Assign(
nodes.Name(var, 'store'),
nodes.Const({})
).set_lineno(lineno),
nodes.CallBlock(
self.call_method('_set_yaml',
args=[nodes.Name(var, 'load')]),
[], [], body).set_lineno(lineno)
]

nodes.Assign(
nodes.Name(var, 'store'),
nodes.Const({})
).set_lineno(lineno),
nodes.CallBlock(
self.call_method('_set_yaml',
args=[nodes.Name(var, 'load')]),
[], [], body).set_lineno(lineno)
]

def _set_yaml(self, var, caller=None):
"""
@@ -356,6 +386,7 @@ class YamlVar(Extension):
var.update(yaml.load(out))
return ''


def parse_kwargs(parser):
"""
Parses keyword arguments in tags.
@@ -368,17 +399,19 @@ def parse_kwargs(parser):
value = nodes.Const(parser.stream.next().value)
return (name, value)


class Syntax(Extension):

"""
A wrapper around the syntax filter for syntactic sugar.
"""

tags = set(['syntax'])


def parse(self, parser):
"""
Parses the statements and defers to the callback for pygments processing.
Parses the statements and defers to the callback for
pygments processing.
"""
lineno = parser.stream.next().lineno
lex = nodes.Const(None)
@@ -392,8 +425,8 @@ class Syntax(Extension):
(_, value1) = parse_kwargs(parser)

(lex, filename) = (value, value1) \
if name == 'lex' \
else (value1, value)
if name == 'lex' \
else (value1, value)
else:
lex = nodes.Const(parser.stream.next().value)
if parser.stream.skip_if('comma'):
@@ -401,10 +434,9 @@ class Syntax(Extension):

body = parser.parse_statements(['name:endsyntax'], drop_needle=True)
return nodes.CallBlock(
self.call_method('_render_syntax',
args=[lex, filename]),
[], [], body).set_lineno(lineno)

self.call_method('_render_syntax',
args=[lex, filename]),
[], [], body).set_lineno(lineno)

def _render_syntax(self, lex, filename, caller=None):
"""
@@ -415,7 +447,9 @@ class Syntax(Extension):
output = caller().strip()
return syntax(self.environment, output, lex, filename)


class IncludeText(Extension):

"""
Automatically runs `markdown` and `typogrify` on included
files.
@@ -429,8 +463,8 @@ class IncludeText(Extension):
"""
node = parser.parse_include()
return nodes.CallBlock(
self.call_method('_render_include_text'),
[], [], [node]).set_lineno(node.lineno)
self.call_method('_render_include_text'),
[], [], [node]).set_lineno(node.lineno)

def _render_include_text(self, caller=None):
"""
@@ -448,7 +482,9 @@ class IncludeText(Extension):

MARKINGS = '_markings_'


class Reference(Extension):

"""
Marks a block in a template such that its available for use
when referenced using a `refer` tag.
@@ -465,11 +501,13 @@ class Reference(Extension):
tag = token.value
name = parser.stream.next().value
body = parser.parse_statements(['name:end%s' % tag], drop_needle=True)
return nodes.CallBlock(
self.call_method('_render_output',
args=[nodes.Name(MARKINGS, 'load'), nodes.Const(name)]),
[], [], body).set_lineno(lineno)

return nodes.CallBlock(self.call_method('_render_output',
args=[
nodes.Name(MARKINGS,
'load'),
nodes.Const(name)
]), [], [],
body).set_lineno(lineno)

def _render_output(self, markings, name, caller=None):
"""
@@ -482,7 +520,9 @@ class Reference(Extension):
markings[name] = out
return out


class Refer(Extension):

"""
Imports content blocks specified in the referred template as
variables in a given namespace.
@@ -507,43 +547,43 @@ class Refer(Extension):
temp = parser.free_identifier(lineno)

return [
nodes.Assign(
nodes.Name(temp.name, 'store'),
nodes.Name(MARKINGS, 'load')
).set_lineno(lineno),
nodes.Assign(
nodes.Name(MARKINGS, 'store'),
nodes.Const({})).set_lineno(lineno),
nodes.Assign(
nodes.Name(namespace, 'store'),
nodes.Const({})).set_lineno(lineno),
nodes.CallBlock(
self.call_method('_push_resource',
args=[
nodes.Name(namespace, 'load'),
nodes.Name('site', 'load'),
nodes.Name('resource', 'load'),
template]),
[], [], []).set_lineno(lineno),
nodes.Assign(
nodes.Name('resource', 'store'),
nodes.Getitem(nodes.Name(namespace, 'load'),
nodes.Const('resource'), 'load')
).set_lineno(lineno),
nodes.CallBlock(
self.call_method('_assign_reference',
args=[
nodes.Name(MARKINGS, 'load'),
nodes.Name(namespace, 'load')]),
[], [], [includeNode]).set_lineno(lineno),
nodes.Assign(nodes.Name('resource', 'store'),
nodes.Getitem(nodes.Name(namespace, 'load'),
nodes.Const('parent_resource'), 'load')
).set_lineno(lineno),
nodes.Assign(
nodes.Name(MARKINGS, 'store'),
nodes.Name(temp.name, 'load')
).set_lineno(lineno),
nodes.Assign(
nodes.Name(temp.name, 'store'),
nodes.Name(MARKINGS, 'load')
).set_lineno(lineno),
nodes.Assign(
nodes.Name(MARKINGS, 'store'),
nodes.Const({})).set_lineno(lineno),
nodes.Assign(
nodes.Name(namespace, 'store'),
nodes.Const({})).set_lineno(lineno),
nodes.CallBlock(
self.call_method('_push_resource',
args=[
nodes.Name(namespace, 'load'),
nodes.Name('site', 'load'),
nodes.Name('resource', 'load'),
template]),
[], [], []).set_lineno(lineno),
nodes.Assign(
nodes.Name('resource', 'store'),
nodes.Getitem(nodes.Name(namespace, 'load'),
nodes.Const('resource'), 'load')
).set_lineno(lineno),
nodes.CallBlock(
self.call_method('_assign_reference',
args=[
nodes.Name(MARKINGS, 'load'),
nodes.Name(namespace, 'load')]),
[], [], [includeNode]).set_lineno(lineno),
nodes.Assign(nodes.Name('resource', 'store'),
nodes.Getitem(nodes.Name(namespace, 'load'),
nodes.Const('parent_resource'), 'load')
).set_lineno(lineno),
nodes.Assign(
nodes.Name(MARKINGS, 'store'),
nodes.Name(temp.name, 'load')
).set_lineno(lineno),
]

def _push_resource(self, namespace, site, resource, template, caller):
@@ -553,10 +593,10 @@ class Refer(Extension):
namespace['parent_resource'] = resource
if not hasattr(resource, 'depends'):
resource.depends = []
if not template in resource.depends:
if template not in resource.depends:
resource.depends.append(template)
namespace['resource'] = site.content.resource_from_relative_path(
template)
template)
return ''

def _assign_reference(self, markings, namespace, caller):
@@ -573,6 +613,7 @@ class Refer(Extension):


class HydeLoader(FileSystemLoader):

"""
A wrapper around the file system loader that performs
hyde specific tweaks.
@@ -582,9 +623,9 @@ class HydeLoader(FileSystemLoader):
config = site.config if hasattr(site, 'config') else None
if config:
super(HydeLoader, self).__init__([
unicode(config.content_root_path),
unicode(config.layout_root_path),
])
unicode(config.content_root_path),
unicode(config.layout_root_path),
])
else:
super(HydeLoader, self).__init__(unicode(sitepath))

@@ -604,8 +645,8 @@ class HydeLoader(FileSystemLoader):
try:
(contents,
filename,
date) = super(HydeLoader, self).get_source(
environment, template)
date) = super(HydeLoader, self).get_source(
environment, template)
except UnicodeDecodeError:
HydeException.reraise(
"Unicode error when processing %s" % template, sys.exc_info())
@@ -624,6 +665,7 @@ class HydeLoader(FileSystemLoader):

# pylint: disable-msg=W0104,E0602,W0613,R0201
class Jinja2Template(Template):

"""
The Jinja2 Template implementation
"""
@@ -638,23 +680,23 @@ class Jinja2Template(Template):
self.site = site
self.engine = engine
self.preprocessor = (engine.preprocessor
if hasattr(engine, 'preprocessor') else None)
if hasattr(engine, 'preprocessor') else None)

self.loader = HydeLoader(self.sitepath, site, self.preprocessor)

default_extensions = [
IncludeText,
Spaceless,
Asciidoc,
Markdown,
restructuredText,
Syntax,
Reference,
Refer,
YamlVar,
'jinja2.ext.do',
'jinja2.ext.loopcontrols',
'jinja2.ext.with_'
IncludeText,
Spaceless,
Asciidoc,
Markdown,
restructuredText,
Syntax,
Reference,
Refer,
YamlVar,
'jinja2.ext.do',
'jinja2.ext.loopcontrols',
'jinja2.ext.with_'
]

defaults = {
@@ -694,12 +736,12 @@ class Jinja2Template(Template):
settings['filters'][name] = getattr(module, function_name)

self.env = Environment(
loader=self.loader,
undefined=SilentUndefined,
line_statement_prefix=settings['line_statement_prefix'],
trim_blocks=True,
bytecode_cache=FileSystemBytecodeCache(),
extensions=settings['extensions'])
loader=self.loader,
undefined=SilentUndefined,
line_statement_prefix=settings['line_statement_prefix'],
trim_blocks=True,
bytecode_cache=FileSystemBytecodeCache(),
extensions=settings['extensions'])
self.env.globals['media_url'] = media_url
self.env.globals['content_url'] = content_url
self.env.globals['full_url'] = full_url
@@ -738,7 +780,6 @@ class Jinja2Template(Template):
if self.env.bytecode_cache:
self.env.bytecode_cache.clear()


def get_dependencies(self, path):
"""
Finds dependencies hierarchically based on the included
@@ -774,10 +815,12 @@ class Jinja2Template(Template):
The pattern for matching selected template statements.
"""
return {
"block_open": '\s*\{\%\s*block\s*([^\s]+)\s*\%\}',
"block_close": '\s*\{\%\s*endblock\s*([^\s]*)\s*\%\}',
"include": '\s*\{\%\s*include\s*(?:\'|\")(.+?\.[^.]*)(?:\'|\")\s*\%\}',
"extends": '\s*\{\%\s*extends\s*(?:\'|\")(.+?\.[^.]*)(?:\'|\")\s*\%\}'
"block_open": '\s*\{\%\s*block\s*([^\s]+)\s*\%\}',
"block_close": '\s*\{\%\s*endblock\s*([^\s]*)\s*\%\}',
"include":
'\s*\{\%\s*include\s*(?:\'|\")(.+?\.[^.]*)(?:\'|\")\s*\%\}',
"extends":
'\s*\{\%\s*extends\s*(?:\'|\")(.+?\.[^.]*)(?:\'|\")\s*\%\}'
}

def get_include_statement(self, path_to_include):


+ 25
- 24
hyde/generator.py View File

@@ -43,7 +43,6 @@ class Generator(object):
site.context = Context.load(site.sitepath, site.config.context)
self.__context__.update(site.context)


@contextmanager
def context_for_resource(self, resource):
"""
@@ -77,7 +76,8 @@ class Generator(object):
providing restricted access to the methods.
"""

def __init__(self, preprocessor=None, postprocessor=None, context_for_path=None):
def __init__(self, preprocessor=None, postprocessor=None,
context_for_path=None):
self.preprocessor = preprocessor
self.postprocessor = postprocessor
self.context_for_path = context_for_path
@@ -86,14 +86,16 @@ class Generator(object):
logger.info("Generating site at [%s]" % self.site.sitepath)
self.template = Template.find_template(self.site)
logger.debug("Using [%s] as the template",
self.template.__class__.__name__)
self.template.__class__.__name__)

logger.info("Configuring the template environment")
preprocessor = self.events.begin_text_resource
postprocessor = self.events.text_resource_complete
proxy = GeneratorProxy(context_for_path=self.context_for_path,
preprocessor=preprocessor,
postprocessor=postprocessor)
self.template.configure(self.site,
engine=GeneratorProxy(
context_for_path=self.context_for_path,
preprocessor=self.events.begin_text_resource,
postprocessor=self.events.text_resource_complete))
engine=proxy)
self.events.template_loaded(self.template)

def initialize(self):
@@ -124,7 +126,7 @@ class Generator(object):

rel_path = resource.relative_path
deps = self.deps[rel_path] if rel_path in self.deps \
else self.update_deps(resource)
else self.update_deps(resource)
return deps

def update_deps(self, resource):
@@ -143,7 +145,8 @@ class Generator(object):
dep_res = self.site.content.resource_from_relative_path(dep)
if dep_res:
if dep_res.relative_path in self.waiting_deps.keys():
self.waiting_deps[dep_res.relative_path].append(rel_path)
self.waiting_deps[
dep_res.relative_path].append(rel_path)
else:
deps.extend(self.get_dependencies(dep_res))
if resource.uses_template:
@@ -166,7 +169,7 @@ class Generator(object):
self.load_site_if_needed()

target = File(self.site.config.deploy_root_path.child(
resource.relative_deploy_path))
resource.relative_deploy_path))
if not target.exists or target.older_than(resource.source_file):
logger.debug("Found changes in %s" % resource)
return True
@@ -209,7 +212,7 @@ class Generator(object):
self.load_site_if_needed()
self.events.begin_site()
logger.info("Generating site to [%s]" %
self.site.config.deploy_root_path)
self.site.config.deploy_root_path)
self.__generate_node__(self.site.content, incremental)
self.events.site_complete()
self.finalize()
@@ -261,9 +264,8 @@ class Generator(object):
except HydeException:
self.generate_all()

def generate_resource_at_path(self,
resource_path=None,
incremental=False):
def generate_resource_at_path(self, resource_path=None,
incremental=False):
"""
Generates a single resource. If resource_path is non-existent or empty,
generates the entire website.
@@ -311,7 +313,6 @@ class Generator(object):
self.__generate_resource__(resource, incremental)
self.events.node_complete(node)


def __generate_resource__(self, resource, incremental=False):
self.refresh_config()
if not resource.is_processable:
@@ -323,7 +324,7 @@ class Generator(object):
logger.debug("Processing [%s]", resource)
with self.context_for_resource(resource) as context:
target = File(self.site.config.deploy_root_path.child(
resource.relative_deploy_path))
resource.relative_deploy_path))
target.parent.make()
if resource.simple_copy:
logger.debug("Simply Copying [%s]", resource)
@@ -334,19 +335,19 @@ class Generator(object):
logger.debug("Rendering [%s]", resource)
try:
text = self.template.render_resource(resource,
context)
context)
except Exception, e:
HydeException.reraise("Error occurred when"
" processing template: [%s]: %s" %
(resource, repr(e)),
sys.exc_info()
)
HydeException.reraise("Error occurred when processing"
"template: [%s]: %s" %
(resource, repr(e)),
sys.exc_info())
else:
text = resource.source_file.read_all()
text = self.events.begin_text_resource(resource, text) or text
text = self.events.begin_text_resource(
resource, text) or text

text = self.events.text_resource_complete(
resource, text) or text
resource, text) or text
target.write(text)
copymode(resource.source_file.path, target.path)
else:


+ 3
- 2
hyde/layout.py View File

@@ -11,6 +11,7 @@ LAYOUTS = "layouts"


class Layout(object):

"""
Represents a layout package
"""
@@ -26,10 +27,10 @@ class Layout(object):
layout_folder = None
if HYDE_DATA in os.environ:
layout_folder = Layout._get_layout_folder(
os.environ[HYDE_DATA], layout_name)
os.environ[HYDE_DATA], layout_name)
if not layout_folder:
layout_folder = Layout._get_layout_folder(
File(__file__).parent, layout_name)
File(__file__).parent, layout_name)
return layout_folder

@staticmethod


+ 4
- 1
hyde/lib/pygments/rst_directive.py View File

@@ -58,7 +58,9 @@ from docutils.parsers.rst import directives, Directive
from pygments import highlight
from pygments.lexers import get_lexer_by_name, TextLexer


class Pygments(Directive):

""" Source code syntax hightlighting.
"""
required_arguments = 1
@@ -75,7 +77,8 @@ class Pygments(Directive):
# no lexer found - use the text one instead of an exception
lexer = TextLexer()
# take an arbitrary option if more than one is given
formatter = self.options and VARIANTS[self.options.keys()[0]] or DEFAULT
formatter = self.options and VARIANTS[
self.options.keys()[0]] or DEFAULT
parsed = highlight(u'\n'.join(self.content), lexer, formatter)
return [nodes.raw('', parsed, format='html')]



+ 2
- 1
hyde/main.py View File

@@ -5,9 +5,10 @@ The hyde executable
"""
from hyde.engine import Engine


def main():
"""Main"""
Engine().run()

if __name__ == "__main__":
main()
main()

+ 18
- 13
hyde/model.py View File

@@ -14,6 +14,7 @@ logger = getLoggerWithNullHandler('hyde.engine')

SEQS = (tuple, list, set, frozenset)


def make_expando(primitive):
"""
Creates an expando object, a sequence of expando objects or just
@@ -29,6 +30,7 @@ def make_expando(primitive):


class Expando(object):

"""
A generic expando class that creates attributes from
the passed in dictionary.
@@ -63,7 +65,6 @@ class Expando(object):
"""
setattr(self, unicode(key).encode('utf-8'), make_expando(value))


def __repr__(self):
return unicode(self.to_dict())

@@ -79,9 +80,9 @@ class Expando(object):
elif isinstance(v, SEQS):
seq = type(v)
result[k] = seq(item.to_dict()
if isinstance(item, Expando)
else item for item in v
)
if isinstance(item, Expando)
else item for item in v
)
else:
result[k] = v
return result
@@ -94,6 +95,7 @@ class Expando(object):


class Context(object):

"""
Wraps the context related functions and utilities.
"""
@@ -125,7 +127,9 @@ class Context(object):

return context


class Dependents(IterableUserDict):

"""
Represents the dependency graph for hyde.
"""
@@ -146,11 +150,14 @@ class Dependents(IterableUserDict):
if self.deps_file.parent.exists:
self.deps_file.write(yaml.dump(self.data))


def _expand_path(sitepath, path):
child = sitepath.child_folder(path)
return Folder(child.fully_expanded_path)


class Config(Expando):

"""
Represents the hyde configuration file
"""
@@ -158,7 +165,7 @@ class Config(Expando):
def __init__(self, sitepath, config_file=None, config_dict=None):
self.default_config = dict(
mode='production',
simple_copy = [],
simple_copy=[],
content_root='content',
deploy_root='deploy',
media_root='media',
@@ -167,9 +174,9 @@ class Config(Expando):
base_url="/",
encode_safe=None,
not_found='404.html',
plugins = [],
ignore = [ "*~", "*.bak", ".hg", ".git", ".svn"],
meta = {
plugins=[],
ignore=["*~", "*.bak", ".hg", ".git", ".svn"],
meta={
"nodemeta": 'meta.yaml'
}
)
@@ -188,7 +195,7 @@ class Config(Expando):
if not self.config_files:
return True
return any((conf.has_changed_since(self.load_time)
for conf in self.config_files))
for conf in self.config_files))

def load(self):
conf = dict(**self.default_config)
@@ -202,15 +209,14 @@ class Config(Expando):
return
self.update(self.load())


def read_config(self, config_file):
"""
Reads the configuration file and updates this
object while allowing for inherited configurations.
"""
conf_file = self.sitepath.child(
config_file if
config_file else 'site.yaml')
config_file if
config_file else 'site.yaml')
conf = {}
if File(conf_file).exists:
self.config_files.append(File(conf_file))
@@ -224,7 +230,6 @@ class Config(Expando):
self.load_time = datetime.now()
return conf


@property
def deploy_root_path(self):
"""


+ 76
- 47
hyde/plugin.py View File

@@ -13,7 +13,6 @@ import os
import re
import subprocess
import sys
import traceback

from commando.util import getLoggerWithNullHandler, load_python_object
from fswrap import File
@@ -22,30 +21,53 @@ 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"
"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
"""
@@ -61,7 +83,8 @@ class PluginProxy(object):
if self.site.plugins:
for plugin in self.site.plugins:
if hasattr(plugin, method_name):
checker = getattr(plugin, 'should_call__' + method_name)
checker = getattr(
plugin, 'should_call__' + method_name)
if checker(*args):
function = getattr(plugin, method_name)
try:
@@ -80,9 +103,11 @@ class PluginProxy(object):

return __call_plugins__
raise HydeException(
"Unknown plugin method [%s] called." % method_name)
"Unknown plugin method [%s] called." % method_name)


class Plugin(object):

"""
The plugin protocol
"""
@@ -92,10 +117,9 @@ class Plugin(object):
super(Plugin, self).__init__()
self.site = site
self.logger = getLoggerWithNullHandler(
'hyde.engine.%s' % self.__class__.__name__)
'hyde.engine.%s' % self.__class__.__name__)
self.template = None


def template_loaded(self, template):
"""
Called when the template for the site has been identified.
@@ -123,7 +147,8 @@ class Plugin(object):
elif name.startswith('should_call__'):
(_, _, method) = name.rpartition('__')
if (method in ('begin_text_resource', 'text_resource_complete',
'begin_binary_resource', 'binary_resource_complete')):
'begin_binary_resource',
'binary_resource_complete')):
result = self._file_filter
elif (method in ('begin_node', 'node_complete')):
result = self._dir_filter
@@ -132,7 +157,7 @@ class Plugin(object):
return True
result = always_true

return result if result else super(Plugin, self).__getattribute__(name)
return result if result else super(Plugin, self).__getattribute__(name)

@property
def settings(self):
@@ -147,7 +172,6 @@ class Plugin(object):
pass
return opts


@property
def plugin_name(self):
"""
@@ -195,26 +219,26 @@ class Plugin(object):
except AttributeError:
filters = None
result = any(fnmatch.fnmatch(resource.path, f)
for f in filters) if filters else True
for f in filters) if filters else True
return result

def _dir_filter(self, node, *args, **kwargs):
"""
Returns True if the node path is a descendant of the include_paths property in
plugin settings.
Returns True if the node path is a descendant of the
include_paths property in plugin settings.
"""
try:
node_filters = self.settings.include_paths
if not isinstance(node_filters, list):
node_filters = [node_filters]
node_filters = [self.site.content.node_from_relative_path(f)
for f in node_filters]
for f in node_filters]
except AttributeError:
node_filters = None
result = any(node.source == f.source or
node.source.is_descendant_of(f.source)
for f in node_filters if f) \
if node_filters else True
node.source.is_descendant_of(f.source)
for f in node_filters if f) \
if node_filters else True
return result

def begin_text_resource(self, resource, text):
@@ -293,7 +317,7 @@ class Plugin(object):
return load_python_object(plugin_name)(site)

site.plugins = [load_plugin(name)
for name in site.config.plugins]
for name in site.config.plugins]

@staticmethod
def get_proxy(site):
@@ -302,7 +326,9 @@ class Plugin(object):
"""
return PluginProxy(site)


class CLTransformer(Plugin):

"""
Handy class for plugins that simply call a command line app to
transform resources.
@@ -333,11 +359,11 @@ class CLTransformer(Plugin):
"""

return ("%(name)s executable path not configured properly. "
"This plugin expects `%(name)s.app` to point "
"to the full path of the `%(exec)s` executable." %
{
"name":self.plugin_name, "exec": self.executable_name
})
"This plugin expects `%(name)s.app` to point "
"to the full path of the `%(exec)s` executable." %
{
"name": self.plugin_name, "exec": self.executable_name
})

@property
def app(self):
@@ -398,7 +424,7 @@ class CLTransformer(Plugin):
if match:
val = args[match]
param = "%s%s" % (self.option_prefix(descriptive),
descriptive)
descriptive)
if descriptive.endswith("="):
param += val
val = None
@@ -414,13 +440,15 @@ class CLTransformer(Plugin):
try:
self.logger.debug(
"Calling executable [%s] with arguments %s" %
(args[0], unicode(args[1:])))
(args[0], unicode(args[1:])))
return subprocess.check_output(args)
except subprocess.CalledProcessError, error:
self.logger.error(error.output)
raise


class TextyPlugin(Plugin):

"""
Base class for text preprocessing plugins.

@@ -493,10 +521,11 @@ class TextyPlugin(Plugin):
"""
Replace a text base pattern with a template statement.
"""
text_open = re.compile(self.open_pattern, re.UNICODE|re.MULTILINE)
text_open = re.compile(self.open_pattern, re.UNICODE | re.MULTILINE)
text = text_open.sub(self.text_to_tag, text)
if self.close_pattern:
text_close = re.compile(self.close_pattern, re.UNICODE|re.MULTILINE)
text_close = re.compile(
self.close_pattern, re.UNICODE | re.MULTILINE)
text = text_close.sub(
partial(self.text_to_tag, start=False), text)
partial(self.text_to_tag, start=False), text)
return text

+ 8
- 4
hyde/publisher.py View File

@@ -8,7 +8,9 @@ Contains abstract classes and utilities that help publishing a website to a
server.
"""


class Publisher(object):

"""
The abstract base class for publishers.
"""
@@ -18,13 +20,14 @@ class Publisher(object):
def __init__(self, site, settings, message):
super(Publisher, self).__init__()
self.logger = getLoggerWithNullHandler(
'hyde.engine.%s' % self.__class__.__name__)
'hyde.engine.%s' % self.__class__.__name__)
self.site = site
self.message = message
self.initialize(settings)

@abc.abstractmethod
def initialize(self, settings): pass
def initialize(self, settings):
pass

@abc.abstractmethod
def publish(self):
@@ -43,7 +46,8 @@ class Publisher(object):
# Find the first configured publisher
try:
publisher = site.config.publisher.__dict__.iterkeys().next()
logger.warning("No default publisher configured. Using: %s" % publisher)
logger.warning(
"No default publisher configured. Using: %s" % publisher)
settings = attrgetter("publisher.%s" % publisher)(site.config)
except (AttributeError, StopIteration):
logger.error(
@@ -56,4 +60,4 @@ class Publisher(object):
raise Exception("Please specify the publisher type in config.")

pub_class = load_python_object(settings.type)
return pub_class(site, settings, message)
return pub_class(site, settings, message)

+ 19
- 13
hyde/server.py View File

@@ -18,7 +18,9 @@ from fswrap import File, Folder
from commando.util import getLoggerWithNullHandler
logger = getLoggerWithNullHandler('hyde.server')


class HydeRequestHandler(SimpleHTTPRequestHandler):

"""
Serves files by regenerating the resource (or)
everything when a request is issued.
@@ -35,7 +37,7 @@ class HydeRequestHandler(SimpleHTTPRequestHandler):
logger.debug("Processing request: [%s]" % self.path)
result = urlparse.urlparse(self.path)
query = urlparse.parse_qs(result.query)
if 'refresh' in query or result.query=='refresh':
if 'refresh' in query or result.query == 'refresh':
self.server.regenerate()
if 'refresh' in query:
del query['refresh']
@@ -48,7 +50,6 @@ class HydeRequestHandler(SimpleHTTPRequestHandler):
else:
SimpleHTTPRequestHandler.do_GET(self)


def translate_path(self, path):
"""
Finds the absolute path of the requested file by
@@ -56,7 +57,8 @@ class HydeRequestHandler(SimpleHTTPRequestHandler):
"""
site = self.server.site
result = urlparse.urlparse(urllib.unquote(self.path).decode('utf-8'))
logger.debug("Trying to load file based on request: [%s]" % result.path)
logger.debug(
"Trying to load file based on request: [%s]" % result.path)
path = result.path.lstrip('/')
res = None
if path.strip() == "" or File(path).kind.strip() == "":
@@ -65,13 +67,16 @@ class HydeRequestHandler(SimpleHTTPRequestHandler):
if isinstance(deployed, Folder):
node = site.content.node_from_relative_path(path)
res = node.get_resource('index.html')
elif hasattr(site.config, 'urlcleaner') and hasattr(site.config.urlcleaner, 'strip_extensions'):
elif hasattr(site.config, 'urlcleaner') and hasattr(
site.config.urlcleaner, 'strip_extensions'):
for ext in site.config.urlcleaner.strip_extensions:
res = site.content.resource_from_relative_deploy_path(path + '.' + ext)
res = site.content.resource_from_relative_deploy_path(
path + '.' + ext)
if res:
break
for ext in site.config.urlcleaner.strip_extensions:
new_path = site.config.deploy_root_path.child(path + '.' + ext)
new_path = site.config.deploy_root_path.child(
path + '.' + ext)
if File(new_path).exists:
return new_path
else:
@@ -83,7 +88,7 @@ class HydeRequestHandler(SimpleHTTPRequestHandler):
else:
self.server.generate_resource(res)
new_path = site.config.deploy_root_path.child(
res.relative_deploy_path)
res.relative_deploy_path)
return new_path

def do_404(self):
@@ -95,13 +100,13 @@ class HydeRequestHandler(SimpleHTTPRequestHandler):
self.redirect(site.config.not_found)
else:
res = site.content.resource_from_relative_deploy_path(
site.config.not_found)
site.config.not_found)

message = "Requested resource not found"
if not res:
logger.error(
"Cannot find the 404 template [%s]."
% site.config.not_found)
% site.config.not_found)
else:
f404 = File(self.translate_path(site.config.not_found))
if f404.exists:
@@ -118,6 +123,7 @@ class HydeRequestHandler(SimpleHTTPRequestHandler):


class HydeWebServer(HTTPServer):

"""
The hyde web server that regenerates the resource, node or site when
a request is issued.
@@ -133,7 +139,7 @@ class HydeWebServer(HTTPServer):
self.__shutdown_request = False
self.map_extensions()
HTTPServer.__init__(self, (address, port),
HydeRequestHandler)
HydeRequestHandler)

def map_extensions(self):
"""
@@ -161,7 +167,7 @@ class HydeWebServer(HTTPServer):
self.generator.generate_all(incremental=False)
except Exception, exception:
logger.error('Error occured when regenerating the site [%s]'
% exception.message)
% exception.message)
logger.debug(traceback.format_exc())

def generate_node(self, node):
@@ -179,7 +185,7 @@ class HydeWebServer(HTTPServer):
except Exception, exception:
logger.error(
'Error [%s] occured when generating the node [%s]'
% (repr(exception), node))
% (repr(exception), node))
logger.debug(traceback.format_exc())

def generate_resource(self, resource):
@@ -198,5 +204,5 @@ class HydeWebServer(HTTPServer):
except Exception, exception:
logger.error(
'Error [%s] occured when serving the resource [%s]'
% (repr(exception), resource))
% (repr(exception), resource))
logger.debug(traceback.format_exc())

+ 23
- 19
hyde/site.py View File

@@ -15,6 +15,7 @@ from hyde.model import Config
from commando.util import getLoggerWithNullHandler
from fswrap import FS, File, Folder


def path_normalized(f):
@wraps(f)
def wrapper(self, path):
@@ -23,6 +24,7 @@ def path_normalized(f):

logger = getLoggerWithNullHandler('hyde.engine')


class Processable(object):
"""
A node or resource.
@@ -64,8 +66,8 @@ class Processable(object):
after its been processed.
"""
return self._relative_deploy_path \
if self._relative_deploy_path is not None \
else self.relative_path
if self._relative_deploy_path is not None \
else self.relative_path

def set_relative_deploy_path(self, path):
"""
@@ -75,7 +77,8 @@ class Processable(object):
self._relative_deploy_path = path
self.site.content.deploy_path_changed(self)

relative_deploy_path = property(get_relative_deploy_path, set_relative_deploy_path)
relative_deploy_path = property(get_relative_deploy_path,
set_relative_deploy_path)

@property
def url(self):
@@ -118,9 +121,10 @@ class Resource(Processable):

@property
def slug(self):
#TODO: Add a more sophisticated slugify method
# TODO: Add a more sophisticated slugify method
return self.source.name_without_extension


class Node(Processable):
"""
Represents any folder that is processed by hyde
@@ -159,7 +163,7 @@ class Node(Processable):

if self.contains_resource(resource_name):
return self.root.resource_from_path(
self.source_folder.child(resource_name))
self.source_folder.child(resource_name))
return None

def add_child_node(self, folder):
@@ -223,6 +227,7 @@ class Node(Processable):
"""
return self.source_folder.get_relative_path(self.root.source_folder)


class RootNode(Node):
"""
Represents one of the roots of site: Content, Media or Layout
@@ -253,7 +258,7 @@ class RootNode(Node):
If no match is found it returns None.
"""
return self.node_from_path(
self.source_folder.child(unicode(relative_path)))
self.source_folder.child(unicode(relative_path)))

@path_normalized
def resource_from_path(self, path):
@@ -270,7 +275,7 @@ class RootNode(Node):
If no match is found it returns None.
"""
return self.resource_from_path(
self.source_folder.child(relative_path))
self.source_folder.child(relative_path))

def deploy_path_changed(self, item):
"""
@@ -320,7 +325,7 @@ class RootNode(Node):
node = node.add_child_node(h_folder)
self.node_map[unicode(h_folder)] = node
logger.debug("Added node [%s] to [%s]" % (
node.relative_path, self.source_folder))
node.relative_path, self.source_folder))

return node

@@ -350,11 +355,10 @@ class RootNode(Node):
self.resource_map[unicode(afile)] = resource
relative_path = resource.relative_path
resource.simple_copy = any(fnmatch.fnmatch(relative_path, pattern)
for pattern
in self.site.config.simple_copy)
for pattern in self.site.config.simple_copy)

logger.debug("Added resource [%s] to [%s]" %
(resource.relative_path, self.source_folder))
(resource.relative_path, self.source_folder))
return resource

def load(self):
@@ -396,6 +400,7 @@ def _encode_path(base, path, safe):
path = quote(path, safe) if safe is not None else quote(path)
return base.rstrip('/') + '/' + path.lstrip('/')


class Site(object):
"""
Represents the site to be generated.
@@ -420,9 +425,8 @@ class Site(object):
"""
if self.config.needs_refresh():
logger.debug("Refreshing config data")
self.config = Config(self.sitepath,
self.config.config_file,
self.config.config_dict)
self.config = Config(self.sitepath, self.config.config_file,
self.config.config_dict)

def reload_if_needed(self):
"""
@@ -453,13 +457,13 @@ class Site(object):
"""
return _encode_path(self.config.base_url, path, self._safe_chars(safe))


def media_url(self, path, safe=None):
"""
Returns the media url by appending the media base url from the config
with the given path. The return value is url encoded.
"""
return _encode_path(self.config.media_url, path, self._safe_chars(safe))
return _encode_path(self.config.media_url, path,
self._safe_chars(safe))

def full_url(self, path, safe=None):
"""
@@ -467,12 +471,12 @@ class Site(object):
configuration and returns the appropriate url. The return value
is url encoded.
"""
if urlparse.urlparse(path)[:2] != ("",""):
if urlparse.urlparse(path)[:2] != ("", ""):
return path

if self.is_media(path):
relative_path = File(path).get_relative_path(
Folder(self.config.media_root))
Folder(self.config.media_root))
return self.media_url(relative_path, safe)
else:
return self.content_url(path, safe)
@@ -482,4 +486,4 @@ class Site(object):
Given the relative path, determines if it is content or media.
"""
folder = self.content.source.child_folder(path)
return folder.is_descendant_of(self.config.media_root_path)
return folder.is_descendant_of(self.config.media_root_path)

+ 3
- 1
hyde/template.py View File

@@ -11,6 +11,7 @@ from commando.util import getLoggerWithNullHandler


class HtmlWrap(object):

"""
A wrapper class for raw html.

@@ -36,7 +37,9 @@ class HtmlWrap(object):
return self.raw
return self.q(selector).html()


class Template(object):

"""
Interface for hyde template engines. To use a different template engine,
the following interface must be implemented.
@@ -50,7 +53,6 @@ class Template(object):

@abc.abstractmethod
def configure(self, site, engine):

"""
The site object should contain a config attribute. The config object
is a simple YAML object with required settings. The template


+ 7
- 6
hyde/tests/ext/test_auto_extend.py View File

@@ -20,7 +20,7 @@ class TestAutoExtend(object):
def setUp(self):
TEST_SITE.make()
TEST_SITE.parent.child_folder(
'sites/test_jinja').copy_contents_to(TEST_SITE)
'sites/test_jinja').copy_contents_to(TEST_SITE)

def tearDown(self):
TEST_SITE.delete()
@@ -33,7 +33,8 @@ class TestAutoExtend(object):
gen = Generator(s)
gen.generate_resource_at_path(bd.path)
res = s.content.resource_from_path(bd.path)
target = File(s.config.deploy_root_path.child(res.relative_deploy_path))
target = File(
s.config.deploy_root_path.child(res.relative_deploy_path))
assert target.exists
text = target.read_all()
q = PyQuery(text)
@@ -44,7 +45,8 @@ class TestAutoExtend(object):
s.config.plugins = ['hyde.ext.plugins.meta.MetaPlugin',
'hyde.ext.plugins.meta.AutoExtendPlugin',
'hyde.ext.plugins.text.BlockdownPlugin']
txt ="This template tests to make sure blocks can be replaced with markdownish syntax."
txt = ("This template tests to make sure blocks can be replaced with"
"markdownish syntax.")
templ = """
---
extends: base.html
@@ -54,14 +56,13 @@ extends: base.html
====/title========"""
self.assert_extended(s, txt, templ)



def test_can_auto_extend_with_default_blocks(self):
s = Site(TEST_SITE)
s.config.plugins = ['hyde.ext.plugins.meta.MetaPlugin',
'hyde.ext.plugins.meta.AutoExtendPlugin',
'hyde.ext.plugins.text.BlockdownPlugin']
txt ="This template tests to make sure blocks can be replaced with markdownish syntax."
txt = ("This template tests to make sure blocks can be replaced with"
"markdownish syntax.")
templ = """
---
extends: base.html


+ 5
- 3
hyde/tests/ext/test_blockdown.py View File

@@ -18,7 +18,7 @@ class TestBlockdown(object):
def setUp(self):
TEST_SITE.make()
TEST_SITE.parent.child_folder(
'sites/test_jinja').copy_contents_to(TEST_SITE)
'sites/test_jinja').copy_contents_to(TEST_SITE)

def tearDown(self):
TEST_SITE.delete()
@@ -26,7 +26,8 @@ class TestBlockdown(object):
def test_can_parse_blockdown(self):
s = Site(TEST_SITE)
s.config.plugins = ['hyde.ext.plugins.text.BlockdownPlugin']
txt ="This template tests to make sure blocks can be replaced with markdownish syntax."
txt = ("This template tests to make sure blocks can be replaced"
"with markdownish syntax.")
templ = """
{%% extends "base.html" %%}
=====title========
@@ -39,7 +40,8 @@ class TestBlockdown(object):
gen = Generator(s)
gen.generate_resource_at_path(bd.path)
res = s.content.resource_from_path(bd.path)
target = File(s.config.deploy_root_path.child(res.relative_deploy_path))
target = File(
s.config.deploy_root_path.child(res.relative_deploy_path))
assert target.exists
text = target.read_all()
q = PyQuery(text)


+ 8
- 4
hyde/tests/ext/test_combine.py View File

@@ -12,14 +12,17 @@ from fswrap import File, Folder
COMBINE_SOURCE = File(__file__).parent.child_folder('combine')
TEST_SITE = File(__file__).parent.parent.child_folder('_test')


class CombineTester(object):

def _test_combine(self, content):
s = Site(TEST_SITE)
s.config.plugins = [
'hyde.ext.plugins.meta.MetaPlugin',
'hyde.ext.plugins.structure.CombinePlugin']
source = TEST_SITE.child('content/media/js/script.js')
target = File(Folder(s.config.deploy_root_path).child('media/js/script.js'))
target = File(
Folder(s.config.deploy_root_path).child('media/js/script.js'))
File(source).write(content)

gen = Generator(s)
@@ -29,12 +32,13 @@ class CombineTester(object):
text = target.read_all()
return text, s


class TestCombine(CombineTester):

def setUp(self):
TEST_SITE.make()
TEST_SITE.parent.child_folder(
'sites/test_jinja').copy_contents_to(TEST_SITE)
'sites/test_jinja').copy_contents_to(TEST_SITE)
TEST_SITE.child_folder('content/media/js').make()
COMBINE_SOURCE.copy_contents_to(TEST_SITE.child('content/media/js'))

@@ -107,7 +111,7 @@ combine:
---

First line""")
for i in range(1,4):
for i in range(1, 4):
assert not File(Folder(s.config.deploy_root_path).
child('media/js/script.%d.js' % i)).exists

@@ -117,7 +121,7 @@ class TestCombinePaths(CombineTester):
def setUp(self):
TEST_SITE.make()
TEST_SITE.parent.child_folder(
'sites/test_jinja').copy_contents_to(TEST_SITE)
'sites/test_jinja').copy_contents_to(TEST_SITE)
TEST_SITE.child_folder('content/media/js').make()
JS = TEST_SITE.child_folder('content/scripts').make()
S1 = JS.child_folder('s1').make()


+ 5
- 4
hyde/tests/ext/test_depends.py View File

@@ -17,10 +17,10 @@ class TestDepends(object):
def setUp(self):
TEST_SITE.make()
TEST_SITE.parent.child_folder(
'sites/test_jinja').copy_contents_to(TEST_SITE)
'sites/test_jinja').copy_contents_to(TEST_SITE)
TEST_SITE.parent.child_folder(
'templates/jinja2').copy_contents_to(
TEST_SITE.child_folder('content'))
'templates/jinja2').copy_contents_to(
TEST_SITE.child_folder('content'))

def tearDown(self):
TEST_SITE.delete()
@@ -40,6 +40,7 @@ depends: index.html
gen = Generator(s)
gen.load_site_if_needed()
gen.load_template_if_needed()

def dateformat(x):
return x.strftime('%Y-%m-%d')
gen.template.env.filters['dateformat'] = dateformat
@@ -52,4 +53,4 @@ depends: index.html

assert 'helpers.html' in deps
assert 'layout.html' in deps
assert 'index.html' in deps
assert 'index.html' in deps

+ 4
- 5
hyde/tests/ext/test_drafts.py View File

@@ -23,12 +23,13 @@ A draft post.

"""


class TestDrafts(object):

def setUp(self):
TEST_SITE.make()
TEST_SITE.parent.child_folder(
'sites/test_jinja').copy_contents_to(TEST_SITE)
'sites/test_jinja').copy_contents_to(TEST_SITE)
draft = TEST_SITE.child_file('content/blog/2013/may/draft-post.html')
draft.parent.make()
draft.write(DRAFT_POST)
@@ -50,7 +51,7 @@ class TestDrafts(object):
gen = Generator(s)
gen.generate_all()
assert not s.config.deploy_root_path.child_file(
'blog/2013/may/draft-post.html').exists
'blog/2013/may/draft-post.html').exists

def test_drafts_are_published_in_development(self):
s = Site(TEST_SITE)
@@ -66,6 +67,4 @@ class TestDrafts(object):
gen = Generator(s)
gen.generate_all()
assert s.config.deploy_root_path.child_file(
'blog/2013/may/draft-post.html').exists


'blog/2013/may/draft-post.html').exists

+ 3
- 4
hyde/tests/ext/test_flattener.py View File

@@ -19,7 +19,7 @@ class TestFlattner(object):
def setUp(self):
TEST_SITE.make()
TEST_SITE.parent.child_folder(
'sites/test_jinja').copy_contents_to(TEST_SITE)
'sites/test_jinja').copy_contents_to(TEST_SITE)

def tearDown(self):
TEST_SITE.delete()
@@ -42,7 +42,8 @@ class TestFlattner(object):
gen.generate_all()

assert not s.config.deploy_root_path.child_folder('blog').exists
assert File(s.config.deploy_root_path.child('merry-christmas.html')).exists
assert File(
s.config.deploy_root_path.child('merry-christmas.html')).exists

def test_flattener_fixes_nodes(self):
s = Site(TEST_SITE)
@@ -64,5 +65,3 @@ class TestFlattner(object):

assert blog_node
assert blog_node.url == '/'



+ 29
- 19
hyde/tests/ext/test_grouper.py View File

@@ -21,7 +21,7 @@ class TestGrouperSingleLevel(object):
def setUp(self):
TEST_SITE.make()
TEST_SITE.parent.child_folder(
'sites/test_grouper').copy_contents_to(TEST_SITE)
'sites/test_grouper').copy_contents_to(TEST_SITE)

self.s = Site(TEST_SITE)
cfg = """
@@ -55,7 +55,8 @@ class TestGrouperSingleLevel(object):
SorterPlugin(self.s).begin_site()
GrouperPlugin(self.s).begin_site()

self.all = ['installation.html', 'overview.html', 'templating.html', 'plugins.html', 'tags.html']
self.all = ['installation.html', 'overview.html',
'templating.html', 'plugins.html', 'tags.html']
self.start = ['installation.html', 'overview.html', 'templating.html']
self.plugins = ['plugins.html', 'tags.html']
self.section = self.all
@@ -72,7 +73,8 @@ class TestGrouperSingleLevel(object):

def test_site_grouper_walk_groups(self):

groups = dict([(g.name, g) for g in self.s.grouper['section'].walk_groups()])
groups = dict([(g.name, g)
for g in self.s.grouper['section'].walk_groups()])
assert len(groups) == 3
assert 'section' in groups
assert 'start' in groups
@@ -81,7 +83,8 @@ class TestGrouperSingleLevel(object):
def test_walk_section_groups(self):

assert hasattr(self.s.content, 'walk_section_groups')
groups = dict([(grouper.group.name, grouper) for grouper in self.s.content.walk_section_groups()])
groups = dict([(grouper.group.name, grouper)
for grouper in self.s.content.walk_section_groups()])
assert len(groups) == 3
assert 'section' in groups
assert 'start' in groups
@@ -93,15 +96,16 @@ class TestGrouperSingleLevel(object):
def test_walk_start_groups(self):

assert hasattr(self.s.content, 'walk_start_groups')
groups = dict([(g.name, g) for g, resources in self.s.content.walk_start_groups()])
groups = dict([(g.name, g)
for g, resources in self.s.content.walk_start_groups()])
assert len(groups) == 1
assert 'start' in groups


def test_walk_plugins_groups(self):

assert hasattr(self.s.content, 'walk_plugins_groups')
groups = dict([(g.name, g) for g, resources in self.s.content.walk_plugins_groups()])
groups = dict([(g.name, g) for g, resources in
self.s.content.walk_plugins_groups()])
assert len(groups) == 1
assert 'plugins' in groups

@@ -109,22 +113,24 @@ class TestGrouperSingleLevel(object):

assert hasattr(self.s.content, 'walk_resources_grouped_by_section')

resources = [resource.name for resource in self.s.content.walk_resources_grouped_by_section()]
resources = [resource.name for resource in
self.s.content.walk_resources_grouped_by_section()]
assert resources == self.all


def test_walk_start_resources(self):

assert hasattr(self.s.content, 'walk_resources_grouped_by_start')

start_resources = [resource.name for resource in self.s.content.walk_resources_grouped_by_start()]
start_resources = [resource.name for resource in
self.s.content.walk_resources_grouped_by_start()]
assert start_resources == self.start

def test_walk_plugins_resources(self):

assert hasattr(self.s.content, 'walk_resources_grouped_by_plugins')

plugin_resources = [resource.name for resource in self.s.content.walk_resources_grouped_by_plugins()]
plugin_resources = [resource.name for resource in
self.s.content.walk_resources_grouped_by_plugins()]
assert plugin_resources == self.plugins

def test_resource_group(self):
@@ -134,7 +140,8 @@ class TestGrouperSingleLevel(object):
for name, group in groups.items():
pages = getattr(self, name)
for page in pages:
res = self.s.content.resource_from_relative_path('blog/' + page)
res = self.s.content.resource_from_relative_path(
'blog/' + page)
assert hasattr(res, 'section_group')
res_group = getattr(res, 'section_group')
assert res_group == group
@@ -146,7 +153,8 @@ class TestGrouperSingleLevel(object):
for name, group in groups.items():
pages = getattr(self, name)
for page in pages:
res = self.s.content.resource_from_relative_path('blog/' + page)
res = self.s.content.resource_from_relative_path(
'blog/' + page)
res_groups = getattr(res, 'walk_%s_groups' % name)()
assert group in res_groups

@@ -154,7 +162,8 @@ class TestGrouperSingleLevel(object):

resources = []
for page in self.all:
resources.append(self.s.content.resource_from_relative_path('blog/' + page))
resources.append(
self.s.content.resource_from_relative_path('blog/' + page))

index = 0
for res in resources:
@@ -174,7 +183,7 @@ class TestGrouperSingleLevel(object):

def test_nav_with_grouper(self):

text ="""
text = """
{% for group, resources in site.content.walk_section_groups() %}
<ul>
<li>
@@ -225,7 +234,7 @@ class TestGrouperSingleLevel(object):
gen = Generator(self.s)
gen.load_site_if_needed()
gen.load_template_if_needed()
out = gen.template.render(text, {'site':self.s})
out = gen.template.render(text, {'site': self.s})
assert_html_equals(out, expected)

def test_nav_with_grouper_sorted(self):
@@ -264,7 +273,7 @@ class TestGrouperSingleLevel(object):
SorterPlugin(self.s).begin_site()
GrouperPlugin(self.s).begin_site()

text ="""
text = """
{% set sorted = site.grouper['section'].groups|sort(attribute='name') %}
{% for group in sorted %}
<ul>
@@ -314,9 +323,10 @@ class TestGrouperSingleLevel(object):


"""
self.s.config.grouper.section.groups.append(Expando({"name": "awesome", "description": "Aweesoome"}));
self.s.config.grouper.section.groups.append(
Expando({"name": "awesome", "description": "Aweesoome"}))
gen = Generator(self.s)
gen.load_site_if_needed()
gen.load_template_if_needed()
out = gen.template.render(text, {'site':self.s})
out = gen.template.render(text, {'site': self.s})
assert_html_equals(out, expected)

+ 0
- 286
hyde/tests/ext/test_images.py View File

@@ -1,286 +0,0 @@
# -*- coding: utf-8 -*-
"""
Use nose
`$ pip install nose`
`$ nosetests`

Requires PIL
"""
from hyde.generator import Generator
from hyde.site import Site
from hyde.ext.plugins.images import thumb_scale_size


from fswrap import File
import yaml

TEST_SITE = File(__file__).parent.parent.child_folder('_test')
IMAGE_SOURCE = File(__file__).parent.child_folder('images')

PORTRAIT_IMAGE = "portrait.jpg"
PORTRAIT_SIZE = (90, 120)
LANDSCAPE_IMAGE = "landscape.jpg"
LANDSCAPE_SIZE = (120, 90)

IMAGES = [PORTRAIT_IMAGE, LANDSCAPE_IMAGE]
SIZES = [PORTRAIT_SIZE, LANDSCAPE_SIZE]

# PIL requirement
try:
from PIL import Image
except ImportError:
# No pillow
import Image

class TestImageSizer(object):

def setUp(self):
TEST_SITE.make()
TEST_SITE.parent.child_folder(
'sites/test_jinja').copy_contents_to(TEST_SITE)
IMAGES = TEST_SITE.child_folder('content/media/img')
IMAGES.make()
IMAGE_SOURCE.copy_contents_to(IMAGES)
self.site = Site(TEST_SITE)

def tearDown(self):
TEST_SITE.delete()

def _generic_test_image(self, text):
self.site.config.mode = "production"
self.site.config.plugins = ['hyde.ext.plugins.images.ImageSizerPlugin']
tlink = File(self.site.content.source_folder.child('timg.html'))
tlink.write(text)
gen = Generator(self.site)
gen.generate_all()
f = File(self.site.config.deploy_root_path.child(tlink.name))
assert f.exists
html = f.read_all()
assert html
return html

def test_size_image(self):
text = u"""
<img src="/media/img/%s">
""" % PORTRAIT_IMAGE
html = self._generic_test_image(text)
assert ' width="%d"' % PORTRAIT_SIZE[0] in html
assert ' height="%d"' % PORTRAIT_SIZE[1] in html

def test_size_image_relative(self):
text = u"""
<img src="media/img/%s">
""" % PORTRAIT_IMAGE
html = self._generic_test_image(text)
assert ' width="%d"' % PORTRAIT_SIZE[0] in html
assert ' height="%d"' % PORTRAIT_SIZE[1] in html

def test_size_image_no_resize(self):
text = u"""
<img src="/media/img/%s" width="2000" height="150">
""" % PORTRAIT_IMAGE
html = self._generic_test_image(text)
assert ' width="%d"' % PORTRAIT_SIZE[0] not in html
assert ' height="%d"' % PORTRAIT_SIZE[1] not in html

def test_size_image_size_proportional(self):
text = u"""
<img src="/media/img/%s" width="%d">
""" % (PORTRAIT_IMAGE, PORTRAIT_SIZE[0]*2)
html = self._generic_test_image(text)
assert ' width="%d"' % (PORTRAIT_SIZE[0]*2) in html
assert ' height="%d"' % (PORTRAIT_SIZE[1]*2) in html

def test_size_image_not_exists(self):
text = u"""
<img src="/media/img/hyde-logo-no.png">
"""
self._generic_test_image(text)

def test_size_image_multiline(self):
text = u"""
<img src="/media/img/%s">
""" % PORTRAIT_IMAGE
html = self._generic_test_image(text)
assert ' width="%d"' % PORTRAIT_SIZE[0] in html
assert ' height="%d"' % PORTRAIT_SIZE[1] in html

def test_size_multiple_images(self):
text = u"""
<img src="/media/img/%s">
<img src="/media/img/%s">Hello <img src="/media/img/%s">
<img src="/media/img/%s">Bye
""" % ((PORTRAIT_IMAGE,)*4)
html = self._generic_test_image(text)
assert ' width="%d"' % PORTRAIT_SIZE[0] in html
assert ' height="%d"' % PORTRAIT_SIZE[1] in html
assert 'Hello ' in html
assert 'Bye' in html
assert len([f for f in html.split("<img")
if ' width=' in f]) == 4
assert len([f for f in html.split("<img")
if ' height=' in f]) == 4

def test_size_malformed1(self):
text = u"""
<img src="/media/img/%s>
""" % PORTRAIT_IMAGE
html = self._generic_test_image(text)
assert ' width="%d"' % PORTRAIT_SIZE[0] in html
assert ' height="%d"' % PORTRAIT_SIZE[1] in html

def test_size_malformed2(self):
text = u"""
<img src="/media/img/%s alt="hello">
""" % PORTRAIT_IMAGE
html = self._generic_test_image(text)
assert ' width="%d"' % PORTRAIT_SIZE[0] in html
assert ' height="%d"' % PORTRAIT_SIZE[1] in html

def test_outside_media_url(self):
self.site.config.media_url = "http://media.example.com/"
text = u"""
<img src="http://media.example.com/img/%s" alt="hello">
""" % PORTRAIT_IMAGE
html = self._generic_test_image(text)
assert ' width="%d"' % PORTRAIT_SIZE[0] in html
assert ' height="%d"' % PORTRAIT_SIZE[1] in html

class TestImageThumbSize(object):

def test_width_only(self):
ow, oh = 100, 200
nw, nh = thumb_scale_size(ow, oh, 50, None)
assert nw == 50
assert nh == 100

def test_width_only_nonintegral(self):
ow, oh = 100, 205
nw, nh = thumb_scale_size(ow, oh, 50, None)
assert nw == 50
assert nh == 103

def test_height_only(self):
ow, oh = 100, 200
nw, nh = thumb_scale_size(ow, oh, None, 100)
assert nw == 50
assert nh == 100

def test_height_only_nonintegral(self):
ow, oh = 105, 200
nw, nh = thumb_scale_size(ow, oh, None, 100)
assert nw == 53
assert nh == 100

def test_height_and_width_portrait(self):
ow, oh = 100, 200
nw, nh = thumb_scale_size(ow, oh, 50, 50)
assert nw == 50
assert nh == 100

def test_height_and_width_landscape(self):
ow, oh = 200, 100
nw, nh = thumb_scale_size(ow, oh, 50, 50)
assert nw == 100
assert nh == 50

class TestImageThumbnails(object):
# TODO: add tests for cropping? (not easy currently)

def setUp(self):
TEST_SITE.make()
TEST_SITE.parent.child_folder(
'sites/test_jinja').copy_contents_to(TEST_SITE)
IMAGES = TEST_SITE.child_folder('content/media/img')
IMAGES.make()
IMAGE_SOURCE.copy_contents_to(IMAGES)
self.image_folder = IMAGES
self.site = Site(TEST_SITE)

def tearDown(self):
TEST_SITE.delete()

def _generate_site_with_meta(self, meta):
self.site.config.mode = "production"
self.site.config.plugins = ['hyde.ext.plugins.meta.MetaPlugin', 'hyde.ext.plugins.images.ImageThumbnailsPlugin']

mlink = File(self.image_folder.child('meta.yaml'))
meta_text = yaml.dump(meta, default_flow_style=False)
mlink.write(meta_text)
gen = Generator(self.site)
gen.generate_all()

def _test_generic_thumbnails(self, meta):
self._generate_site_with_meta(meta)
thumb_meta = meta.get('thumbnails', [])
for th in thumb_meta:
prefix = th.get('prefix')
if prefix is None:
continue

for fn in [PORTRAIT_IMAGE, LANDSCAPE_IMAGE]:
f = File(self._deployed_image(prefix, fn))
assert f.exists

def _deployed_image(self, prefix, filename):
return self.site.config.deploy_root_path.child('media/img/%s%s'%(prefix,filename))

def test_width(self):
prefix='thumb_'
meta = dict(thumbnails=[dict(width=50, prefix=prefix, include=['*.jpg'])])
self._test_generic_thumbnails(meta)
for fn in IMAGES:
im = Image.open(self._deployed_image(prefix, fn))
assert im.size[0] == 50

def test_height(self):
prefix='thumb_'
meta = dict(thumbnails=[dict(height=50, prefix=prefix, include=['*.jpg'])])
self._test_generic_thumbnails(meta)
for fn in IMAGES:
im = Image.open(self._deployed_image(prefix, fn))
assert im.size[1] == 50

def test_width_and_height(self):
prefix='thumb_'
meta = dict(thumbnails=[dict(width=50, height=50, prefix=prefix, include=['*.jpg'])])
self._test_generic_thumbnails(meta)
for fn in IMAGES:
im = Image.open(self._deployed_image(prefix, fn))
assert im.size[0] == 50
assert im.size[1] == 50

def test_larger(self):
prefix='thumb_'
meta = dict(thumbnails=[dict(larger=50, prefix=prefix, include=['*.jpg'])])
self._test_generic_thumbnails(meta)

im = Image.open(self._deployed_image(prefix, PORTRAIT_IMAGE))
assert im.size[1] == 50

im = Image.open(self._deployed_image(prefix, LANDSCAPE_IMAGE))
assert im.size[0] == 50

def test_smaller(self):
prefix='thumb_'
meta = dict(thumbnails=[dict(smaller=50, prefix=prefix, include=['*.jpg'])])
self._test_generic_thumbnails(meta)

im = Image.open(self._deployed_image(prefix, PORTRAIT_IMAGE))
assert im.size[0] == 50

im = Image.open(self._deployed_image(prefix, LANDSCAPE_IMAGE))
assert im.size[1] == 50

def test_larger_and_smaller(self):
prefix='thumb_'
meta = dict(thumbnails=[dict(larger=100, smaller=50, prefix=prefix, include=['*.jpg'])])
self._test_generic_thumbnails(meta)

im = Image.open(self._deployed_image(prefix, PORTRAIT_IMAGE))
assert im.size[0] == 50
assert im.size[1] == 100

im = Image.open(self._deployed_image(prefix, LANDSCAPE_IMAGE))
assert im.size[0] == 100
assert im.size[1] == 50

+ 3
- 3
hyde/tests/ext/test_less.py View File

@@ -18,11 +18,10 @@ class TestLess(object):
def setUp(self):
TEST_SITE.make()
TEST_SITE.parent.child_folder(
'sites/test_jinja').copy_contents_to(TEST_SITE)
'sites/test_jinja').copy_contents_to(TEST_SITE)
LESS_SOURCE.copy_contents_to(TEST_SITE.child('content/media/css'))
File(TEST_SITE.child('content/media/css/site.css')).delete()


def tearDown(self):
TEST_SITE.delete()

@@ -30,7 +29,8 @@ class TestLess(object):
s = Site(TEST_SITE)
s.config.plugins = ['hyde.ext.plugins.css.LessCSSPlugin']
source = TEST_SITE.child('content/media/css/site.less')
target = File(Folder(s.config.deploy_root_path).child('media/css/site.css'))
target = File(
Folder(s.config.deploy_root_path).child('media/css/site.css'))
gen = Generator(s)
gen.generate_resource_at_path(source)



+ 3
- 3
hyde/tests/ext/test_markings.py View File

@@ -12,6 +12,7 @@ from pyquery import PyQuery

TEST_SITE = File(__file__).parent.parent.child_folder('_test')


def assert_valid_conversion(html):
assert html
q = PyQuery(html)
@@ -25,18 +26,17 @@ def assert_valid_conversion(html):
assert '.' not in q.text()
assert '/' not in q.text()


class TestMarkings(object):

def setUp(self):
TEST_SITE.make()
TEST_SITE.parent.child_folder(
'sites/test_jinja').copy_contents_to(TEST_SITE)
'sites/test_jinja').copy_contents_to(TEST_SITE)

def tearDown(self):
TEST_SITE.delete()



def test_mark(self):
text = u"""
===


+ 10
- 10
hyde/tests/ext/test_meta.py View File

@@ -20,7 +20,7 @@ class TestMeta(object):
def setUp(self):
TEST_SITE.make()
TEST_SITE.parent.child_folder(
'sites/test_jinja').copy_contents_to(TEST_SITE)
'sites/test_jinja').copy_contents_to(TEST_SITE)

def tearDown(self):
TEST_SITE.delete()
@@ -76,8 +76,8 @@ Heading 2

def test_can_load_front_matter(self):
d = {'title': 'A nice title',
'author': 'Lakshmi Vyas',
'twitter': 'lakshmivyas'}
'author': 'Lakshmi Vyas',
'twitter': 'lakshmivyas'}
text = """
---
title: %(title)s
@@ -118,8 +118,8 @@ twitter: %(twitter)s

def test_can_load_from_node_meta(self):
d = {'title': 'A nice title',
'author': 'Lakshmi Vyas',
'twitter': 'lakshmivyas'}
'author': 'Lakshmi Vyas',
'twitter': 'lakshmivyas'}
text = """
===
title: Even nicer title
@@ -161,7 +161,7 @@ title: Even nicer title

def test_can_load_from_site_meta(self):
d = {'title': 'A nice title',
'author': 'Lakshmi Vyas'}
'author': 'Lakshmi Vyas'}
text = """
---
title: Even nicer title
@@ -205,15 +205,14 @@ title: Even nicer title
assert v in q("span." + k).text()
assert q("span.title").text() == "Even nicer title"


def test_multiple_levels(self):

page_d = {'title': 'An even nicer title'}

blog_d = {'author': 'Laks'}

content_d = {'title': 'A nice title',
'author': 'Lakshmi Vyas'}
content_d = {'title': 'A nice title',
'author': 'Lakshmi Vyas'}

site_d = {'author': 'Lakshmi',
'twitter': 'lakshmivyas',
@@ -256,7 +255,8 @@ title: %(title)s
for k, v in expected.items():
assert hasattr(res.meta, k)
assert getattr(res.meta, k) == v
target = File(Folder(s.config.deploy_root_path).child('blog/about2.html'))
target = File(
Folder(s.config.deploy_root_path).child('blog/about2.html'))
text = target.read_all()
q = PyQuery(text)
for k, v in expected.items():


+ 5
- 4
hyde/tests/ext/test_optipng.py View File

@@ -19,12 +19,11 @@ class TestOptipng(object):
def setUp(self):
TEST_SITE.make()
TEST_SITE.parent.child_folder(
'sites/test_jinja').copy_contents_to(TEST_SITE)
'sites/test_jinja').copy_contents_to(TEST_SITE)
IMAGES = TEST_SITE.child_folder('content/media/images')
IMAGES.make()
OPTIPNG_SOURCE.copy_contents_to(IMAGES)


def tearDown(self):
TEST_SITE.delete()

@@ -33,8 +32,10 @@ class TestOptipng(object):
s.config.mode = "production"
s.config.plugins = ['hyde.ext.plugins.images.OptiPNGPlugin']
s.config.optipng = Expando(dict(args=dict(quiet="")))
source =File(TEST_SITE.child('content/media/images/hyde-lt-b.png'))
target = File(Folder(s.config.deploy_root_path).child('media/images/hyde-lt-b.png'))
source = File(TEST_SITE.child('content/media/images/hyde-lt-b.png'))
target = File(
Folder(s.config.deploy_root_path).child(
'media/images/hyde-lt-b.png'))
gen = Generator(s)
gen.generate_resource_at_path(source)
assert target.exists


+ 3
- 10
hyde/tests/ext/test_paginator.py View File

@@ -13,12 +13,13 @@ from fswrap import File

TEST_SITE = File(__file__).parent.parent.child_folder('_test')


class TestPaginator(object):

def setUp(self):
TEST_SITE.make()
TEST_SITE.parent.child_folder(
'sites/test_paginator').copy_contents_to(TEST_SITE)
'sites/test_paginator').copy_contents_to(TEST_SITE)
self.s = Site(TEST_SITE)
self.deploy = TEST_SITE.child_folder('deploy')

@@ -27,14 +28,12 @@ class TestPaginator(object):
self.gen.load_template_if_needed()
self.gen.generate_all()


def tearDown(self):
TEST_SITE.delete()


def test_pages_of_one(self):
pages = ['pages_of_one.txt', 'page2/pages_of_one.txt',
'page3/pages_of_one.txt', 'page4/pages_of_one.txt']
'page3/pages_of_one.txt', 'page4/pages_of_one.txt']
files = [File(self.deploy.child(p)) for p in pages]
for f in files:
assert f.exists
@@ -42,7 +41,6 @@ class TestPaginator(object):
page5 = File(self.deploy.child('page5/pages_of_one.txt'))
assert not page5.exists


def test_pages_of_one_content(self):
expected_page1_content = dedent('''\
Another Sad Post
@@ -77,7 +75,6 @@ class TestPaginator(object):
content = File(page4).read_all()
assert expected_page4_content == content


def test_pages_of_ten(self):
page1 = self.deploy.child('pages_of_ten.txt')
page2 = self.deploy.child('page2/pages_of_ten.txt')
@@ -85,7 +82,6 @@ class TestPaginator(object):
assert File(page1).exists
assert not File(page2).exists


def test_pages_of_ten_depends(self):
depends = self.gen.deps['pages_of_ten.txt']

@@ -96,7 +92,6 @@ class TestPaginator(object):
assert 'blog/angry-post.html' in depends
assert 'blog/happy-post.html' in depends


def test_pages_of_ten_content(self):
expected_content = dedent('''\
Another Sad Post
@@ -109,7 +104,6 @@ class TestPaginator(object):
content = File(page).read_all()
assert expected_content == content


def test_pages_of_one_depends(self):
depends = self.gen.deps['pages_of_one.txt']

@@ -120,7 +114,6 @@ class TestPaginator(object):
assert 'blog/angry-post.html' in depends
assert 'blog/happy-post.html' in depends


def test_custom_file_pattern(self):
page1 = self.deploy.child('custom_file_pattern.txt')
page2 = self.deploy.child('custom_file_pattern-2.txt')


+ 6
- 2
hyde/tests/ext/test_requirejs.py View File

@@ -12,10 +12,13 @@ from fswrap import File, Folder
RJS_SOURCE = File(__file__).parent.child_folder('requirejs')
TEST_SITE = File(__file__).parent.parent.child_folder('_test')


class TestRequireJS(object):

def setUp(self):
TEST_SITE.make()
TEST_SITE.parent.child_folder('sites/test_jinja').copy_contents_to(TEST_SITE)
TEST_SITE.parent.child_folder(
'sites/test_jinja').copy_contents_to(TEST_SITE)
RJS_SOURCE.copy_contents_to(TEST_SITE.child('content/media/js'))
File(TEST_SITE.child('content/media/js/app.js')).delete()

@@ -26,7 +29,8 @@ class TestRequireJS(object):
s = Site(TEST_SITE)
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'))
target = File(
Folder(s.config.deploy_root_path).child('media/js/app.js'))
gen = Generator(s)
gen.generate_resource_at_path(source)



+ 3
- 5
hyde/tests/ext/test_scss.py View File

@@ -19,21 +19,20 @@ class TestSassyCSS(object):
def setUp(self):
TEST_SITE.make()
TEST_SITE.parent.child_folder(
'sites/test_jinja').copy_contents_to(TEST_SITE)
'sites/test_jinja').copy_contents_to(TEST_SITE)
SCSS_SOURCE.copy_contents_to(TEST_SITE.child('content/media/css'))
File(TEST_SITE.child('content/media/css/site.css')).delete()


def tearDown(self):
TEST_SITE.delete()


def test_scss(self):
s = Site(TEST_SITE)
s.config.mode = 'prod'
s.config.plugins = ['hyde.ext.plugins.css.SassyCSSPlugin']
source = TEST_SITE.child('content/media/css/site.scss')
target = File(Folder(s.config.deploy_root_path).child('media/css/site.css'))
target = File(
Folder(s.config.deploy_root_path).child('media/css/site.css'))
gen = Generator(s)
gen.generate_resource_at_path(source)

@@ -41,4 +40,3 @@ class TestSassyCSS(object):
text = target.read_all()
expected_text = File(SCSS_SOURCE.child('expected-site.css')).read_all()
assert_no_diff(expected_text, text)


+ 0
- 366
hyde/tests/ext/test_sorter.py View File

@@ -1,366 +0,0 @@
# -*- coding: utf-8 -*-
"""
Use nose
`$ pip install nose`
`$ nosetests`
"""
from hyde.ext.plugins.meta import MetaPlugin, SorterPlugin
from hyde.generator import Generator
from hyde.site import Site
from hyde.model import Config, Expando

from fswrap import File, Folder
import yaml


TEST_SITE = File(__file__).parent.parent.child_folder('_test')


class TestSorter(object):

def setUp(self):
TEST_SITE.make()
TEST_SITE.parent.child_folder(
'sites/test_jinja').copy_contents_to(TEST_SITE)

def tearDown(self):
TEST_SITE.delete()

def test_walk_resources_sorted(self):
s = Site(TEST_SITE)
s.load()
s.config.plugins = ['hyde.ext.meta.SorterPlugin']
s.config.sorter = Expando(dict(kind=dict(attr=['source_file.kind', 'name'])))

SorterPlugin(s).begin_site()

assert hasattr(s.content, 'walk_resources_sorted_by_kind')
expected = ["404.html",
"about.html",
"apple-touch-icon.png",
"merry-christmas.html",
"crossdomain.xml",
"favicon.ico",
"robots.txt",
"site.css"
]

pages = [page.name for page in
s.content.walk_resources_sorted_by_kind()]

assert pages == sorted(expected, key=lambda f: (File(f).kind, f))

def test_walk_resources_sorted_reverse(self):
s = Site(TEST_SITE)
s.load()
s.config.plugins = ['hyde.ext.meta.SorterPlugin']
s.config.sorter = Expando(dict(kind=dict(attr=['source_file.kind', 'name'], reverse=True)))

SorterPlugin(s).begin_site()

assert hasattr(s.content, 'walk_resources_sorted_by_kind')
expected = ["404.html",
"about.html",
"apple-touch-icon.png",
"merry-christmas.html",
"crossdomain.xml",
"favicon.ico",
"robots.txt",
"site.css"
]

pages = [page.name for page in
s.content.walk_resources_sorted_by_kind()]


assert pages == sorted(expected, key=lambda f: (File(f).kind, f), reverse=True)

def test_walk_resources_sorted_with_filters(self):
s = Site(TEST_SITE)
cfg = """
plugins:
- hyde.ext.meta.SorterPlugin
sorter:
kind2:
filters:
source_file.kind: html
"""
s.config = Config(TEST_SITE, config_dict=yaml.load(cfg))
s.load()
SorterPlugin(s).begin_site()

assert hasattr(s.content, 'walk_resources_sorted_by_kind2')
expected = ["404.html",
"about.html",
"merry-christmas.html"
]

pages = [page.name for page in s.content.walk_resources_sorted_by_kind2()]

assert pages == sorted(expected)

def test_walk_resources_sorted_with_multiple_attributes(self):
s = Site(TEST_SITE)
cfg = """
plugins:
- hyde.ext.meta.SorterPlugin
sorter:
multi:
attr:
- source_file.kind
- node.name
- name

"""
s.config = Config(TEST_SITE, config_dict=yaml.load(cfg))
s.load()
SorterPlugin(s).begin_site()

assert hasattr(s.content, 'walk_resources_sorted_by_multi')
expected = ["content/404.html",
"content/about.html",
"content/apple-touch-icon.png",
"content/blog/2010/december/merry-christmas.html",
"content/crossdomain.xml",
"content/favicon.ico",
"content/robots.txt",
"content/site.css"
]

pages = [page.name for page in s.content.walk_resources_sorted_by_multi()]

expected_sorted = [File(page).name
for page in
sorted(expected,
key=lambda p: tuple(
[File(p).kind,
File(p).parent.name, p]))]
assert pages == expected_sorted

def test_walk_resources_sorted_no_default_is_processable(self):
s = Site(TEST_SITE)
cfg = """
plugins:
- hyde.ext.meta.SorterPlugin
sorter:
kind2:
filters:
source_file.kind: html
attr:
- name
"""
s.config = Config(TEST_SITE, config_dict=yaml.load(cfg))
s.load()
p_404 = s.content.resource_from_relative_path('404.html')
p_404.is_processable = False
SorterPlugin(s).begin_site()

assert hasattr(s.content, 'walk_resources_sorted_by_kind2')
expected = ["404.html", "about.html", "merry-christmas.html"]

pages = [page.name for page in s.content.walk_resources_sorted_by_kind2()]

assert pages == sorted(expected)

def test_prev_next(self):
s = Site(TEST_SITE)
cfg = """
plugins:
- hyde.ext.meta.SorterPlugin
sorter:
kind2:
filters:
source_file.kind: html
attr:
- name
"""
s.config = Config(TEST_SITE, config_dict=yaml.load(cfg))
s.load()
SorterPlugin(s).begin_site()

p_404 = s.content.resource_from_relative_path('404.html')
p_about = s.content.resource_from_relative_path('about.html')
p_mc = s.content.resource_from_relative_path(
'blog/2010/december/merry-christmas.html')

assert hasattr(p_404, 'prev_by_kind2')
assert not p_404.prev_by_kind2
assert hasattr(p_404, 'next_by_kind2')
assert p_404.next_by_kind2 == p_about

assert hasattr(p_about, 'prev_by_kind2')
assert p_about.prev_by_kind2 == p_404
assert hasattr(p_about, 'next_by_kind2')
assert p_about.next_by_kind2 == p_mc

assert hasattr(p_mc, 'prev_by_kind2')
assert p_mc.prev_by_kind2 == p_about
assert hasattr(p_mc, 'next_by_kind2')
assert not p_mc.next_by_kind2

def test_prev_next_looped(self):
s = Site(TEST_SITE)
cfg = """
plugins:
- hyde.ext.meta.SorterPlugin
sorter:
kind2:
circular: true
filters:
source_file.kind: html
attr:
- name
"""
s.config = Config(TEST_SITE, config_dict=yaml.load(cfg))
s.load()
SorterPlugin(s).begin_site()

p_404 = s.content.resource_from_relative_path('404.html')
p_about = s.content.resource_from_relative_path('about.html')
p_mc = s.content.resource_from_relative_path(
'blog/2010/december/merry-christmas.html')

assert hasattr(p_404, 'prev_by_kind2')
assert p_404.prev_by_kind2 == p_mc
assert hasattr(p_404, 'next_by_kind2')
assert p_404.next_by_kind2 == p_about

assert hasattr(p_about, 'prev_by_kind2')
assert p_about.prev_by_kind2 == p_404
assert hasattr(p_about, 'next_by_kind2')
assert p_about.next_by_kind2 == p_mc

assert hasattr(p_mc, 'prev_by_kind2')
assert p_mc.prev_by_kind2 == p_about
assert hasattr(p_mc, 'next_by_kind2')
assert p_mc.next_by_kind2 == p_404

def test_prev_next_reversed(self):
s = Site(TEST_SITE)
cfg = """
plugins:
- hyde.ext.meta.SorterPlugin
sorter:
folder_name:
attr:
- node.name
reverse: True
filters:
source_file.kind: html
"""
s.config = Config(TEST_SITE, config_dict=yaml.load(cfg))
s.load()
SorterPlugin(s).begin_site()

p_404 = s.content.resource_from_relative_path('404.html')
p_about = s.content.resource_from_relative_path('about.html')
p_mc = s.content.resource_from_relative_path(
'blog/2010/december/merry-christmas.html')

assert hasattr(p_mc, 'prev_by_folder_name')
assert not p_mc.prev_by_folder_name
assert hasattr(p_mc, 'next_by_folder_name')
assert p_mc.next_by_folder_name == p_404

assert hasattr(p_404, 'prev_by_folder_name')
assert p_404.prev_by_folder_name == p_mc
assert hasattr(p_404, 'next_by_folder_name')
assert p_404.next_by_folder_name == p_about

assert hasattr(p_about, 'prev_by_folder_name')
assert p_about.prev_by_folder_name == p_404
assert hasattr(p_about, 'next_by_folder_name')
assert not p_about.next_by_folder_name

def test_walk_resources_sorted_using_generator(self):
s = Site(TEST_SITE)
cfg = """
meta:
time: !!timestamp 2010-10-23
title: NahNahNah
plugins:
- hyde.ext.plugins.meta.MetaPlugin
- hyde.ext.plugins.meta.SorterPlugin
sorter:
time:
attr: meta.time
filters:
source_file.kind: html
"""
s.config = Config(TEST_SITE, config_dict=yaml.load(cfg))
text = """
---
time: !!timestamp 2010-12-31
title: YayYayYay
---
{% extends "base.html" %}

{% block main %}
{% set latest = site.content.walk_resources_sorted_by_time()|reverse|first %}
<span class="latest">{{ latest.meta.title }}</span>
{% endblock %}
"""

about2 = File(TEST_SITE.child('content/about2.html'))
about2.write(text)
gen = Generator(s)
gen.generate_all()

from pyquery import PyQuery
target = File(Folder(s.config.deploy_root_path).child('about2.html'))
text = target.read_all()
q = PyQuery(text)

assert q('span.latest').text() == 'YayYayYay'

class TestSorterMeta(object):

def setUp(self):
TEST_SITE.make()
TEST_SITE.parent.child_folder(
'sites/test_sorter').copy_contents_to(TEST_SITE)

def tearDown(self):
TEST_SITE.delete()

def test_attribute_checker_no_meta(self):
s = Site(TEST_SITE)
s.load()
from hyde.ext.plugins.meta import attributes_checker
for r in s.content.walk_resources():
assert not attributes_checker(r, ['meta.index'])

def test_attribute_checker_with_meta(self):
s = Site(TEST_SITE)
s.load()
MetaPlugin(s).begin_site()
from hyde.ext.plugins.meta import attributes_checker
have_index = ["angry-post.html",
"another-sad-post.html",
"happy-post.html"]
for r in s.content.walk_resources():
expected = r.name in have_index
assert attributes_checker(r, ['meta.index']) == expected


def test_walk_resources_sorted_by_index(self):
s = Site(TEST_SITE)
s.load()
config = {
"index": {
"attr": ['meta.index', 'name']
}
}
s.config.sorter = Expando(config)
MetaPlugin(s).begin_site()
SorterPlugin(s).begin_site()

assert hasattr(s.content, 'walk_resources_sorted_by_index')
expected = ["angry-post.html",
"another-sad-post.html",
"happy-post.html"]

pages = [page.name for page in
s.content.walk_resources_sorted_by_index()]

assert pages == sorted(expected, key=lambda f: (File(f).kind, f))

+ 10
- 6
hyde/tests/ext/test_stylus.py View File

@@ -13,16 +13,16 @@ from fswrap import File, Folder
STYLUS_SOURCE = File(__file__).parent.child_folder('stylus')
TEST_SITE = File(__file__).parent.parent.child_folder('_test')


class TestStylus(object):

def setUp(self):
TEST_SITE.make()
TEST_SITE.parent.child_folder(
'sites/test_jinja').copy_contents_to(TEST_SITE)
'sites/test_jinja').copy_contents_to(TEST_SITE)
STYLUS_SOURCE.copy_contents_to(TEST_SITE.child('content/media/css'))
File(TEST_SITE.child('content/media/css/site.css')).delete()


def tearDown(self):
TEST_SITE.delete()

@@ -34,13 +34,15 @@ class TestStylus(object):
if File(path).exists:
s.config.stylus = Expando(dict(app=path))
source = TEST_SITE.child('content/media/css/site.styl')
target = File(Folder(s.config.deploy_root_path).child('media/css/site.css'))
target = File(
Folder(s.config.deploy_root_path).child('media/css/site.css'))
gen = Generator(s)
gen.generate_resource_at_path(source)

assert target.exists
text = target.read_all()
expected_text = File(STYLUS_SOURCE.child('expected-site.css')).read_all()
expected_text = File(
STYLUS_SOURCE.child('expected-site.css')).read_all()
assert text.strip() == expected_text.strip()

def test_can_compress_with_stylus(self):
@@ -52,11 +54,13 @@ class TestStylus(object):
if File(path).exists:
s.config.stylus = Expando(dict(app=path))
source = TEST_SITE.child('content/media/css/site.styl')
target = File(Folder(s.config.deploy_root_path).child('media/css/site.css'))
target = File(
Folder(s.config.deploy_root_path).child('media/css/site.css'))
gen = Generator(s)
gen.generate_resource_at_path(source)

assert target.exists
text = target.read_all()
expected_text = File(STYLUS_SOURCE.child('expected-site-compressed.css')).read_all()
expected_text = File(
STYLUS_SOURCE.child('expected-site-compressed.css')).read_all()
assert text.strip() == expected_text.strip()

+ 1
- 3
hyde/tests/ext/test_syntext.py View File

@@ -18,13 +18,11 @@ class TestSyntext(object):
def setUp(self):
TEST_SITE.make()
TEST_SITE.parent.child_folder(
'sites/test_jinja').copy_contents_to(TEST_SITE)
'sites/test_jinja').copy_contents_to(TEST_SITE)

def tearDown(self):
TEST_SITE.delete()



def test_syntext(self):
text = u"""
~~~~~~~~css~~~~~~~


+ 0
- 226
hyde/tests/ext/test_tagger.py View File

@@ -1,226 +0,0 @@
# -*- coding: utf-8 -*-
"""
Use nose
`$ pip install nose`
`$ nosetests`
"""
from hyde.generator import Generator
from hyde.site import Site

from fswrap import File

TEST_SITE = File(__file__).parent.parent.child_folder('_test')

class TestTagger(object):

def setUp(self):
TEST_SITE.make()
TEST_SITE.parent.child_folder(
'sites/test_tagger').copy_contents_to(TEST_SITE)
self.s = Site(TEST_SITE)
self.deploy = TEST_SITE.child_folder('deploy')


def tearDown(self):
TEST_SITE.delete()

def test_tagger_walker(self):
gen = Generator(self.s)
gen.load_site_if_needed()
gen.generate_all()

assert hasattr(self.s, 'tagger')
assert hasattr(self.s.tagger, 'tags')
assert self.s.tagger.tags
tags = self.s.tagger.tags.to_dict()

assert len(tags) == 6

for tag in ['sad', 'happy', 'angry', 'thoughts', 'events']:
assert tag in tags

sad_posts = [post.name for post in tags['sad']['resources']]
assert len(sad_posts) == 2
assert "sad-post.html" in sad_posts
assert "another-sad-post.html" in sad_posts
sad_posts == [post.name for post in
self.s.content.walk_resources_tagged_with('sad')]


happy_posts = [post.name for post in
self.s.content.walk_resources_tagged_with('happy')]
assert len(happy_posts) == 1
assert "happy-post.html" in happy_posts

angry_posts = [post.name for post in
self.s.content.walk_resources_tagged_with('angry')]
assert len(angry_posts) == 1
assert "angry-post.html" in angry_posts

sad_thought_posts = [post.name for post in
self.s.content.walk_resources_tagged_with('sad+thoughts')]
assert len(sad_thought_posts) == 1
assert "sad-post.html" in sad_thought_posts

def test_tagger_archives_generated(self):
gen = Generator(self.s)
gen.load_site_if_needed()
gen.load_template_if_needed()
gen.generate_all()
tags_folder = self.deploy.child_folder('blog/tags')

assert tags_folder.exists
tags = ['sad', 'happy', 'angry', 'thoughts', 'events']

archives = (File(tags_folder.child("%s.html" % tag)) for tag in tags)

for archive in archives:
assert archive.exists

from pyquery import PyQuery

q = PyQuery(File(tags_folder.child('sad.html')).read_all())
assert q

assert q('li').length == 2
assert q('li:nth-child(1) a').attr('href') == '/blog/another-sad-post.html'
assert q('li:nth-child(2) a').attr('href') == '/blog/sad-post.html'

q = PyQuery(File(tags_folder.child('happy.html')).read_all())
assert q

assert q('li').length == 1
assert q('li a:first-child').attr('href') == '/blog/happy-post.html'

q = PyQuery(File(tags_folder.child('angry.html')).read_all())
assert q

assert q('li').length == 1
assert q('li a:first-child').attr('href') == '/blog/angry-post.html'

q = PyQuery(File(tags_folder.child('thoughts.html')).read_all())
assert q

assert q('li').length == 3
assert q('li:nth-child(1) a').attr('href') == '/blog/happy-post.html'
assert q('li:nth-child(2) a').attr('href') == '/blog/angry-post.html'
assert q('li:nth-child(3) a').attr('href') == '/blog/sad-post.html'

q = PyQuery(File(tags_folder.child('events.html')).read_all())
assert q

assert q('li').length == 1
assert q('li a:first-child').attr('href') == '/blog/another-sad-post.html'

def test_tagger_metadata(self):
conf = {
"tagger":{
"tags": {
"sad" : {
"emotions": ["Dissappointed", "Lost"]
},
"angry": {
"emotions": ["Irritated", "Annoyed", "Disgusted"]
}
}
}
}
s = Site(TEST_SITE)
s.config.update(conf)
gen = Generator(s)
gen.load_site_if_needed()
gen.generate_all()

assert hasattr(s, 'tagger')
assert hasattr(s.tagger, 'tags')
assert s.tagger.tags
tags = s.tagger.tags
sad_tag = tags.sad
assert hasattr(sad_tag, "emotions")

assert sad_tag.emotions == s.config.tagger.tags.sad.emotions

assert hasattr(tags, "angry")
angry_tag = tags.angry
assert angry_tag
assert hasattr(angry_tag, "emotions")
assert angry_tag.emotions == s.config.tagger.tags.angry.emotions

for tagname in ['happy', 'thoughts', 'events']:
tag = getattr(tags, tagname)
assert tag
assert not hasattr(tag, "emotions")

def test_tagger_sorted(self):
conf = {
"tagger":{
"sorter": "time",
"archives": {
"blog": {
"template": "emotions.j2",
"source": "blog",
"target": "blog/tags",
"extension": "html",
"meta": {
"author": "Tagger Plugin"
}
}
},
"tags": {
"sad" : {
"emotions": ["Dissappointed", "Lost"]
},
"angry": {
"emotions": ["Irritated", "Annoyed", "Disgusted"]
}
}
}
}

text = """
<div id="author">{{ resource.meta.author }}</div>
<h1>Posts tagged: {{ tag }} in {{ node.name|title }}</h1>
Emotions:
<ul>
{% for emotion in tag.emotions %}
<li class="emotion">
{{ emotion }}
</li>
{% endfor %}
<ul>
{% for resource in walker() -%}
<li>
<a href="{{ content_url(resource.url) }}">{{ resource.meta.title }}</a>
</li>
{%- endfor %}
</ul>
"""
template = File(TEST_SITE.child('layout/emotions.j2'))
template.write(text)
s = Site(TEST_SITE)
s.config.update(conf)
gen = Generator(s)
gen.load_site_if_needed()
gen.generate_all()

tags_folder = self.deploy.child_folder('blog/tags')
assert tags_folder.exists
tags = ['sad', 'happy', 'angry', 'thoughts', 'events']
archives = dict((tag, File(tags_folder.child("%s.html" % tag))) for tag in tags)

for tag, archive in archives.items():
assert archive.exists

from pyquery import PyQuery

q = PyQuery(archives['sad'].read_all())
assert len(q("li.emotion")) == 2
assert q("#author").text() == "Tagger Plugin"

q = PyQuery(archives['angry'].read_all())
assert len(q("li.emotion")) == 3

for tag, archive in archives.items():
if tag not in ["sad", "angry"]:
q = PyQuery(archives[tag].read_all())
assert not len(q("li.emotion"))

+ 4
- 6
hyde/tests/ext/test_textlinks.py View File

@@ -17,13 +17,11 @@ class TestTextlinks(object):
def setUp(self):
TEST_SITE.make()
TEST_SITE.parent.child_folder(
'sites/test_jinja').copy_contents_to(TEST_SITE)
'sites/test_jinja').copy_contents_to(TEST_SITE)

def tearDown(self):
TEST_SITE.delete()



def test_textlinks(self):
d = {
'objects': 'template/variables',
@@ -34,8 +32,8 @@ class TestTextlinks(object):
{%% markdown %%}
[[!!img/hyde-logo.png]]
* [Rich object model][hyde objects] and
[overridable hierarchical metadata]([[ %(plugins)s ]]) thats available for use in
templates.
[overridable hierarchical metadata]([[ %(plugins)s ]]) thats available
for use in templates.
* Configurable [sorting][], filtering and grouping support.

[hyde objects]: [[ %(objects)s ]]
@@ -57,5 +55,5 @@ class TestTextlinks(object):
assert html
for name, path in d.items():

assert site.config.base_url + quote(path) in html
assert site.config.base_url + quote(path) in html
assert '/media/img/hyde-logo.png' in html

+ 9
- 8
hyde/tests/ext/test_uglify.py View File

@@ -20,12 +20,11 @@ class TestUglify(object):
def setUp(self):
TEST_SITE.make()
TEST_SITE.parent.child_folder(
'sites/test_jinja').copy_contents_to(TEST_SITE)
'sites/test_jinja').copy_contents_to(TEST_SITE)
JS = TEST_SITE.child_folder('content/media/js')
JS.make()
UGLIFY_SOURCE.copy_contents_to(JS)


def tearDown(self):
TEST_SITE.delete()

@@ -34,7 +33,8 @@ class TestUglify(object):
s.config.plugins = ['hyde.ext.plugins.js.UglifyPlugin']
s.config.mode = "production"
source = TEST_SITE.child('content/media/js/jquery.js')
target = File(Folder(s.config.deploy_root_path).child('media/js/jquery.js'))
target = File(
Folder(s.config.deploy_root_path).child('media/js/jquery.js'))
gen = Generator(s)
gen.generate_resource_at_path(source)

@@ -48,9 +48,11 @@ class TestUglify(object):
s = Site(TEST_SITE)
s.config.plugins = ['hyde.ext.plugins.js.UglifyPlugin']
s.config.mode = "production"
s.config.uglify = Expando(dict(args={"comments":"/http\:\/\/jquery.org\/license/"}))
s.config.uglify = Expando(
dict(args={"comments": "/http\:\/\/jquery.org\/license/"}))
source = TEST_SITE.child('content/media/js/jquery.js')
target = File(Folder(s.config.deploy_root_path).child('media/js/jquery.js'))
target = File(
Folder(s.config.deploy_root_path).child('media/js/jquery.js'))
gen = Generator(s)
gen.generate_resource_at_path(source)

@@ -65,7 +67,8 @@ class TestUglify(object):
s.config.plugins = ['hyde.ext.plugins.js.UglifyPlugin']
s.config.mode = "dev"
source = TEST_SITE.child('content/media/js/jquery.js')
target = File(Folder(s.config.deploy_root_path).child('media/js/jquery.js'))
target = File(
Folder(s.config.deploy_root_path).child('media/js/jquery.js'))
gen = Generator(s)
gen.generate_resource_at_path(source)

@@ -74,5 +77,3 @@ class TestUglify(object):
# TODO: Very fragile. Better comparison needed.
text = target.read_all()
assert_no_diff(expected, text)



+ 19
- 17
hyde/tests/ext/test_urlcleaner.py View File

@@ -19,14 +19,14 @@ class TestUrlCleaner(object):
def setUp(self):
TEST_SITE.make()
TEST_SITE.parent.child_folder(
'sites/test_jinja').copy_contents_to(TEST_SITE)
'sites/test_jinja').copy_contents_to(TEST_SITE)

def tearDown(self):
TEST_SITE.delete()

def test_url_cleaner(self):
s = Site(TEST_SITE)
cfg = """
s = Site(TEST_SITE)
cfg = """
plugins:
- hyde.ext.plugins.urls.UrlCleanerPlugin
urlcleaner:
@@ -36,24 +36,26 @@ class TestUrlCleaner(object):
- html
append_slash: true
"""
s.config = Config(TEST_SITE, config_dict=yaml.load(cfg))
text = """
s.config = Config(TEST_SITE, config_dict=yaml.load(cfg))
text = """
{% extends "base.html" %}

{% block main %}
<a id="index" href="{{ content_url('about.html') }}"></a>
<a id="blog" href="{{ content_url('blog/2010/december/merry-christmas.html') }}"></a>
<a id="blog" href=
"{{ content_url('blog/2010/december/merry-christmas.html') }}"></a>
{% endblock %}
"""

about2 = File(TEST_SITE.child('content/test.html'))
about2.write(text)
gen = Generator(s)
gen.generate_all()

from pyquery import PyQuery
target = File(Folder(s.config.deploy_root_path).child('test.html'))
text = target.read_all()
q = PyQuery(text)
assert q('a#index').attr("href") == '/'
assert q('a#blog').attr("href") == '/blog/2010/december/merry-christmas'
about2 = File(TEST_SITE.child('content/test.html'))
about2.write(text)
gen = Generator(s)
gen.generate_all()

from pyquery import PyQuery
target = File(Folder(s.config.deploy_root_path).child('test.html'))
text = target.read_all()
q = PyQuery(text)
assert q('a#index').attr("href") == '/'
assert q('a#blog').attr(
"href") == '/blog/2010/december/merry-christmas'

+ 9
- 8
hyde/tests/ssp/ext/banner.py View File

@@ -2,17 +2,18 @@ from hyde.plugin import Plugin


class BannerPlugin(Plugin):
"""
Adds a comment banner to all generated html files
"""

def text_resource_complete(self, resource, text):
banner = """
"""
Adds a comment banner to all generated html files
"""

def text_resource_complete(self, resource, text):
banner = """
<!--
This file was produced with infinite love, care & sweat.
Please dont copy. If you have to, please drop me a note.
-->
"""
if resource.source.kind == "html":
text = banner + text
return text
if resource.source.kind == "html":
text = banner + text
return text

+ 20
- 11
hyde/tests/test_generate.py View File

@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
"""
Use nose
`$ pip install nose`
@@ -15,11 +15,13 @@ from fswrap import File, Folder

TEST_SITE = File(__file__).parent.child_folder('_test')


class TestGenerator(object):

def setUp(self):
TEST_SITE.make()
TEST_SITE.parent.child_folder('sites/test_jinja').copy_contents_to(TEST_SITE)
TEST_SITE.parent.child_folder(
'sites/test_jinja').copy_contents_to(TEST_SITE)

def tearDown(self):
TEST_SITE.delete()
@@ -38,7 +40,8 @@ class TestGenerator(object):
def test_generate_resource_from_path_with_is_processable_false(self):
site = Site(TEST_SITE)
site.load()
resource = site.content.resource_from_path(TEST_SITE.child('content/about.html'))
resource = site.content.resource_from_path(
TEST_SITE.child('content/about.html'))
resource.is_processable = False
gen = Generator(site)
gen.generate_resource_at_path(TEST_SITE.child('content/about.html'))
@@ -48,7 +51,8 @@ class TestGenerator(object):
def test_generate_resource_from_path_with_uses_template_false(self):
site = Site(TEST_SITE)
site.load()
resource = site.content.resource_from_path(TEST_SITE.child('content/about.html'))
resource = site.content.resource_from_path(
TEST_SITE.child('content/about.html'))
resource.uses_template = False
gen = Generator(site)
gen.generate_resource_at_path(TEST_SITE.child('content/about.html'))
@@ -61,11 +65,13 @@ class TestGenerator(object):
def test_generate_resource_from_path_with_deploy_override(self):
site = Site(TEST_SITE)
site.load()
resource = site.content.resource_from_path(TEST_SITE.child('content/about.html'))
resource = site.content.resource_from_path(
TEST_SITE.child('content/about.html'))
resource.relative_deploy_path = 'about/index.html'
gen = Generator(site)
gen.generate_resource_at_path(TEST_SITE.child('content/about.html'))
about = File(Folder(site.config.deploy_root_path).child('about/index.html'))
about = File(
Folder(site.config.deploy_root_path).child('about/index.html'))
assert about.exists
text = about.read_all()
q = PyQuery(text)
@@ -74,7 +80,8 @@ class TestGenerator(object):
def test_has_resource_changed(self):
site = Site(TEST_SITE)
site.load()
resource = site.content.resource_from_path(TEST_SITE.child('content/about.html'))
resource = site.content.resource_from_path(
TEST_SITE.child('content/about.html'))
gen = Generator(site)
gen.generate_all()
import time
@@ -110,7 +117,8 @@ class TestGenerator(object):
{% endblock %}
"""
site.load()
resource = site.content.resource_from_path(TEST_SITE.child('content/about.html'))
resource = site.content.resource_from_path(
TEST_SITE.child('content/about.html'))
gen = Generator(site)
resource.source_file.write(text)
gen.generate_all()
@@ -150,7 +158,8 @@ class TestGenerator(object):
"""
File(TEST_SITE.child('nav.yaml')).write(nav)
site.load()
resource = site.content.resource_from_path(TEST_SITE.child('content/about.html'))
resource = site.content.resource_from_path(
TEST_SITE.child('content/about.html'))
gen = Generator(site)
resource.source_file.write(text)
gen.generate_all()
@@ -192,7 +201,8 @@ main:
"""
File(TEST_SITE.child('nav.yaml')).write(nav)
site.load()
resource = site.content.resource_from_path(TEST_SITE.child('content/about.html'))
resource = site.content.resource_from_path(
TEST_SITE.child('content/about.html'))
gen = Generator(site)
resource.source_file.write(text)
gen.generate_all()
@@ -257,4 +267,3 @@ main:
left = File(site.config.deploy_root_path.child(f1.name)).read_all()
right = File(site.config.deploy_root_path.child(f2.name)).read_all()
assert left == right


+ 15
- 2
hyde/tests/test_initialize.py View File

@@ -16,22 +16,27 @@ from nose.tools import raises, with_setup, nottest
TEST_SITE = File(__file__).parent.child_folder('_test')
TEST_SITE_AT_USER = Folder('~/_test')


@nottest
def create_test_site():
TEST_SITE.make()


@nottest
def delete_test_site():
TEST_SITE.delete()


@nottest
def create_test_site_at_user():
TEST_SITE_AT_USER.make()


@nottest
def delete_test_site_at_user():
TEST_SITE_AT_USER.delete()


@raises(HydeException)
@with_setup(create_test_site, delete_test_site)
def test_ensure_exception_when_site_yaml_exists():
@@ -39,6 +44,7 @@ def test_ensure_exception_when_site_yaml_exists():
File(TEST_SITE.child('site.yaml')).write("Hey")
e.run(e.parse(['-s', unicode(TEST_SITE), 'create']))


@raises(HydeException)
@with_setup(create_test_site, delete_test_site)
def test_ensure_exception_when_content_folder_exists():
@@ -46,6 +52,7 @@ def test_ensure_exception_when_content_folder_exists():
TEST_SITE.child_folder('content').make()
e.run(e.parse(['-s', unicode(TEST_SITE), 'create']))


@raises(HydeException)
@with_setup(create_test_site, delete_test_site)
def test_ensure_exception_when_layout_folder_exists():
@@ -53,12 +60,14 @@ def test_ensure_exception_when_layout_folder_exists():
TEST_SITE.child_folder('layout').make()
e.run(e.parse(['-s', unicode(TEST_SITE), 'create']))


@with_setup(create_test_site, delete_test_site)
def test_ensure_no_exception_when_empty_site_exists():
e = Engine(raise_exceptions=True)
e.run(e.parse(['-s', unicode(TEST_SITE), 'create']))
verify_site_contents(TEST_SITE, Layout.find_layout())


@with_setup(create_test_site, delete_test_site)
def test_ensure_no_exception_when_forced():
e = Engine(raise_exceptions=True)
@@ -75,6 +84,7 @@ def test_ensure_no_exception_when_forced():
e.run(e.parse(['-s', unicode(TEST_SITE), 'create', '-f']))
verify_site_contents(TEST_SITE, Layout.find_layout())


@with_setup(create_test_site, delete_test_site)
def test_ensure_no_exception_when_sitepath_does_not_exist():
e = Engine(raise_exceptions=True)
@@ -82,6 +92,7 @@ def test_ensure_no_exception_when_sitepath_does_not_exist():
e.run(e.parse(['-s', unicode(TEST_SITE), 'create', '-f']))
verify_site_contents(TEST_SITE, Layout.find_layout())


@with_setup(create_test_site_at_user, delete_test_site_at_user)
def test_ensure_can_create_site_at_user():
e = Engine(raise_exceptions=True)
@@ -89,13 +100,15 @@ def test_ensure_can_create_site_at_user():
e.run(e.parse(['-s', unicode(TEST_SITE_AT_USER), 'create', '-f']))
verify_site_contents(TEST_SITE_AT_USER, Layout.find_layout())


@nottest
def verify_site_contents(site, layout):
assert site.exists
assert site.child_folder('layout').exists
assert File(site.child('info.yaml')).exists

expected = map(lambda f: f.get_relative_path(layout), layout.walker.walk_all())
expected = map(
lambda f: f.get_relative_path(layout), layout.walker.walk_all())
actual = map(lambda f: f.get_relative_path(site), site.walker.walk_all())
assert actual
assert expected
@@ -104,9 +117,9 @@ def verify_site_contents(site, layout):
actual.sort()
assert actual == expected


@raises(HydeException)
@with_setup(create_test_site, delete_test_site)
def test_ensure_exception_when_layout_is_invalid():
e = Engine(raise_exceptions=True)
e.run(e.parse(['-s', unicode(TEST_SITE), 'create', '-l', 'junk']))


+ 56
- 32
hyde/tests/test_jinja2template.py View File

@@ -24,6 +24,7 @@ import yaml
ROOT = File(__file__).parent
JINJA2 = ROOT.child_folder('templates/jinja2')


class Article(object):

def __init__(self, id):
@@ -33,12 +34,14 @@ class Article(object):
self.user = choice(users)
self.body = generate_lorem_ipsum()
self.pub_date = datetime.utcfromtimestamp(
randrange(10 ** 9, 2 * 10 ** 9))
randrange(10 ** 9, 2 * 10 ** 9))
self.published = True


def dateformat(x):
return x.strftime('%Y-%m-%d')


class User(object):

def __init__(self, username):
@@ -59,6 +62,7 @@ navigation = [

context = dict(users=users, articles=articles, page_navigation=navigation)


def test_render():
"""
Uses pyquery to test the html structure for validity
@@ -77,6 +81,7 @@ def test_render():
assert actual("div.article p.meta").length == 20
assert actual("div.article div.text").length == 20


def test_typogrify():
source = """
{%filter typogrify%}
@@ -89,6 +94,7 @@ def test_typogrify():
html = t.render(source, {}).strip()
assert html == u'One <span class="amp">&amp;</span>&nbsp;two'


def test_spaceless():
source = """
{%spaceless%}
@@ -124,6 +130,7 @@ def test_spaceless():
"""
assert html.strip() == expected.strip()


def test_asciidoc():
source = """
{%asciidoc%}
@@ -146,6 +153,7 @@ def test_asciidoc():
assert q("li:nth-child(2)").text().strip() == "test2"
assert q("li:nth-child(3)").text().strip() == "test3"


def test_markdown():
source = """
{%markdown%}
@@ -157,6 +165,7 @@ def test_markdown():
html = t.render(source, {}).strip()
assert html == u'<h3>Heading 3</h3>'


def test_restructuredtext():
source = """
{% restructuredtext %}
@@ -171,6 +180,7 @@ Hello
<h1 class="title">Hello</h1>
</div>""", html


def test_restructuredtext_with_sourcecode():
source = """
{% restructuredtext %}
@@ -188,24 +198,30 @@ See `Example`_
{% endrestructuredtext %}
"""

expected = """
expected = ("""
<div class="document" id="code">
<h1 class="title">Code</h1>
<div class="highlight"><pre><span class="k">def</span> <span class="nf">add</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">):</span>
<span class="k">return</span> <span class="n">a</span> <span class="o">+</span> <span class="n">b</span>
<div class="highlight"><pre><span class="k">def</span> """
"""<span class="nf">add</span><span class="p">(</span>"""
"""<span class="n">a"""
"""</span><span class="p">,</span> <span class="n">b</span>"""
"""<span class="p">):</span>
<span class="k">return</span> <span class="n">a</span> """
"""<span class="o">+</span> <span class="n">b</span>
</pre></div>
<p>See <a class="reference external" href="example.html">Example</a></p>
</div>
"""
""")
t = Jinja2Template(JINJA2.path)
s = Site(JINJA2.path)
c = Config(JINJA2.path, config_dict=dict(
restructuredtext=dict(highlight_source=True)))
restructuredtext=dict(highlight_source=True)))
s.config = c
t.configure(s)
html = t.render(source, {}).strip()
assert html.strip() == expected.strip()


def test_markdown_with_extensions():
source = """
{%markdown%}
@@ -215,13 +231,15 @@ def test_markdown_with_extensions():
"""
t = Jinja2Template(JINJA2.path)
s = Site(JINJA2.path)
c = Config(JINJA2.path, config_dict=dict(markdown=dict(extensions=['headerid'])))
c = Config(JINJA2.path, config_dict=dict(
markdown=dict(extensions=['headerid'])))
s.config = c
t.configure(s)
t.env.filters['dateformat'] = dateformat
html = t.render(source, {}).strip()
assert html == u'<h3 id="heading-3">Heading 3</h3>'


def test_markdown_with_sourcecode():
source = """
{%markdown%}
@@ -237,19 +255,23 @@ See [Example][]
{%endmarkdown%}
"""

expected = """
expected = ("""
<h1>Code</h1>
<div class="codehilite"><pre><span class="k">def</span> <span class="nf">add</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">):</span>
<span class="k">return</span> <span class="n">a</span> <span class="o">+</span> <span class="n">b</span>
<div class="codehilite"><pre><span class="k">def</span> """
"""<span class="nf">add</span><span class="p">(</span>"""
"""<span class="n">a</span><span class="p">,</span> """
"""<span class="n">b</span><span class="p">):</span>
<span class="k">return</span> <span class="n">a</span> <span class="o">+"""
"""</span> <span class="n">b</span>
</pre></div>


<p>See <a href="example.html">Example</a></p>
"""
""")
t = Jinja2Template(JINJA2.path)
s = Site(JINJA2.path)
c = Config(JINJA2.path, config_dict=dict(
markdown=dict(extensions=['codehilite'])))
markdown=dict(extensions=['codehilite'])))
s.config = c
t.configure(s)
html = t.render(source, {}).strip()
@@ -265,13 +287,15 @@ def test_line_statements():
"""
t = Jinja2Template(JINJA2.path)
s = Site(JINJA2.path)
c = Config(JINJA2.path, config_dict=dict(markdown=dict(extensions=['headerid'])))
c = Config(JINJA2.path, config_dict=dict(
markdown=dict(extensions=['headerid'])))
s.config = c
t.configure(s)
t.env.filters['dateformat'] = dateformat
html = t.render(source, {}).strip()
assert html == u'<h3 id="heading-3">Heading 3</h3>'


def test_line_statements_with_config():
source = """
%% markdown
@@ -298,6 +322,7 @@ def test_line_statements_with_config():

TEST_SITE = File(__file__).parent.child_folder('_test')


@nottest
def assert_markdown_typogrify_processed_well(include_text, includer_text):
site = Site(TEST_SITE)
@@ -317,11 +342,13 @@ def assert_markdown_typogrify_processed_well(include_text, includer_text):
assert q(".amp").length == 1
return html


class TestJinjaTemplate(object):

def setUp(self):
TEST_SITE.make()
TEST_SITE.parent.child_folder('sites/test_jinja').copy_contents_to(TEST_SITE)
TEST_SITE.parent.child_folder(
'sites/test_jinja').copy_contents_to(TEST_SITE)

def tearDown(self):
TEST_SITE.delete()
@@ -375,7 +402,6 @@ class TestJinjaTemplate(object):
assert article.length == 1
assert article.text() == "Heya"


def test_depends_with_reference_tag(self):
site = Site(TEST_SITE)
JINJA2.copy_contents_to(site.content.source)
@@ -408,7 +434,6 @@ class TestJinjaTemplate(object):

assert not deps[0]


def test_can_include_templates_with_processing(self):
text = """
===
@@ -424,11 +449,9 @@ Hyde & Jinja.
{% endmarkdown %}{% endfilter %}
"""


text2 = """{% include "inc.md" %}"""
assert_markdown_typogrify_processed_well(text, text2)


def test_includetext(self):
text = """
===
@@ -595,7 +618,6 @@ Hyde & Jinja.
assert "mark" not in html
assert "reference" not in html


def test_refer_with_var(self):
text = """
===
@@ -623,7 +645,6 @@ Hyde & Jinja.
assert "mark" not in html
assert "reference" not in html


def test_yaml_tag(self):

text = """
@@ -728,20 +749,22 @@ item_list:
</ul>
</aside>
"""
text = "{%% filter markdown|typogrify %%}{%% raw %%}%s{%% endraw %%}{%% endfilter %%}" % expected
text = """{%% filter markdown|typogrify %%}{%% raw
%%}%s{%% endraw %%}{%% endfilter %%}""" % expected
t = Jinja2Template(JINJA2.path)
t.configure(None)
html = t.render(text, {}).strip()
assert html.strip() == expected.strip()

def test_urlencode_filter(self):
text= u"""
<a href="{{ 'фотография.jpg'|urlencode }}">фотография</a>
<a href="{{ 'http://localhost:8080/"abc.jpg'|urlencode }}">quoted</a>
text = u"""
<a href="{{ 'фотография.jpg'|urlencode }}"
>фотография</a><a href="{{ 'http://localhost:8080/"abc.jpg'|urlencode
}}">quoted</a>
"""
expected = u"""
<a href="%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D1%8F.jpg">фотография</a>
<a href="http%3A//localhost%3A8080/%22abc.jpg">quoted</a>
<a href="%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D1%8F.jpg"
>фотография</a><a href="http%3A//localhost%3A8080/%22abc.jpg">quoted</a>
"""
t = Jinja2Template(JINJA2.path)
t.configure(None)
@@ -749,13 +772,14 @@ item_list:
assert html.strip() == expected.strip()

def test_urldecode_filter(self):
text= u"""
<a href="{{ 'фотография.jpg'|urlencode }}">{{ "%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D1%8F.jpg"|urldecode }}</a>
"""
expected = u"""
<a href="%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D1%8F.jpg">фотография.jpg</a>
text = u"""
<a href="{{ 'фотография.jpg'|urlencode }}">{{
"%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D1%8F.jpg"|urldecode
}}</a>
"""
expected = (u'<a href="%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1'
u'%80%D0%B0%D1%84%D0%B8%D1%8F.jpg">фотография.jpg</a>')
t = Jinja2Template(JINJA2.path)
t.configure(None)
html = t.render(text, {}).strip()
assert html.strip() == expected.strip()
assert html.strip() == expected.strip()

+ 4
- 0
hyde/tests/test_layout.py View File

@@ -14,19 +14,23 @@ from nose.tools import nottest, with_setup
DATA_ROOT = File(__file__).parent.child_folder('data')
LAYOUT_ROOT = DATA_ROOT.child_folder(LAYOUTS)


@nottest
def setup_data():
DATA_ROOT.make()


@nottest
def cleanup_data():
DATA_ROOT.delete()


def test_find_layout_from_package_dir():
f = Layout.find_layout()
assert f.name == 'basic'
assert f.child_folder('layout').exists


@with_setup(setup_data, cleanup_data)
def test_find_layout_from_env_var():
f = Layout.find_layout()


+ 11
- 2
hyde/tests/test_model.py View File

@@ -8,18 +8,21 @@ from hyde.model import Config, Expando

from fswrap import File, Folder


def test_expando_one_level():
d = {"a": 123, "b": "abc"}
x = Expando(d)
assert x.a == d['a']
assert x.b == d['b']


def test_expando_two_levels():
d = {"a": 123, "b": {"c": 456}}
x = Expando(d)
assert x.a == d['a']
assert x.b.c == d['b']['c']


def test_expando_three_levels():
d = {"a": 123, "b": {"c": 456, "d": {"e": "abc"}}}
x = Expando(d)
@@ -27,6 +30,7 @@ def test_expando_three_levels():
assert x.b.c == d['b']['c']
assert x.b.d.e == d['b']['d']['e']


def test_expando_update():
d1 = {"a": 123, "b": "abc"}
x = Expando(d1)
@@ -34,7 +38,7 @@ def test_expando_update():
assert x.b == d1['b']
d = {"b": {"c": 456, "d": {"e": "abc"}}, "f": "lmn"}
x.update(d)
assert x.a == d1['a']
assert x.a == d1['a']
assert x.b.c == d['b']['c']
assert x.b.d.e == d['b']['d']['e']
assert x.f == d["f"]
@@ -44,11 +48,13 @@ def test_expando_update():
assert x.a == 789
assert x.f == "opq"


def test_expando_to_dict():
d = {"a": 123, "b": {"c": 456, "d": {"e": "abc"}}}
x = Expando(d)
assert d == x.to_dict()


def test_expando_to_dict_with_update():
d1 = {"a": 123, "b": "abc"}
x = Expando(d1)
@@ -67,6 +73,8 @@ def test_expando_to_dict_with_update():
TEST_SITE = File(__file__).parent.child_folder('_test')

import yaml


class TestConfig(object):

@classmethod
@@ -94,7 +102,8 @@ class TestConfig(object):

def setUp(self):
TEST_SITE.make()
TEST_SITE.parent.child_folder('sites/test_jinja').copy_contents_to(TEST_SITE)
TEST_SITE.parent.child_folder(
'sites/test_jinja').copy_contents_to(TEST_SITE)

def tearDown(self):
TEST_SITE.delete()


+ 128
- 78
hyde/tests/test_plugin.py View File

@@ -16,15 +16,18 @@ from fswrap import File, Folder

TEST_SITE = File(__file__).parent.child_folder('_test')


class PluginLoaderStub(Plugin):
pass


class NoReturnPlugin(Plugin):

def begin_text_resource(self, resource, text):
print "NoReturnPlugin"
return None


class ConstantReturnPlugin(Plugin):

def begin_text_resource(self, resource, text):
@@ -37,7 +40,8 @@ class TestPlugins(object):
@classmethod
def setup_class(cls):
TEST_SITE.make()
TEST_SITE.parent.child_folder('sites/test_jinja').copy_contents_to(TEST_SITE)
TEST_SITE.parent.child_folder(
'sites/test_jinja').copy_contents_to(TEST_SITE)
folders = []
text_files = []
binary_files = []
@@ -58,14 +62,13 @@ class TestPlugins(object):
cls.content_text_resources = sorted(text_files)
cls.content_binary_resources = sorted(binary_files)


@classmethod
def teardown_class(cls):
TEST_SITE.delete()

def setUp(self):
self.site = Site(TEST_SITE)
self.site.config.plugins = ['hyde.tests.test_plugin.PluginLoaderStub']
self.site = Site(TEST_SITE)
self.site.config.plugins = ['hyde.tests.test_plugin.PluginLoaderStub']

def test_can_load_plugin_modules(self):
assert not len(self.site.plugins)
@@ -74,25 +77,27 @@ class TestPlugins(object):
assert len(self.site.plugins) == 1
assert self.site.plugins[0].__class__.__name__ == 'PluginLoaderStub'


def test_generator_loads_plugins(self):
gen = Generator(self.site)
Generator(self.site)
assert len(self.site.plugins) == 1

def test_generator_template_registered_called(self):
with patch.object(PluginLoaderStub, 'template_loaded') as template_loaded_stub:
with patch.object(PluginLoaderStub,
'template_loaded') as template_loaded_stub:
gen = Generator(self.site)
gen.generate_all()
assert template_loaded_stub.call_count == 1

def test_generator_template_begin_generation_called(self):
with patch.object(PluginLoaderStub, 'begin_generation') as begin_generation_stub:
with patch.object(PluginLoaderStub,
'begin_generation') as begin_generation_stub:
gen = Generator(self.site)
gen.generate_all()
assert begin_generation_stub.call_count == 1

def test_generator_template_begin_generation_called_for_single_resource(self):
with patch.object(PluginLoaderStub, 'begin_generation') as begin_generation_stub:
def test_generator_template_begin_generation_called_for_single_res(self):
with patch.object(PluginLoaderStub,
'begin_generation') as begin_generation_stub:
gen = Generator(self.site)
path = self.site.content.source_folder.child('about.html')
gen.generate_resource_at_path(path)
@@ -100,29 +105,32 @@ class TestPlugins(object):
assert begin_generation_stub.call_count == 1

def test_generator_template_begin_generation_called_for_single_node(self):
with patch.object(PluginLoaderStub, 'begin_generation') as begin_generation_stub:
with patch.object(PluginLoaderStub,
'begin_generation') as begin_generation_stub:
gen = Generator(self.site)
path = self.site.content.source_folder
gen.generate_node_at_path(path)
assert begin_generation_stub.call_count == 1


def test_generator_template_generation_complete_called(self):
with patch.object(PluginLoaderStub, 'generation_complete') as generation_complete_stub:
with patch.object(PluginLoaderStub,
'generation_complete') as generation_complete_stub:
gen = Generator(self.site)
gen.generate_all()
assert generation_complete_stub.call_count == 1

def test_generator_template_generation_complete_called_for_single_resource(self):
with patch.object(PluginLoaderStub, 'generation_complete') as generation_complete_stub:
def test_generator_template_generation_complete_called_for_single_rs(self):
with patch.object(PluginLoaderStub,
'generation_complete') as generation_complete_stub:
gen = Generator(self.site)
path = self.site.content.source_folder.child('about.html')
gen.generate_resource_at_path(path)

assert generation_complete_stub.call_count == 1

def test_generator_template_generation_complete_called_for_single_node(self):
with patch.object(PluginLoaderStub, 'generation_complete') as generation_complete_stub:
def test_generator_template_generation_complete_called_for_single_nd(self):
with patch.object(PluginLoaderStub,
'generation_complete') as generation_complete_stub:
gen = Generator(self.site)
path = self.site.content.source_folder
gen.generate_node_at_path(path)
@@ -141,7 +149,7 @@ class TestPlugins(object):
gen.generate_resource_at_path(path)
assert begin_site_stub.call_count == 1

def test_generator_template_begin_site_not_called_for_single_resource_second_time(self):
def test_generator_template_begin_site_not_called_sngle_res_scnd_tm(self):
with patch.object(PluginLoaderStub, 'begin_site') as begin_site_stub:
gen = Generator(self.site)
gen.generate_all()
@@ -158,7 +166,7 @@ class TestPlugins(object):

assert begin_site_stub.call_count == 1

def test_generator_template_begin_site_not_called_for_single_node_second_time(self):
def test_generator_template_begin_site_not_call_for_sngl_nd_scnd_tm(self):
with patch.object(PluginLoaderStub, 'begin_site') as begin_site_stub:
gen = Generator(self.site)
gen.generate_all()
@@ -169,24 +177,26 @@ class TestPlugins(object):
assert begin_site_stub.call_count == 1

def test_generator_template_site_complete_called(self):
with patch.object(PluginLoaderStub, 'site_complete') as site_complete_stub:
with patch.object(PluginLoaderStub,
'site_complete') as site_complete_stub:
gen = Generator(self.site)
gen.generate_all()
assert site_complete_stub.call_count == 1


def test_generator_template_site_complete_called_for_single_resource(self):

with patch.object(PluginLoaderStub, 'site_complete') as site_complete_stub:
with patch.object(PluginLoaderStub,
'site_complete') as site_complete_stub:
gen = Generator(self.site)
path = self.site.content.source_folder.child('about.html')
gen.generate_resource_at_path(path)

assert site_complete_stub.call_count == 1

def test_generator_template_site_complete_not_called_for_single_resource_second_time(self):
def test_generator_template_site_complt_not_call_4_sngl_res_scnd_tm(self):

with patch.object(PluginLoaderStub, 'site_complete') as site_complete_stub:
with patch.object(PluginLoaderStub,
'site_complete') as site_complete_stub:
gen = Generator(self.site)
gen.generate_all()
assert site_complete_stub.call_count == 1
@@ -197,16 +207,18 @@ class TestPlugins(object):

def test_generator_template_site_complete_called_for_single_node(self):

with patch.object(PluginLoaderStub, 'site_complete') as site_complete_stub:
with patch.object(PluginLoaderStub,
'site_complete') as site_complete_stub:
gen = Generator(self.site)
path = self.site.content.source_folder
gen.generate_node_at_path(path)

assert site_complete_stub.call_count == 1

def test_generator_template_site_complete_not_called_for_single_node_second_time(self):
def test_generator_template_site_complete_not_call_4_sngl_nd_scnd_tm(self):

with patch.object(PluginLoaderStub, 'site_complete') as site_complete_stub:
with patch.object(PluginLoaderStub,
'site_complete') as site_complete_stub:
gen = Generator(self.site)
gen.generate_all()
path = self.site.content.source_folder
@@ -221,67 +233,81 @@ class TestPlugins(object):
gen.generate_all()

assert begin_node_stub.call_count == len(self.content_nodes)
called_with_nodes = sorted([arg[0][0].path for arg in begin_node_stub.call_args_list])
called_with_nodes = sorted(
[arg[0][0].path for arg in begin_node_stub.call_args_list])
assert called_with_nodes == self.content_nodes

def test_generator_template_begin_node_called_for_single_resource(self):

with patch.object(PluginLoaderStub, 'begin_node') as begin_node_stub:
gen = Generator(self.site)
gen.generate_resource_at_path(self.site.content.source_folder.child('about.html'))
gen.generate_resource_at_path(
self.site.content.source_folder.child('about.html'))
assert begin_node_stub.call_count == len(self.content_nodes)


def test_generator_template_begin_node_not_called_for_single_resource_second_time(self):
def test_generator_template_begin_node_not_called_4_sngl_res_scnd_tm(self):

with patch.object(PluginLoaderStub, 'begin_node') as begin_node_stub:
gen = Generator(self.site)
gen.generate_all()
assert begin_node_stub.call_count == len(self.content_nodes)
gen.generate_resource_at_path(self.site.content.source_folder.child('about.html'))
assert begin_node_stub.call_count == len(self.content_nodes) # No extra calls

gen.generate_resource_at_path(
self.site.content.source_folder.child('about.html'))
assert begin_node_stub.call_count == len(
self.content_nodes) # No extra calls

def test_generator_template_node_complete_called(self):

with patch.object(PluginLoaderStub, 'node_complete') as node_complete_stub:
with patch.object(PluginLoaderStub,
'node_complete') as node_complete_stub:
gen = Generator(self.site)
gen.generate_all()

assert node_complete_stub.call_count == len(self.content_nodes)
called_with_nodes = sorted([arg[0][0].path for arg in node_complete_stub.call_args_list])
called_with_nodes = sorted(
[arg[0][0].path for arg in node_complete_stub.call_args_list])
assert called_with_nodes == self.content_nodes

def test_generator_template_node_complete_called_for_single_resource(self):

with patch.object(PluginLoaderStub, 'node_complete') as node_complete_stub:
with patch.object(PluginLoaderStub,
'node_complete') as node_complete_stub:
gen = Generator(self.site)
gen.generate_resource_at_path(self.site.content.source_folder.child('about.html'))
gen.generate_resource_at_path(
self.site.content.source_folder.child('about.html'))
assert node_complete_stub.call_count == len(self.content_nodes)

def test_generator_template_node_complete_not_called_for_single_resource_second_time(self):
def test_generator_template_node_complete_not_cal_4_sngl_res_scnd_tm(self):

with patch.object(PluginLoaderStub, 'node_complete') as node_complete_stub:
with patch.object(PluginLoaderStub,
'node_complete') as node_complete_stub:
gen = Generator(self.site)
gen.generate_all()
assert node_complete_stub.call_count == len(self.content_nodes)
gen.generate_resource_at_path(self.site.content.source_folder.child('about.html'))
assert node_complete_stub.call_count == len(self.content_nodes) # No extra calls
gen.generate_resource_at_path(
self.site.content.source_folder.child('about.html'))
assert node_complete_stub.call_count == len(
self.content_nodes) # No extra calls

def test_generator_template_begin_text_resource_called(self):

with patch.object(PluginLoaderStub, 'begin_text_resource') as begin_text_resource_stub:
with patch.object(PluginLoaderStub,
'begin_text_resource') as begin_text_resource_stub:
begin_text_resource_stub.reset_mock()
begin_text_resource_stub.return_value = ''
gen = Generator(self.site)
gen.generate_all()

called_with_resources = sorted([arg[0][0].path for arg in begin_text_resource_stub.call_args_list])
assert set(called_with_resources) == set(self.content_text_resources)
called_with_resources = sorted(
[arg[0][0].path for arg in
begin_text_resource_stub.call_args_list])
assert set(called_with_resources) == set(
self.content_text_resources)

def test_generator_template_begin_text_resource_called_for_single_resource(self):
def test_generator_template_begin_text_resource_called_for_sngl_res(self):

with patch.object(PluginLoaderStub, 'begin_text_resource') as begin_text_resource_stub:
with patch.object(PluginLoaderStub,
'begin_text_resource') as begin_text_resource_stub:
begin_text_resource_stub.return_value = ''
gen = Generator(self.site)
gen.generate_all()
@@ -290,81 +316,105 @@ class TestPlugins(object):
gen = Generator(self.site)
gen.generate_resource_at_path(path, incremental=True)

called_with_resources = sorted([arg[0][0].path for arg in begin_text_resource_stub.call_args_list])
called_with_resources = sorted(
[arg[0][0].path for arg in
begin_text_resource_stub.call_args_list])
assert begin_text_resource_stub.call_count == 1
assert called_with_resources[0] == path

def test_generator_template_begin_binary_resource_called(self):

with patch.object(PluginLoaderStub, 'begin_binary_resource') as begin_binary_resource_stub:
with patch.object(PluginLoaderStub, 'begin_binary_resource') as \
begin_binary_resource_stub:
gen = Generator(self.site)
gen.generate_all()

called_with_resources = sorted([arg[0][0].path for arg in begin_binary_resource_stub.call_args_list])
assert begin_binary_resource_stub.call_count == len(self.content_binary_resources)
called_with_resources = sorted(
[arg[0][0].path for arg in
begin_binary_resource_stub.call_args_list])
assert begin_binary_resource_stub.call_count == len(
self.content_binary_resources)
assert called_with_resources == self.content_binary_resources

def test_generator_template_begin_binary_resource_called_for_single_resource(self):
def test_generator_template_begin_binary_resource_called_4_sngl_res(self):

with patch.object(PluginLoaderStub, 'begin_binary_resource') as begin_binary_resource_stub:
with patch.object(PluginLoaderStub, 'begin_binary_resource') as \
begin_binary_resource_stub:
gen = Generator(self.site)
gen.generate_all()
begin_binary_resource_stub.reset_mock()
path = self.site.content.source_folder.child('favicon.ico')
gen.generate_resource_at_path(path)

called_with_resources = sorted([arg[0][0].path for arg in begin_binary_resource_stub.call_args_list])
called_with_resources = sorted(
[arg[0][0].path for arg in
begin_binary_resource_stub.call_args_list])
assert begin_binary_resource_stub.call_count == 1
assert called_with_resources[0] == path

def test_plugin_chaining(self):
self.site.config.plugins = [
self.site.config.plugins = [
'hyde.tests.test_plugin.ConstantReturnPlugin',
'hyde.tests.test_plugin.NoReturnPlugin'
]
path = self.site.content.source_folder.child('about.html')
gen = Generator(self.site)
gen.generate_resource_at_path(path)
about = File(Folder(
self.site.config.deploy_root_path).child('about.html'))
assert about.read_all() == "Jam"
]
path = self.site.content.source_folder.child('about.html')
gen = Generator(self.site)
gen.generate_resource_at_path(path)
about = File(Folder(
self.site.config.deploy_root_path).child('about.html'))
assert about.read_all() == "Jam"

def test_plugin_filters_begin_text_resource(self):
def empty_return(self, resource, text=''):
return text
with patch.object(ConstantReturnPlugin, 'begin_text_resource', new=Mock(wraps=empty_return)) as mock1:
with patch.object(NoReturnPlugin, 'begin_text_resource', new=Mock(wraps=empty_return)) as mock2:
with patch.object(ConstantReturnPlugin, 'begin_text_resource',
new=Mock(wraps=empty_return)) as mock1:
with patch.object(NoReturnPlugin, 'begin_text_resource',
new=Mock(wraps=empty_return)) as mock2:
self.site.config.plugins = [
'hyde.tests.test_plugin.ConstantReturnPlugin',
'hyde.tests.test_plugin.NoReturnPlugin'
]
self.site.config.constantreturn = Expando(dict(include_file_pattern="*.css"))
self.site.config.noreturn = Expando(dict(include_file_pattern=["*.html", "*.txt"]))
]
self.site.config.constantreturn = Expando(
dict(include_file_pattern="*.css"))
self.site.config.noreturn = Expando(
dict(include_file_pattern=["*.html", "*.txt"]))
gen = Generator(self.site)
gen.generate_all()
mock1_args = sorted(set([arg[0][0].name for arg in mock1.call_args_list]))
mock2_args = sorted(set([arg[0][0].name for arg in mock2.call_args_list]))
mock1_args = sorted(
set([arg[0][0].name for arg in mock1.call_args_list]))
mock2_args = sorted(
set([arg[0][0].name for arg in mock2.call_args_list]))
assert len(mock1_args) == 1
assert len(mock2_args) == 4
assert mock1_args == ["site.css"]
assert mock2_args == ["404.html", "about.html", "merry-christmas.html", "robots.txt"]
assert mock2_args == [
"404.html", "about.html",
"merry-christmas.html", "robots.txt"]

def test_plugin_node_filters_begin_text_resource(self):
def empty_return(*args, **kwargs):
return None
with patch.object(ConstantReturnPlugin, 'begin_text_resource', new=Mock(wraps=empty_return)) as mock1:
with patch.object(NoReturnPlugin, 'begin_text_resource', new=Mock(wraps=empty_return)) as mock2:
with patch.object(ConstantReturnPlugin, 'begin_text_resource',
new=Mock(wraps=empty_return)) as mock1:
with patch.object(NoReturnPlugin, 'begin_text_resource',
new=Mock(wraps=empty_return)) as mock2:
self.site.config.plugins = [
'hyde.tests.test_plugin.ConstantReturnPlugin',
'hyde.tests.test_plugin.NoReturnPlugin'
]
self.site.config.constantreturn = Expando(dict(include_paths="media"))
self.site.config.noreturn = Expando(dict(include_file_pattern="*.html", include_paths=["blog"]))
]
self.site.config.constantreturn = Expando(
dict(include_paths="media"))
self.site.config.noreturn = Expando(
dict(include_file_pattern="*.html",
include_paths=["blog"]))
gen = Generator(self.site)
gen.generate_all()
mock1_args = sorted(set([arg[0][0].name for arg in mock1.call_args_list]))
mock2_args = sorted(set([arg[0][0].name for arg in mock2.call_args_list]))
mock1_args = sorted(
set([arg[0][0].name for arg in mock1.call_args_list]))
mock2_args = sorted(
set([arg[0][0].name for arg in mock2.call_args_list]))
assert len(mock1_args) == 1
assert len(mock2_args) == 1
assert mock1_args == ["site.css"]
assert mock2_args == ["merry-christmas.html"]
assert mock2_args == ["merry-christmas.html"]

+ 17
- 8
hyde/tests/test_simple_copy.py View File

@@ -29,10 +29,13 @@ from nose.tools import nottest

TEST_SITE_ROOT = File(__file__).parent.child_folder('sites/test_jinja')


class TestSimpleCopy(object):

@classmethod
def setup_class(cls):
cls.SITE_PATH = File(__file__).parent.child_folder('sites/test_jinja_with_config')
cls.SITE_PATH = File(__file__).parent.child_folder(
'sites/test_jinja_with_config')
cls.SITE_PATH.make()
TEST_SITE_ROOT.copy_contents_to(cls.SITE_PATH)

@@ -67,7 +70,8 @@ class TestSimpleCopy(object):
res = s.content.resource_from_relative_path('about.html')
assert res
assert not res.simple_copy
res = s.content.resource_from_relative_path('blog/2010/december/merry-christmas.html')
res = s.content.resource_from_relative_path(
'blog/2010/december/merry-christmas.html')
assert res
assert res.simple_copy

@@ -81,7 +85,8 @@ class TestSimpleCopy(object):
res = s.content.resource_from_relative_path('about.html')
assert res
assert not res.simple_copy
res = s.content.resource_from_relative_path('blog/2010/december/merry-christmas.html')
res = s.content.resource_from_relative_path(
'blog/2010/december/merry-christmas.html')
assert res
assert res.simple_copy
res = s.content.resource_from_relative_path('media/css/site.css')
@@ -96,8 +101,10 @@ class TestSimpleCopy(object):
s = Site(self.SITE_PATH, self.config)
g = Generator(s)
g.generate_all()
source = s.content.resource_from_relative_path('blog/2010/december/merry-christmas.html')
target = File(s.config.deploy_root_path.child(source.relative_deploy_path))
source = s.content.resource_from_relative_path(
'blog/2010/december/merry-christmas.html')
target = File(
s.config.deploy_root_path.child(source.relative_deploy_path))
left = source.source_file.read_all()
right = target.read_all()
assert left == right
@@ -129,11 +136,13 @@ twitter: @me
])
conf = {'plugins': ['hyde.ext.plugins.meta.MetaPlugin']}
conf.update(self.config.to_dict())
s = Site(self.SITE_PATH, Config(sitepath=self.SITE_PATH, config_dict=conf))
s = Site(self.SITE_PATH, Config(
sitepath=self.SITE_PATH, config_dict=conf))
g = Generator(s)
g.generate_all()
source = s.content.resource_from_relative_path('blog/index.html')
target = File(s.config.deploy_root_path.child(source.relative_deploy_path))
target = File(
s.config.deploy_root_path.child(source.relative_deploy_path))
left = source.source_file.read_all()
right = target.read_all()
assert left == right
assert left == right

+ 40
- 14
hyde/tests/test_site.py View File

@@ -14,6 +14,7 @@ from fswrap import File, Folder

TEST_SITE_ROOT = File(__file__).parent.child_folder('sites/test_jinja')


def test_node_site():
s = Site(TEST_SITE_ROOT)
r = RootNode(TEST_SITE_ROOT.child_folder('content'), s)
@@ -21,6 +22,7 @@ def test_node_site():
n = Node(r.source_folder.child_folder('blog'), r)
assert n.site == s


def test_node_root():
s = Site(TEST_SITE_ROOT)
r = RootNode(TEST_SITE_ROOT.child_folder('content'), s)
@@ -28,12 +30,14 @@ def test_node_root():
n = Node(r.source_folder.child_folder('blog'), r)
assert n.root == r


def test_node_parent():
s = Site(TEST_SITE_ROOT)
r = RootNode(TEST_SITE_ROOT.child_folder('content'), s)
c = r.add_node(TEST_SITE_ROOT.child_folder('content/blog/2010/december'))
assert c.parent == r.node_from_relative_path('blog/2010')


def test_node_module():
s = Site(TEST_SITE_ROOT)
r = RootNode(TEST_SITE_ROOT.child_folder('content'), s)
@@ -43,6 +47,7 @@ def test_node_module():
c = r.add_node(TEST_SITE_ROOT.child_folder('content/blog/2010/december'))
assert c.module == n


def test_node_url():
s = Site(TEST_SITE_ROOT)
r = RootNode(TEST_SITE_ROOT.child_folder('content'), s)
@@ -54,6 +59,7 @@ def test_node_url():
assert c.url == '/' + c.relative_path
assert c.url == '/blog/2010/december'


def test_node_full_url():
s = Site(TEST_SITE_ROOT)
s.config.base_url = 'http://localhost'
@@ -64,6 +70,7 @@ def test_node_full_url():
c = r.add_node(TEST_SITE_ROOT.child_folder('content/blog/2010/december'))
assert c.full_url == 'http://localhost/blog/2010/december'


def test_node_full_url_quoted():
s = Site(TEST_SITE_ROOT)
s.config.base_url = 'http://localhost'
@@ -74,6 +81,7 @@ def test_node_full_url_quoted():
c = r.add_node(TEST_SITE_ROOT.child_folder('content/blo~g/2010/december'))
assert c.full_url == 'http://localhost/' + quote('blo~g/2010/december')


def test_node_relative_path():
s = Site(TEST_SITE_ROOT)
r = RootNode(TEST_SITE_ROOT.child_folder('content'), s)
@@ -83,6 +91,7 @@ def test_node_relative_path():
c = r.add_node(TEST_SITE_ROOT.child_folder('content/blog/2010/december'))
assert c.relative_path == 'blog/2010/december'


def test_load():
s = Site(TEST_SITE_ROOT)
s.load()
@@ -96,6 +105,7 @@ def test_load():
assert resource.relative_path == path
assert not s.content.resource_from_relative_path('/happy-festivus.html')


def test_walk_resources():
s = Site(TEST_SITE_ROOT)
s.load()
@@ -113,6 +123,7 @@ def test_walk_resources():
expected.sort()
assert pages == expected


def test_contains_resource():
s = Site(TEST_SITE_ROOT)
s.load()
@@ -120,13 +131,16 @@ def test_contains_resource():
node = s.content.node_from_relative_path(path)
assert node.contains_resource('merry-christmas.html')


def test_get_resource():
s = Site(TEST_SITE_ROOT)
s.load()
path = 'blog/2010/december'
node = s.content.node_from_relative_path(path)
resource = node.get_resource('merry-christmas.html')
assert resource == s.content.resource_from_relative_path(Folder(path).child('merry-christmas.html'))
assert resource == s.content.resource_from_relative_path(
Folder(path).child('merry-christmas.html'))


def test_resource_slug():
s = Site(TEST_SITE_ROOT)
@@ -143,9 +157,12 @@ def test_get_resource_from_relative_deploy_path():
path = 'blog/2010/december'
node = s.content.node_from_relative_path(path)
resource = node.get_resource('merry-christmas.html')
assert resource == s.content.resource_from_relative_deploy_path(Folder(path).child('merry-christmas.html'))
assert resource == s.content.resource_from_relative_deploy_path(
Folder(path).child('merry-christmas.html'))
resource.relative_deploy_path = Folder(path).child('merry-christmas.php')
assert resource == s.content.resource_from_relative_deploy_path(Folder(path).child('merry-christmas.php'))
assert resource == s.content.resource_from_relative_deploy_path(
Folder(path).child('merry-christmas.php'))


def test_is_processable_default_true():
s = Site(TEST_SITE_ROOT)
@@ -153,6 +170,7 @@ def test_is_processable_default_true():
for page in s.content.walk_resources():
assert page.is_processable


def test_relative_deploy_path():
s = Site(TEST_SITE_ROOT)
s.load()
@@ -160,28 +178,35 @@ def test_relative_deploy_path():
assert page.relative_deploy_path == Folder(page.relative_path)
assert page.url == '/' + page.relative_deploy_path


def test_relative_deploy_path_override():
s = Site(TEST_SITE_ROOT)
s.load()
res = s.content.resource_from_relative_path('blog/2010/december/merry-christmas.html')
res = s.content.resource_from_relative_path(
'blog/2010/december/merry-christmas.html')
res.relative_deploy_path = 'blog/2010/december/happy-holidays.html'
for page in s.content.walk_resources():
if res.source_file == page.source_file:
assert page.relative_deploy_path == 'blog/2010/december/happy-holidays.html'
assert (page.relative_deploy_path ==
'blog/2010/december/happy-holidays.html')
else:
assert page.relative_deploy_path == Folder(page.relative_path)


class TestSiteWithConfig(object):

@classmethod
def setup_class(cls):
cls.SITE_PATH = File(__file__).parent.child_folder('sites/test_jinja_with_config')
cls.SITE_PATH = File(__file__).parent.child_folder(
'sites/test_jinja_with_config')
cls.SITE_PATH.make()
TEST_SITE_ROOT.copy_contents_to(cls.SITE_PATH)
cls.config_file = File(cls.SITE_PATH.child('alternate.yaml'))
with open(cls.config_file.path) as config:
cls.config = Config(sitepath=cls.SITE_PATH, config_dict=yaml.load(config))
cls.SITE_PATH.child_folder('content').rename_to(cls.config.content_root)
cls.config = Config(
sitepath=cls.SITE_PATH, config_dict=yaml.load(config))
cls.SITE_PATH.child_folder('content').rename_to(
cls.config.content_root)

@classmethod
def teardown_class(cls):
@@ -198,7 +223,8 @@ class TestSiteWithConfig(object):
resource = s.content.resource_from_relative_path(path)
assert resource
assert resource.relative_path == path
assert not s.content.resource_from_relative_path('/happy-festivus.html')
assert not s.content.resource_from_relative_path(
'/happy-festivus.html')

def test_content_url(self):
s = Site(self.SITE_PATH, config=self.config)
@@ -217,7 +243,7 @@ class TestSiteWithConfig(object):
s.load()
path = '".jpg/abc'
print s.content_url(path, "")
print "/" + quote(path, "")
print "/" + quote(path, "")
assert s.content_url(path, "") == "/" + quote(path, "")

def test_media_url(self):
@@ -254,7 +280,7 @@ class TestSiteWithConfig(object):
s.load()
path = 'css/site.css'
resource = s.content.resource_from_relative_path(
Folder("media").child(path))
Folder("media").child(path))
assert resource
assert resource.full_url == "/media/" + path

@@ -265,7 +291,7 @@ class TestSiteWithConfig(object):
path = 'apple-touch-icon.png'
resource = s.content.resource_from_relative_path(path)
assert resource
assert resource.full_url == "/" + path
assert resource.full_url == "/" + path
s = Site(self.SITE_PATH, config=c)
s.config.ignore.append('*.png')
resource = s.content.resource_from_relative_path(path)
@@ -281,10 +307,10 @@ class TestSiteWithConfig(object):
assert not git_node
blog_node = s.content.node_from_relative_path('blog')
assert blog_node
assert blog_node.full_url == "/blog"
assert blog_node.full_url == "/blog"
s = Site(self.SITE_PATH, config=c)
s.config.ignore.append('blog')
blog_node = s.content.node_from_relative_path('blog')
assert not blog_node
git_node = s.content.node_from_relative_path('.git')
assert not git_node
assert not git_node

+ 7
- 3
hyde/tests/util.py View File

@@ -1,6 +1,7 @@
import re
import difflib


def strip_spaces_between_tags(value):
"""
Stolen from `django.util.html`
@@ -8,10 +9,11 @@ def strip_spaces_between_tags(value):
"""
return re.sub(r'>\s+<', '><', unicode(value))


def assert_no_diff(expected, out):
diff = [l for l in difflib.unified_diff(expected.splitlines(True),
out.splitlines(True),
n=3)]
out.splitlines(True),
n=3)]
assert not diff, ''.join(diff)


@@ -23,6 +25,7 @@ def assert_html_equals(expected, actual, sanitize=None):
actual = sanitize(actual)
assert expected == actual


def trap_exit_fail(f):
def test_wrapper(*args):
try:
@@ -32,6 +35,7 @@ def trap_exit_fail(f):
test_wrapper.__name__ = f.__name__
return test_wrapper


def trap_exit_pass(f):
def test_wrapper(*args):
try:
@@ -39,4 +43,4 @@ def trap_exit_pass(f):
except SystemExit:
pass
test_wrapper.__name__ = f.__name__
return test_wrapper
return test_wrapper

+ 1
- 1
hyde/util.py View File

@@ -54,4 +54,4 @@ def discover_executable(name, sitepath):
full_name = os.path.join(path, name)
if os.path.exists(full_name):
return full_name
return None
return None

Loading…
Cancel
Save