flake8 in integrationmain
| @@ -22,5 +22,7 @@ before_script: | |||
| - export PYTHONPATH=$PYTHONPATH:/usr/share/asciidoc/ | |||
| script: | |||
| # Source code sanity check | |||
| - flake8 hyde | |||
| # Run Python tests and generate coverage statistics | |||
| - nosetests | |||
| @@ -5,3 +5,4 @@ mock==1.0.1 | |||
| nose==1.3.6 | |||
| Pillow==2.7.0 | |||
| pyScss==1.3.4 | |||
| flake8==2.4.1 | |||
| @@ -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 | |||
| @@ -132,7 +132,8 @@ setup(name=PROJECT, | |||
| 'pyquery==1.2.9', | |||
| 'docutils==0.12', | |||
| 'Pillow==2.7.0', | |||
| 'pyScss==1.3.4' | |||
| 'pyScss==1.3.4', | |||
| 'flake8==2.4.1' | |||
| ), | |||
| test_suite='nose.collector', | |||
| include_package_data = True, | |||