@@ -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,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 | |||
@@ -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')) |
@@ -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) |
@@ -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)) |
@@ -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)) | |||
@@ -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 | |||
""" | |||
@@ -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)) |
@@ -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) | |||
@@ -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) | |||
@@ -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 | |||
@@ -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 | |||
@@ -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: | |||
@@ -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: | |||
@@ -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) |
@@ -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() | |||
@@ -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() | |||
@@ -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 | |||
@@ -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): | |||
@@ -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: | |||
@@ -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 | |||
@@ -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')] | |||
@@ -5,9 +5,10 @@ The hyde executable | |||
""" | |||
from hyde.engine import Engine | |||
def main(): | |||
"""Main""" | |||
Engine().run() | |||
if __name__ == "__main__": | |||
main() | |||
main() |
@@ -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): | |||
""" | |||
@@ -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,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) |
@@ -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()) |
@@ -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) |
@@ -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 | |||
@@ -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 | |||
@@ -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) | |||
@@ -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() | |||
@@ -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 |
@@ -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 |
@@ -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 == '/' | |||
@@ -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) |
@@ -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 |
@@ -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) | |||
@@ -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""" | |||
=== | |||
@@ -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(): | |||
@@ -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 | |||
@@ -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') | |||
@@ -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) | |||
@@ -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) | |||
@@ -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)) |
@@ -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() |
@@ -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~~~~~~~ | |||
@@ -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")) |
@@ -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 |
@@ -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,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' |
@@ -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 |
@@ -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 | |||
@@ -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'])) | |||
@@ -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">&</span> 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() |
@@ -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() | |||
@@ -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() | |||
@@ -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"] |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |