@@ -32,10 +32,11 @@ class Engine(Application): | |||||
) | ) | ||||
@command(description='hyde - a python static website generator', | @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('-v', '--verbose', help="Show detailed information in console") | ||||
@true('-x', '--raise-exceptions', default=None, | @true('-x', '--raise-exceptions', default=None, | ||||
help="Don't handle exceptions.") | |||||
help="Don't handle exceptions.") | |||||
@version('--version', version='%(prog)s ' + __version__) | @version('--version', version='%(prog)s ' + __version__) | ||||
@store('-s', '--sitepath', default='.', help="Location of the hyde site") | @store('-s', '--sitepath', default='.', help="Location of the hyde site") | ||||
def main(self, args): | def main(self, args): | ||||
@@ -52,7 +53,7 @@ class Engine(Application): | |||||
@subcommand('create', help='Create a new hyde site.') | @subcommand('create', help='Create a new hyde site.') | ||||
@store('-l', '--layout', default='basic', help='Layout for the new site') | @store('-l', '--layout', default='basic', help='Layout for the new site') | ||||
@true('-f', '--force', default=False, dest='overwrite', | @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): | def create(self, args): | ||||
""" | """ | ||||
The create command. Creates a new site from the template at the given | 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: | if exists and not args.overwrite: | ||||
raise HydeException( | 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) | layout = Layout.find_layout(args.layout) | ||||
self.logger.info( | self.logger.info( | ||||
"Creating site at [%s] with layout [%s]" % (sitepath, layout)) | "Creating site at [%s] with layout [%s]" % (sitepath, layout)) | ||||
if not layout or not layout.exists: | if not layout or not layout.exists: | ||||
raise HydeException( | 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) | layout.copy_contents_to(args.sitepath) | ||||
self.logger.info("Site creation complete") | self.logger.info("Site creation complete") | ||||
@subcommand('gen', help='Generate the site') | @subcommand('gen', help='Generate the site') | ||||
@store('-c', '--config-path', default='site.yaml', dest='config', | @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, | @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, | @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): | def gen(self, args): | ||||
""" | """ | ||||
The generate command. Generates the site at the given | The generate command. Generates the site at the given | ||||
@@ -103,13 +104,13 @@ class Engine(Application): | |||||
@subcommand('serve', help='Serve the website') | @subcommand('serve', help='Serve the website') | ||||
@store('-a', '--address', default='localhost', dest='address', | @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', | @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', | @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, | @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): | def serve(self, args): | ||||
""" | """ | ||||
The serve command. Serves the site at the given | 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) | site = self.make_site(sitepath, args.config, args.deploy) | ||||
from hyde.server import HydeWebServer | from hyde.server import HydeWebServer | ||||
server = HydeWebServer(site, args.address, args.port) | 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: | try: | ||||
server.serve_forever() | server.serve_forever() | ||||
except (KeyboardInterrupt, SystemExit): | except (KeyboardInterrupt, SystemExit): | ||||
@@ -131,11 +133,11 @@ class Engine(Application): | |||||
@subcommand('publish', help='Publish the website') | @subcommand('publish', help='Publish the website') | ||||
@store('-c', '--config-path', default='site.yaml', dest='config', | @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', | @store('-p', '--publisher', dest='publisher', default='default', | ||||
help='Points to the publisher configuration.') | |||||
help='Points to the publisher configuration.') | |||||
@store('-m', '--message', dest='message', | @store('-m', '--message', dest='message', | ||||
help='Optional message.') | |||||
help='Optional message.') | |||||
def publish(self, args): | def publish(self, args): | ||||
""" | """ | ||||
Publishes the site based on the configuration from the `target` | Publishes the site based on the configuration from the `target` | ||||
@@ -145,11 +147,10 @@ class Engine(Application): | |||||
site = self.make_site(sitepath, args.config) | site = self.make_site(sitepath, args.config) | ||||
from hyde.publisher import Publisher | from hyde.publisher import Publisher | ||||
publisher = Publisher.load_publisher(site, | publisher = Publisher.load_publisher(site, | ||||
args.publisher, | |||||
args.message) | |||||
args.publisher, | |||||
args.message) | |||||
publisher.publish() | publisher.publish() | ||||
def make_site(self, sitepath, config, deploy=None): | def make_site(self, sitepath, config, deploy=None): | ||||
""" | """ | ||||
Creates a site object from the given sitepath and the config file. | 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) | config = Config(sitepath, config_file=config) | ||||
if deploy: | if deploy: | ||||
config.deploy_root = deploy | config.deploy_root = deploy | ||||
return Site(sitepath, config) | |||||
return Site(sitepath, config) |
@@ -1,4 +1,5 @@ | |||||
class HydeException(Exception): | class HydeException(Exception): | ||||
""" | """ | ||||
Base class for exceptions from hyde | Base class for exceptions from hyde | ||||
""" | """ | ||||
@@ -7,5 +8,3 @@ class HydeException(Exception): | |||||
def reraise(message, exc_info): | def reraise(message, exc_info): | ||||
_, _, tb = exc_info | _, _, tb = exc_info | ||||
raise HydeException(message), None, tb | raise HydeException(message), None, tb | ||||
@@ -10,13 +10,12 @@ from hyde.plugin import Plugin | |||||
class DraftsPlugin(Plugin): | class DraftsPlugin(Plugin): | ||||
def begin_site(self): | def begin_site(self): | ||||
in_production = self.site.config.mode.startswith('prod') | in_production = self.site.config.mode.startswith('prod') | ||||
if not in_production: | 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 | return | ||||
for resource in self.site.content.walk_resources(): | for resource in self.site.content.walk_resources(): | ||||
@@ -33,4 +32,4 @@ class DraftsPlugin(Plugin): | |||||
self.logger.info( | self.logger.info( | ||||
'%s is%s draft' % (resource, | '%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 | # Less CSS | ||||
# | # | ||||
class LessCSSPlugin(CLTransformer): | class LessCSSPlugin(CLTransformer): | ||||
""" | """ | ||||
The plugin class for less css | The plugin class for less css | ||||
""" | """ | ||||
@@ -29,7 +31,6 @@ class LessCSSPlugin(CLTransformer): | |||||
re.compile('^\\s*@import\s+(?:\'|\")([^\'\"]*)(?:\'|\")\s*\;\s*$', | re.compile('^\\s*@import\s+(?:\'|\")([^\'\"]*)(?:\'|\")\s*\;\s*$', | ||||
re.MULTILINE) | re.MULTILINE) | ||||
@property | @property | ||||
def executable_name(self): | def executable_name(self): | ||||
return "lessc" | return "lessc" | ||||
@@ -39,7 +40,7 @@ class LessCSSPlugin(CLTransformer): | |||||
Check user defined | Check user defined | ||||
""" | """ | ||||
return resource.source_file.kind == 'less' and \ | 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): | def _should_replace_imports(self, resource): | ||||
return getattr(resource, 'meta', {}).get('uses_template', True) | return getattr(resource, 'meta', {}).get('uses_template', True) | ||||
@@ -72,13 +73,13 @@ class LessCSSPlugin(CLTransformer): | |||||
afile = File(afile.path + '.less') | afile = File(afile.path + '.less') | ||||
ref = self.site.content.resource_from_path(afile.path) | ref = self.site.content.resource_from_path(afile.path) | ||||
if not ref: | 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 | ref.is_processable = False | ||||
return self.template.get_include_statement(ref.relative_path) | return self.template.get_include_statement(ref.relative_path) | ||||
text = self.import_finder.sub(import_to_include, text) | text = self.import_finder.sub(import_to_include, text) | ||||
return text | return text | ||||
@property | @property | ||||
def plugin_name(self): | def plugin_name(self): | ||||
""" | """ | ||||
@@ -114,10 +115,10 @@ class LessCSSPlugin(CLTransformer): | |||||
try: | try: | ||||
self.call_app(args) | self.call_app(args) | ||||
except subprocess.CalledProcessError: | 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() | return target.read_all() | ||||
@@ -127,6 +128,7 @@ class LessCSSPlugin(CLTransformer): | |||||
# | # | ||||
class StylusPlugin(CLTransformer): | class StylusPlugin(CLTransformer): | ||||
""" | """ | ||||
The plugin class for stylus css | The plugin class for stylus css | ||||
""" | """ | ||||
@@ -162,7 +164,8 @@ class StylusPlugin(CLTransformer): | |||||
if not match.lastindex: | if not match.lastindex: | ||||
return '' | return '' | ||||
path = match.groups(1)[0] | 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: | if len(afile.kind.strip()) == 0: | ||||
afile = File(afile.path + '.styl') | afile = File(afile.path + '.styl') | ||||
@@ -179,8 +182,8 @@ class StylusPlugin(CLTransformer): | |||||
else: | else: | ||||
ref.is_processable = False | ref.is_processable = False | ||||
return "\n" + \ | return "\n" + \ | ||||
self.template.get_include_statement(ref.relative_path) + \ | |||||
"\n" | |||||
self.template.get_include_statement(ref.relative_path) + \ | |||||
"\n" | |||||
return '@import "' + path + '"\n' | return '@import "' + path + '"\n' | ||||
text = self.import_finder.sub(import_to_include, text) | text = self.import_finder.sub(import_to_include, text) | ||||
@@ -196,7 +199,7 @@ class StylusPlugin(CLTransformer): | |||||
except AttributeError: | except AttributeError: | ||||
mode = "production" | mode = "production" | ||||
defaults = {"compress":""} | |||||
defaults = {"compress": ""} | |||||
if mode.startswith('dev'): | if mode.startswith('dev'): | ||||
defaults = {} | defaults = {} | ||||
return defaults | return defaults | ||||
@@ -227,9 +230,9 @@ class StylusPlugin(CLTransformer): | |||||
self.call_app(args) | self.call_app(args) | ||||
except subprocess.CalledProcessError: | except subprocess.CalledProcessError: | ||||
HydeException.reraise( | 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') | target = File(source.path + '.css') | ||||
return target.read_all() | return target.read_all() | ||||
@@ -239,6 +242,7 @@ class StylusPlugin(CLTransformer): | |||||
# | # | ||||
class CleverCSSPlugin(Plugin): | class CleverCSSPlugin(Plugin): | ||||
""" | """ | ||||
The plugin class for CleverCSS | The plugin class for CleverCSS | ||||
""" | """ | ||||
@@ -257,7 +261,7 @@ class CleverCSSPlugin(Plugin): | |||||
Check user defined | Check user defined | ||||
""" | """ | ||||
return resource.source_file.kind == 'ccss' and \ | 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): | def _should_replace_imports(self, resource): | ||||
return getattr(resource, 'meta', {}).get('uses_template', True) | return getattr(resource, 'meta', {}).get('uses_template', True) | ||||
@@ -282,8 +286,8 @@ class CleverCSSPlugin(Plugin): | |||||
return text | return text | ||||
import_finder = re.compile( | import_finder = re.compile( | ||||
'^\\s*@import\s+(?:\'|\")([^\'\"]*)(?:\'|\")\s*\;\s*$', | |||||
re.MULTILINE) | |||||
'^\\s*@import\s+(?:\'|\")([^\'\"]*)(?:\'|\")\s*\;\s*$', | |||||
re.MULTILINE) | |||||
def import_to_include(match): | def import_to_include(match): | ||||
if not match.lastindex: | if not match.lastindex: | ||||
@@ -294,7 +298,8 @@ class CleverCSSPlugin(Plugin): | |||||
afile = File(afile.path + '.ccss') | afile = File(afile.path + '.ccss') | ||||
ref = self.site.content.resource_from_path(afile.path) | ref = self.site.content.resource_from_path(afile.path) | ||||
if not ref: | 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 | ref.is_processable = False | ||||
return self.template.get_include_statement(ref.relative_path) | return self.template.get_include_statement(ref.relative_path) | ||||
text = import_finder.sub(import_to_include, text) | text = import_finder.sub(import_to_include, text) | ||||
@@ -313,7 +318,9 @@ class CleverCSSPlugin(Plugin): | |||||
# Sassy CSS | # Sassy CSS | ||||
# | # | ||||
class SassyCSSPlugin(Plugin): | class SassyCSSPlugin(Plugin): | ||||
""" | """ | ||||
The plugin class for SassyCSS | The plugin class for SassyCSS | ||||
""" | """ | ||||
@@ -332,7 +339,7 @@ class SassyCSSPlugin(Plugin): | |||||
Check user defined | Check user defined | ||||
""" | """ | ||||
return resource.source_file.kind == 'scss' and \ | return resource.source_file.kind == 'scss' and \ | ||||
getattr(resource, 'meta', {}).get('parse', True) | |||||
getattr(resource, 'meta', {}).get('parse', True) | |||||
@property | @property | ||||
def options(self): | def options(self): | ||||
@@ -364,7 +371,6 @@ class SassyCSSPlugin(Plugin): | |||||
""" | """ | ||||
return self.settings.get('includes', []) | return self.settings.get('includes', []) | ||||
def begin_site(self): | def begin_site(self): | ||||
""" | """ | ||||
Find all the sassycss files and set their relative deploy path. | 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.STATIC_ROOT = self.site.config.content_root_path.path | ||||
self.scss.ASSETS_URL = self.site.media_url('/') | self.scss.ASSETS_URL = self.site.media_url('/') | ||||
self.scss.ASSETS_ROOT = self.site.config.deploy_root_path.child( | 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(): | for resource in self.site.content.walk_resources(): | ||||
if self._should_parse_resource(resource): | if self._should_parse_resource(resource): | ||||
@@ -391,8 +397,8 @@ class SassyCSSPlugin(Plugin): | |||||
includes = [resource.node.path] + self.includes | includes = [resource.node.path] + self.includes | ||||
includes = [path.rstrip(os.sep) + os.sep for path in includes] | includes = [path.rstrip(os.sep) + os.sep for path in includes] | ||||
options = self.options | options = self.options | ||||
if not 'load_paths' in options: | |||||
if 'load_paths' not in options: | |||||
options['load_paths'] = [] | options['load_paths'] = [] | ||||
options['load_paths'].extend(includes) | 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) | return scss.compile(text) |
@@ -7,7 +7,9 @@ Depends plugin | |||||
from hyde.plugin import Plugin | from hyde.plugin import Plugin | ||||
class DependsPlugin(Plugin): | class DependsPlugin(Plugin): | ||||
""" | """ | ||||
The plugin class setting explicit dependencies. | The plugin class setting explicit dependencies. | ||||
""" | """ | ||||
@@ -16,15 +18,14 @@ class DependsPlugin(Plugin): | |||||
super(DependsPlugin, self).__init__(site) | super(DependsPlugin, self).__init__(site) | ||||
def begin_site(self): | 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): | def _update_resource(self, resource): | ||||
""" | """ | ||||
@@ -53,7 +54,7 @@ class DependsPlugin(Plugin): | |||||
for dep in depends: | for dep in depends: | ||||
resource.depends.append(dep.format(node=resource.node, | 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)) | resource.depends = list(set(resource.depends)) |
@@ -39,6 +39,7 @@ class PILPlugin(Plugin): | |||||
class ImageSizerPlugin(PILPlugin): | class ImageSizerPlugin(PILPlugin): | ||||
""" | """ | ||||
Each HTML page is modified to add width and height for images if | Each HTML page is modified to add width and height for images if | ||||
they are not already specified. | they are not already specified. | ||||
@@ -50,26 +51,28 @@ class ImageSizerPlugin(PILPlugin): | |||||
super(ImageSizerPlugin, self).__init__(site) | super(ImageSizerPlugin, self).__init__(site) | ||||
self.cache = {} | self.cache = {} | ||||
def _handle_img(self, resource, src, width, height): | def _handle_img(self, resource, src, width, height): | ||||
"""Determine what should be added to an img tag""" | """Determine what should be added to an img tag""" | ||||
if height is not None and width is not None: | if height is not None and width is not None: | ||||
return "" # Nothing | return "" # Nothing | ||||
if src is None: | 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 | return "" # Nothing | ||||
if src not in self.cache: | if src not in self.cache: | ||||
if src.startswith(self.site.config.media_url): | if src.startswith(self.site.config.media_url): | ||||
path = src[len(self.site.config.media_url):].lstrip("/") | path = src[len(self.site.config.media_url):].lstrip("/") | ||||
path = self.site.config.media_root_path.child(path) | 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): | elif re.match(r'([a-z]+://|//).*', src): | ||||
# Not a local link | # Not a local link | ||||
return "" # Nothing | return "" # Nothing | ||||
elif src.startswith("/"): | elif src.startswith("/"): | ||||
# Absolute resource | # Absolute resource | ||||
path = src.lstrip("/") | path = src.lstrip("/") | ||||
image = self.site.content.resource_from_relative_deploy_path(path) | |||||
image = self.site.content.resource_from_relative_deploy_path( | |||||
path) | |||||
else: | else: | ||||
# Relative resource | # Relative resource | ||||
path = resource.node.source_folder.child(src) | path = resource.node.source_folder.child(src) | ||||
@@ -80,7 +83,7 @@ class ImageSizerPlugin(PILPlugin): | |||||
return "" # Nothing | return "" # Nothing | ||||
if image.source_file.kind not in ['png', 'jpg', 'jpeg', 'gif']: | if image.source_file.kind not in ['png', 'jpg', 'jpeg', 'gif']: | ||||
self.logger.warn( | 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 | return "" # Nothing | ||||
# Now, get the size of the image | # Now, get the size of the image | ||||
try: | try: | ||||
@@ -96,9 +99,9 @@ class ImageSizerPlugin(PILPlugin): | |||||
if new_width is None or new_height is None: | if new_width is None or new_height is None: | ||||
return "" # Nothing | return "" # Nothing | ||||
if width is not None: | 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: | 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) | return 'height="%d" width="%d" ' % (new_height, new_width) | ||||
def text_resource_complete(self, resource, text): | def text_resource_complete(self, resource, text): | ||||
@@ -151,7 +154,7 @@ class ImageSizerPlugin(PILPlugin): | |||||
continue | continue | ||||
attr = None | attr = None | ||||
for tag in tags: | for tag in tags: | ||||
if text[pos:(pos+len(tag)+1)] == ("%s=" % tag): | |||||
if text[pos:(pos + len(tag) + 1)] == ("%s=" % tag): | |||||
attr = tag | attr = tag | ||||
pos = pos + len(tag) + 1 | pos = pos + len(tag) + 1 | ||||
break | break | ||||
@@ -177,12 +180,13 @@ class ImageSizerPlugin(PILPlugin): | |||||
return text | return text | ||||
def scale_aspect(a, b1, b2): | def scale_aspect(a, b1, b2): | ||||
from math import ceil | |||||
""" | |||||
from math import ceil | |||||
""" | |||||
Scales a by b2/b1 rounding up to nearest integer | 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): | 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) | width = scale_aspect(orig_width, orig_height, height) | ||||
elif height is None: | elif height is None: | ||||
height = scale_aspect(orig_height, orig_width, width) | 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) | width = scale_aspect(orig_width, orig_height, height) | ||||
else: | else: | ||||
height = scale_aspect(orig_height, orig_width, width) | height = scale_aspect(orig_height, orig_width, width) | ||||
@@ -208,7 +212,9 @@ def thumb_scale_size(orig_width, orig_height, width, height): | |||||
# Image Thumbnails | # Image Thumbnails | ||||
# | # | ||||
class ImageThumbnailsPlugin(PILPlugin): | class ImageThumbnailsPlugin(PILPlugin): | ||||
""" | """ | ||||
Provide a function to get thumbnail for any image resource. | Provide a function to get thumbnail for any image resource. | ||||
@@ -239,11 +245,11 @@ class ImageThumbnailsPlugin(PILPlugin): | |||||
prefix: thumbs4_ | prefix: thumbs4_ | ||||
include: | include: | ||||
- '*.jpg' | - '*.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 | If larger/smaller are specified, then the orientation (i.e., landscape or | ||||
portrait) is preserved while thumbnailing. | portrait) is preserved while thumbnailing. | ||||
@@ -256,7 +262,8 @@ class ImageThumbnailsPlugin(PILPlugin): | |||||
def __init__(self, site): | def __init__(self, site): | ||||
super(ImageThumbnailsPlugin, self).__init__(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 | Generate a thumbnail for the given image | ||||
""" | """ | ||||
@@ -268,14 +275,17 @@ class ImageThumbnailsPlugin(PILPlugin): | |||||
# for simple maintenance but keep original deploy path to preserve | # for simple maintenance but keep original deploy path to preserve | ||||
# naming logic in generated site | # naming logic in generated site | ||||
path = os.path.join(".thumbnails", | 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)) | "%s%s" % (prefix, name)) | ||||
target = resource.site.config.content_root_path.child_file(path) | target = resource.site.config.content_root_path.child_file(path) | ||||
res = self.site.content.add_resource(target) | 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() | 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 | return | ||||
self.logger.debug("Making thumbnail for [%s]" % resource) | self.logger.debug("Making thumbnail for [%s]" % resource) | ||||
@@ -285,17 +295,18 @@ class ImageThumbnailsPlugin(PILPlugin): | |||||
format = im.format | format = im.format | ||||
if preserve_orientation and im.size[1] > im.size[0]: | 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)) | self.logger.debug("Resize to: %d,%d" % (resize_width, resize_height)) | ||||
im = im.resize((resize_width, resize_height), self.Image.ANTIALIAS) | im = im.resize((resize_width, resize_height), self.Image.ANTIALIAS) | ||||
if width is not None and height is not None: | if width is not None and height is not None: | ||||
shiftx = shifty = 0 | shiftx = shifty = 0 | ||||
if crop_type == "center": | 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": | elif crop_type == "bottomright": | ||||
shiftx = (im.size[0] - width) | shiftx = (im.size[0] - width) | ||||
shifty = (im.size[1] - height) | shifty = (im.size[1] - height) | ||||
@@ -304,22 +315,23 @@ class ImageThumbnailsPlugin(PILPlugin): | |||||
options = dict(optimize=True) | options = dict(optimize=True) | ||||
if format == "JPEG": | if format == "JPEG": | ||||
options['quality'] = 75 | |||||
options['quality'] = 75 | |||||
im.save(target.path, **options) | im.save(target.path, **options) | ||||
def begin_site(self): | 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 | # Grab default values from config | ||||
config = self.site.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'): | if hasattr(config, 'thumbnails'): | ||||
defaults.update(config.thumbnails) | defaults.update(config.thumbnails) | ||||
@@ -327,45 +339,64 @@ class ImageThumbnailsPlugin(PILPlugin): | |||||
if hasattr(node, 'meta') and hasattr(node.meta, 'thumbnails'): | if hasattr(node, 'meta') and hasattr(node.meta, 'thumbnails'): | ||||
for th in node.meta.thumbnails: | for th in node.meta.thumbnails: | ||||
if not hasattr(th, 'include'): | 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 | continue | ||||
include = th.include | 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"]: | 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 | 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 | continue | ||||
if ((larger is not None or smaller is not None) and | 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 | continue | ||||
if larger is None and smaller is None: | if larger is None and smaller is None: | ||||
preserve_orientation = False | |||||
dim1, dim2 = width, height | |||||
preserve_orientation = False | |||||
dim1, dim2 = width, height | |||||
else: | 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: | for resource in node.resources: | ||||
if match_includes(resource.path): | 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 | # JPEG Optimization | ||||
# | # | ||||
class JPEGOptimPlugin(CLTransformer): | class JPEGOptimPlugin(CLTransformer): | ||||
""" | """ | ||||
The plugin class for JPEGOptim | The plugin class for JPEGOptim | ||||
""" | """ | ||||
@@ -408,7 +439,7 @@ class JPEGOptimPlugin(CLTransformer): | |||||
"strip-icc", | "strip-icc", | ||||
] | ] | ||||
target = File(self.site.config.deploy_root_path.child( | target = File(self.site.config.deploy_root_path.child( | ||||
resource.relative_deploy_path)) | |||||
resource.relative_deploy_path)) | |||||
jpegoptim = self.app | jpegoptim = self.app | ||||
args = [unicode(jpegoptim)] | args = [unicode(jpegoptim)] | ||||
args.extend(self.process_args(supported)) | args.extend(self.process_args(supported)) | ||||
@@ -417,6 +448,7 @@ class JPEGOptimPlugin(CLTransformer): | |||||
class JPEGTranPlugin(CLTransformer): | class JPEGTranPlugin(CLTransformer): | ||||
""" | """ | ||||
Almost like jpegoptim except it uses jpegtran. jpegtran allows to make | Almost like jpegoptim except it uses jpegtran. jpegtran allows to make | ||||
progressive JPEG. Unfortunately, it only does lossless compression. If | progressive JPEG. Unfortunately, it only does lossless compression. If | ||||
@@ -463,7 +495,7 @@ class JPEGTranPlugin(CLTransformer): | |||||
"copy", | "copy", | ||||
] | ] | ||||
source = File(self.site.config.deploy_root_path.child( | source = File(self.site.config.deploy_root_path.child( | ||||
resource.relative_deploy_path)) | |||||
resource.relative_deploy_path)) | |||||
target = File.make_temp('') | target = File.make_temp('') | ||||
jpegtran = self.app | jpegtran = self.app | ||||
args = [unicode(jpegtran)] | args = [unicode(jpegtran)] | ||||
@@ -474,12 +506,12 @@ class JPEGTranPlugin(CLTransformer): | |||||
target.delete() | target.delete() | ||||
# | # | ||||
# PNG Optimization | # PNG Optimization | ||||
# | # | ||||
class OptiPNGPlugin(CLTransformer): | class OptiPNGPlugin(CLTransformer): | ||||
""" | """ | ||||
The plugin class for OptiPNG | The plugin class for OptiPNG | ||||
""" | """ | ||||
@@ -535,7 +567,7 @@ class OptiPNGPlugin(CLTransformer): | |||||
"nz" | "nz" | ||||
] | ] | ||||
target = File(self.site.config.deploy_root_path.child( | target = File(self.site.config.deploy_root_path.child( | ||||
resource.relative_deploy_path)) | |||||
resource.relative_deploy_path)) | |||||
optipng = self.app | optipng = self.app | ||||
args = [unicode(optipng)] | args = [unicode(optipng)] | ||||
args.extend(self.process_args(supported)) | args.extend(self.process_args(supported)) | ||||
@@ -16,6 +16,7 @@ from fswrap import File | |||||
# | # | ||||
class UglifyPlugin(CLTransformer): | class UglifyPlugin(CLTransformer): | ||||
""" | """ | ||||
The plugin class for Uglify JS | The plugin class for Uglify JS | ||||
""" | """ | ||||
@@ -85,7 +86,9 @@ class UglifyPlugin(CLTransformer): | |||||
out = target.read_all() | out = target.read_all() | ||||
return out | return out | ||||
class RequireJSPlugin(CLTransformer): | class RequireJSPlugin(CLTransformer): | ||||
""" | """ | ||||
requirejs plugin | requirejs plugin | ||||
@@ -103,6 +106,7 @@ class RequireJSPlugin(CLTransformer): | |||||
Please see the homepage of requirejs for usage details. | Please see the homepage of requirejs for usage details. | ||||
""" | """ | ||||
def __init__(self, site): | def __init__(self, site): | ||||
super(RequireJSPlugin, self).__init__(site) | super(RequireJSPlugin, self).__init__(site) | ||||
@@ -124,20 +128,22 @@ class RequireJSPlugin(CLTransformer): | |||||
rjs = self.app | rjs = self.app | ||||
target = File.make_temp('') | target = File.make_temp('') | ||||
args = [unicode(rjs)] | args = [unicode(rjs)] | ||||
args.extend(['-o', unicode(resource), ("out=" + target.fully_expanded_path)]) | |||||
args.extend( | |||||
['-o', unicode(resource), ("out=" + target.fully_expanded_path)]) | |||||
try: | try: | ||||
self.call_app(args) | self.call_app(args) | ||||
except subprocess.CalledProcessError: | 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() | return target.read_all() | ||||
class CoffeePlugin(CLTransformer): | class CoffeePlugin(CLTransformer): | ||||
""" | """ | ||||
The plugin class for Coffeescript | The plugin class for Coffeescript | ||||
""" | """ | ||||
@@ -5,7 +5,9 @@ Contains classes to help manage multi-language pages. | |||||
from hyde.plugin import Plugin | from hyde.plugin import Plugin | ||||
class LanguagePlugin(Plugin): | class LanguagePlugin(Plugin): | ||||
""" | """ | ||||
Each page should be tagged with a language using `language` meta | Each page should be tagged with a language using `language` meta | ||||
data. Each page should also have an UUID stored in `uuid` meta | data. Each page should also have an UUID stored in `uuid` meta | ||||
@@ -36,7 +38,8 @@ class LanguagePlugin(Plugin): | |||||
def __init__(self, site): | def __init__(self, site): | ||||
super(LanguagePlugin, self).__init__(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): | def begin_site(self): | ||||
""" | """ | ||||
@@ -60,8 +63,9 @@ class LanguagePlugin(Plugin): | |||||
resource.translations = \ | resource.translations = \ | ||||
[r for r in resources | [r for r in resources | ||||
if r.meta.language != language] | 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): | class Metadata(Expando): | ||||
""" | """ | ||||
Container class for yaml meta data. | Container class for yaml meta data. | ||||
""" | """ | ||||
@@ -49,6 +50,7 @@ class Metadata(Expando): | |||||
class MetaPlugin(Plugin): | class MetaPlugin(Plugin): | ||||
""" | """ | ||||
Metadata plugin for hyde. Loads meta data in the following order: | Metadata plugin for hyde. Loads meta data in the following order: | ||||
@@ -66,8 +68,8 @@ class MetaPlugin(Plugin): | |||||
def __init__(self, site): | def __init__(self, site): | ||||
super(MetaPlugin, self).__init__(site) | super(MetaPlugin, self).__init__(site) | ||||
self.yaml_finder = re.compile( | 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): | def begin_site(self): | ||||
""" | """ | ||||
@@ -88,7 +90,8 @@ class MetaPlugin(Plugin): | |||||
if not hasattr(resource, 'meta'): | if not hasattr(resource, 'meta'): | ||||
resource.meta = Metadata({}, node.meta) | resource.meta = Metadata({}, node.meta) | ||||
if resource.source_file.is_text and not resource.simple_copy: | 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): | def __read_resource__(self, resource, text): | ||||
""" | """ | ||||
@@ -96,7 +99,8 @@ class MetaPlugin(Plugin): | |||||
the resource. Load meta data by looking for the marker. | the resource. Load meta data by looking for the marker. | ||||
Once loaded, remove the meta area from the text. | 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) | match = re.match(self.yaml_finder, text) | ||||
if not match: | if not match: | ||||
self.logger.debug("No metadata found in resource [%s]" % resource) | self.logger.debug("No metadata found in resource [%s]" % resource) | ||||
@@ -113,7 +117,7 @@ class MetaPlugin(Plugin): | |||||
resource.meta.update(data) | resource.meta.update(data) | ||||
self.__update_standard_attributes__(resource) | self.__update_standard_attributes__(resource) | ||||
self.logger.debug("Successfully loaded metadata from resource [%s]" | self.logger.debug("Successfully loaded metadata from resource [%s]" | ||||
% resource) | |||||
% resource) | |||||
return text or ' ' | return text or ' ' | ||||
def __update_standard_attributes__(self, obj): | def __update_standard_attributes__(self, obj): | ||||
@@ -165,6 +169,7 @@ class MetaPlugin(Plugin): | |||||
# | # | ||||
class AutoExtendPlugin(Plugin): | class AutoExtendPlugin(Plugin): | ||||
""" | """ | ||||
The plugin class for extending templates using metadata. | The plugin class for extending templates using metadata. | ||||
""" | """ | ||||
@@ -204,9 +209,9 @@ class AutoExtendPlugin(Plugin): | |||||
extended_text += '\n' | extended_text += '\n' | ||||
if block: | if block: | ||||
extended_text += ('%s\n%s\n%s' % | 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: | else: | ||||
extended_text += text | extended_text += text | ||||
return extended_text | return extended_text | ||||
@@ -218,6 +223,7 @@ class AutoExtendPlugin(Plugin): | |||||
# | # | ||||
class Tag(Expando): | class Tag(Expando): | ||||
""" | """ | ||||
A simple object that represents a tag. | A simple object that represents a tag. | ||||
""" | """ | ||||
@@ -255,6 +261,7 @@ def get_tagger_sort_method(site): | |||||
sys.exc_info()) | sys.exc_info()) | ||||
return walker | return walker | ||||
def walk_resources_tagged_with(node, tag): | def walk_resources_tagged_with(node, tag): | ||||
tags = set(unicode(tag).split('+')) | tags = set(unicode(tag).split('+')) | ||||
walker = get_tagger_sort_method(node.site) | walker = get_tagger_sort_method(node.site) | ||||
@@ -266,7 +273,9 @@ def walk_resources_tagged_with(node, tag): | |||||
if tags <= taglist: | if tags <= taglist: | ||||
yield resource | yield resource | ||||
class TaggerPlugin(Plugin): | class TaggerPlugin(Plugin): | ||||
""" | """ | ||||
Tagger plugin for hyde. Adds the ability to do tag resources and search | Tagger plugin for hyde. Adds the ability to do tag resources and search | ||||
based on the tags. | based on the tags. | ||||
@@ -286,6 +295,7 @@ class TaggerPlugin(Plugin): | |||||
target: blog/tags | target: blog/tags | ||||
archive_extension: html | archive_extension: html | ||||
""" | """ | ||||
def __init__(self, site): | def __init__(self, site): | ||||
super(TaggerPlugin, self).__init__(site) | super(TaggerPlugin, self).__init__(site) | ||||
@@ -295,11 +305,13 @@ class TaggerPlugin(Plugin): | |||||
and methods for walking tagged resources. | and methods for walking tagged resources. | ||||
""" | """ | ||||
self.logger.debug("Adding tags from metadata") | 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 = {} | tags = {} | ||||
add_method(Node, | 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) | walker = get_tagger_sort_method(self.site) | ||||
for resource in walker(): | for resource in walker(): | ||||
self._process_tags_in_resource(resource, tags) | self._process_tags_in_resource(resource, tags) | ||||
@@ -337,14 +349,14 @@ class TaggerPlugin(Plugin): | |||||
return | return | ||||
for tagname in taglist: | for tagname in taglist: | ||||
if not tagname in tags: | |||||
if tagname not in tags: | |||||
tag = Tag(tagname) | tag = Tag(tagname) | ||||
tags[tagname] = tag | tags[tagname] = tag | ||||
tag.resources.append(resource) | tag.resources.append(resource) | ||||
add_method(Node, | 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: | else: | ||||
tags[tagname].resources.append(resource) | tags[tagname].resources.append(resource) | ||||
if not hasattr(resource, 'tags'): | if not hasattr(resource, 'tags'): | ||||
@@ -367,13 +379,13 @@ class TaggerPlugin(Plugin): | |||||
for name, config in archive_config.to_dict().iteritems(): | for name, config in archive_config.to_dict().iteritems(): | ||||
self._create_tag_archive(config) | self._create_tag_archive(config) | ||||
def _create_tag_archive(self, config): | def _create_tag_archive(self, config): | ||||
""" | """ | ||||
Generates archives for each tag based on the given configuration. | 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 | content = self.site.content.source_folder | ||||
source = Folder(config.get('source', '')) | source = Folder(config.get('source', '')) | ||||
target = content.child_folder(config.get('target', 'tags')) | target = content.child_folder(config.get('target', 'tags')) | ||||
@@ -441,15 +453,17 @@ def filter_method(item, settings=None): | |||||
break | break | ||||
return all_match | return all_match | ||||
def attributes_checker(item, attributes=None): | def attributes_checker(item, attributes=None): | ||||
""" | """ | ||||
Checks if the given list of attributes exist. | Checks if the given list of attributes exist. | ||||
""" | """ | ||||
try: | try: | ||||
attrgetter(*attributes)(item) | |||||
return True | |||||
attrgetter(*attributes)(item) | |||||
return True | |||||
except AttributeError: | except AttributeError: | ||||
return False | |||||
return False | |||||
def sort_method(node, settings=None): | 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), | resources = ifilter(lambda x: excluder_(x) and filter_(x), | ||||
node.walk_resources()) | node.walk_resources()) | ||||
return sorted(resources, | return sorted(resources, | ||||
key=attrgetter(*attr), | |||||
reverse=reverse) | |||||
key=attrgetter(*attr), | |||||
reverse=reverse) | |||||
class SorterPlugin(Plugin): | class SorterPlugin(Plugin): | ||||
""" | """ | ||||
Sorter plugin for hyde. Adds the ability to do | Sorter plugin for hyde. Adds the ability to do | ||||
sophisticated sorting by expanding the site objects | sophisticated sorting by expanding the site objects | ||||
@@ -529,8 +544,8 @@ class SorterPlugin(Plugin): | |||||
setattr(Resource, next_att, None) | setattr(Resource, next_att, None) | ||||
walker = getattr(self.site.content, | 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 | first, last = None, None | ||||
for prev, next in pairwalk(walker()): | for prev, next in pairwalk(walker()): | ||||
if not first: | if not first: | ||||
@@ -555,7 +570,9 @@ class SorterPlugin(Plugin): | |||||
Grouper = namedtuple('Grouper', 'group resources') | Grouper = namedtuple('Grouper', 'group resources') | ||||
class Group(Expando): | class Group(Expando): | ||||
""" | """ | ||||
A wrapper class for groups. Adds methods for | A wrapper class for groups. Adds methods for | ||||
grouping resources. | grouping resources. | ||||
@@ -573,21 +590,21 @@ class Group(Expando): | |||||
super(Group, self).__init__(grouping) | super(Group, self).__init__(grouping) | ||||
add_method(Node, | 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, | 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, | 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, | 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): | def set_expando(self, key, value): | ||||
""" | """ | ||||
@@ -612,9 +629,9 @@ class Group(Expando): | |||||
group_name = None | group_name = None | ||||
return next((g for g in group.walk_groups() | 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 | @staticmethod | ||||
def walk_resource_groups(resource, group): | def walk_resource_groups(resource, group): | ||||
@@ -693,7 +710,9 @@ class Group(Expando): | |||||
if group_value == self.name: | if group_value == self.name: | ||||
yield resource | yield resource | ||||
class GrouperPlugin(Plugin): | class GrouperPlugin(Plugin): | ||||
""" | """ | ||||
Grouper plugin for hyde. Adds the ability to do | Grouper plugin for hyde. Adds the ability to do | ||||
group resources and nodes in an arbitrary | group resources and nodes in an arbitrary | ||||
@@ -726,6 +745,7 @@ class GrouperPlugin(Plugin): | |||||
Helpful snippets and tweaks to | Helpful snippets and tweaks to | ||||
make hyde more awesome. | make hyde more awesome. | ||||
""" | """ | ||||
def __init__(self, site): | def __init__(self, site): | ||||
super(GrouperPlugin, self).__init__(site) | super(GrouperPlugin, self).__init__(site) | ||||
@@ -748,9 +768,8 @@ class GrouperPlugin(Plugin): | |||||
setattr(Resource, next_att, None) | setattr(Resource, next_att, None) | ||||
self.site.grouper[name] = Group(grouping) | self.site.grouper[name] = Group(grouping) | ||||
walker = Group.walk_resources( | walker = Group.walk_resources( | ||||
self.site.content, self.site.grouper[name]) | |||||
self.site.content, self.site.grouper[name]) | |||||
for prev, next in pairwalk(walker): | for prev, next in pairwalk(walker): | ||||
setattr(next, prev_att, prev) | setattr(next, prev_att, prev) | ||||
setattr(prev, next_att, next) | setattr(prev, next_att, next) | ||||
@@ -63,6 +63,7 @@ except ImportError: | |||||
class SphinxPlugin(Plugin): | class SphinxPlugin(Plugin): | ||||
"""The plugin class for rendering sphinx-generated documentation.""" | """The plugin class for rendering sphinx-generated documentation.""" | ||||
def __init__(self, site): | def __init__(self, site): | ||||
@@ -93,7 +94,7 @@ class SphinxPlugin(Plugin): | |||||
else: | else: | ||||
for name in dir(user_settings): | for name in dir(user_settings): | ||||
if not name.startswith("_"): | if not name.startswith("_"): | ||||
setattr(settings,name,getattr(user_settings,name)) | |||||
setattr(settings, name, getattr(user_settings, name)) | |||||
return settings | return settings | ||||
@property | @property | ||||
@@ -109,11 +110,11 @@ class SphinxPlugin(Plugin): | |||||
conf_path = self.site.sitepath.child_folder(conf_path) | conf_path = self.site.sitepath.child_folder(conf_path) | ||||
# Sphinx always execs the config file in its parent dir. | # Sphinx always execs the config file in its parent dir. | ||||
conf_file = conf_path.child("conf.py") | conf_file = conf_path.child("conf.py") | ||||
self._sphinx_config = {"__file__":conf_file} | |||||
self._sphinx_config = {"__file__": conf_file} | |||||
curdir = os.getcwd() | curdir = os.getcwd() | ||||
os.chdir(conf_path.path) | os.chdir(conf_path.path) | ||||
try: | try: | ||||
execfile(conf_file,self._sphinx_config) | |||||
execfile(conf_file, self._sphinx_config) | |||||
finally: | finally: | ||||
os.chdir(curdir) | os.chdir(curdir) | ||||
return self._sphinx_config | return self._sphinx_config | ||||
@@ -132,16 +133,17 @@ class SphinxPlugin(Plugin): | |||||
# We need to: | # We need to: | ||||
# * change the deploy name from .rst to .html | # * change the deploy name from .rst to .html | ||||
# * if a block_map is given, switch off default_block | # * 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(): | for resource in self.site.content.walk_resources(): | ||||
if resource.source_file.path.endswith(suffix): | 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 | target_folder = File(resource.relative_deploy_path).parent | ||||
resource.relative_deploy_path = target_folder.child(new_name) | resource.relative_deploy_path = target_folder.child(new_name) | ||||
if settings.block_map: | if settings.block_map: | ||||
resource.meta.default_block = None | 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. | """Event hook for processing an individual resource. | ||||
If the input resource is a sphinx input file, this method will replace | 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 | This means that if no sphinx-related resources need updating, then | ||||
we entirely avoid running sphinx. | 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): | if not resource.source_file.path.endswith(suffix): | ||||
return text | return text | ||||
if self.sphinx_build_dir is None: | if self.sphinx_build_dir is None: | ||||
@@ -164,9 +166,9 @@ class SphinxPlugin(Plugin): | |||||
if not settings.block_map: | if not settings.block_map: | ||||
output.append(sphinx_output["body"]) | output.append(sphinx_output["body"]) | ||||
else: | else: | ||||
for (nm,content) in sphinx_output.iteritems(): | |||||
for (nm, content) in sphinx_output.iteritems(): | |||||
try: | try: | ||||
block = getattr(settings.block_map,nm) | |||||
block = getattr(settings.block_map, nm) | |||||
except AttributeError: | except AttributeError: | ||||
pass | pass | ||||
else: | else: | ||||
@@ -198,41 +200,45 @@ class SphinxPlugin(Plugin): | |||||
conf_path = self.settings.conf_path | conf_path = self.settings.conf_path | ||||
conf_path = self.site.sitepath.child_folder(conf_path) | conf_path = self.site.sitepath.child_folder(conf_path) | ||||
conf_file = conf_path.child("conf.py") | 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("or set sphinx.conf_path to the directory") | ||||
logger.error("containing your sphinx conf.py") | logger.error("containing your sphinx conf.py") | ||||
raise | raise | ||||
# Check that the hyde_json extension is loaded | # 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: | if "hyde.ext.plugins.sphinx" not in extensions: | ||||
logger.error("The hyde_json sphinx extension is not configured.") | logger.error("The hyde_json sphinx extension is not configured.") | ||||
logger.error("Please add 'hyde.ext.plugins.sphinx' to the list") | logger.error("Please add 'hyde.ext.plugins.sphinx' to the list") | ||||
logger.error("of extensions in your sphinx conf.py file.") | 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") | raise RuntimeError("sphinx is not configured correctly") | ||||
# Check that the master doc exists in the source tree. | # 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): | if not os.path.exists(master_doc): | ||||
logger.error("The sphinx master document doesn't exist.") | 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("or change the 'master_doc' setting in your") | ||||
logger.error("sphinx conf.py file.") | 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") | raise RuntimeError("sphinx is not configured correctly") | ||||
# Check that I am *before* the other plugins, | # Check that I am *before* the other plugins, | ||||
# with the possible exception of MetaPlugin | # with the possible exception of MetaPlugin | ||||
for plugin in self.site.plugins: | for plugin in self.site.plugins: | ||||
if plugin is self: | if plugin is self: | ||||
break | break | ||||
if not isinstance(plugin,_MetaPlugin): | |||||
if not isinstance(plugin, _MetaPlugin): | |||||
logger.error("The sphinx plugin is installed after the") | 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("It's quite likely that this will break things.") | ||||
logger.error("Please move the sphinx plugin to the top") | logger.error("Please move the sphinx plugin to the top") | ||||
logger.error("of the plugins list.") | 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") | raise RuntimeError("sphinx is not configured correctly") | ||||
def _run_sphinx(self): | def _run_sphinx(self): | ||||
@@ -254,7 +260,7 @@ class SphinxPlugin(Plugin): | |||||
if sphinx.main(sphinx_args) != 0: | if sphinx.main(sphinx_args) != 0: | ||||
raise RuntimeError("sphinx build failed") | 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. | """Get the sphinx output for a given resource. | ||||
This returns a dict mapping block names to HTML text fragments. | This returns a dict mapping block names to HTML text fragments. | ||||
@@ -263,13 +269,14 @@ class SphinxPlugin(Plugin): | |||||
related pages and so-on. | related pages and so-on. | ||||
""" | """ | ||||
relpath = File(resource.relative_path) | 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) | return json.load(f) | ||||
class HydeJSONHTMLBuilder(JSONHTMLBuilder): | class HydeJSONHTMLBuilder(JSONHTMLBuilder): | ||||
"""A slightly-customised JSONHTMLBuilder, for use by Hyde. | """A slightly-customised JSONHTMLBuilder, for use by Hyde. | ||||
This is a Sphinx builder that serilises the generated HTML fragments into | 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. | work correctly once things have been processed by Hyde. | ||||
""" | """ | ||||
name = "hyde_json" | name = "hyde_json" | ||||
def get_target_uri(self, docname, typ=None): | def get_target_uri(self, docname, typ=None): | ||||
return docname + ".html" | return docname + ".html" | ||||
@@ -291,5 +299,3 @@ def setup(app): | |||||
Hyde plugin. It simply registers the HydeJSONHTMLBuilder class. | Hyde plugin. It simply registers the HydeJSONHTMLBuilder class. | ||||
""" | """ | ||||
app.add_builder(HydeJSONHTMLBuilder) | app.add_builder(HydeJSONHTMLBuilder) | ||||
@@ -20,9 +20,11 @@ import operator | |||||
# | # | ||||
class FlattenerPlugin(Plugin): | class FlattenerPlugin(Plugin): | ||||
""" | """ | ||||
The plugin class for flattening nested folders. | The plugin class for flattening nested folders. | ||||
""" | """ | ||||
def __init__(self, site): | def __init__(self, site): | ||||
super(FlattenerPlugin, self).__init__(site) | super(FlattenerPlugin, self).__init__(site) | ||||
@@ -50,7 +52,7 @@ class FlattenerPlugin(Plugin): | |||||
target_path = target.child(resource.name) | target_path = target.child(resource.name) | ||||
self.logger.debug( | self.logger.debug( | ||||
'Flattening resource path [%s] to [%s]' % | 'Flattening resource path [%s] to [%s]' % | ||||
(resource, target_path)) | |||||
(resource, target_path)) | |||||
resource.relative_deploy_path = target_path | resource.relative_deploy_path = target_path | ||||
for child in node.walk(): | for child in node.walk(): | ||||
child.relative_deploy_path = target.path | child.relative_deploy_path = target.path | ||||
@@ -61,12 +63,14 @@ class FlattenerPlugin(Plugin): | |||||
# | # | ||||
class CombinePlugin(Plugin): | class CombinePlugin(Plugin): | ||||
""" | """ | ||||
To use this combine, the following configuration should be added | To use this combine, the following configuration should be added | ||||
to meta data:: | to meta data:: | ||||
combine: | combine: | ||||
sort: false #Optional. Defaults to true. | 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. | recurse: true #Optional. Default false. | ||||
files: | files: | ||||
- ns1.*.js | - ns1.*.js | ||||
@@ -97,13 +101,14 @@ class CombinePlugin(Plugin): | |||||
except AttributeError: | except AttributeError: | ||||
raise AttributeError("No resources to combine for [%s]" % resource) | raise AttributeError("No resources to combine for [%s]" % resource) | ||||
if type(files) is str: | if type(files) is str: | ||||
files = [ files ] | |||||
files = [files] | |||||
# Grab resources to combine | # Grab resources to combine | ||||
# select site root | # select site root | ||||
try: | 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: | except AttributeError: | ||||
root = resource.node | root = resource.node | ||||
@@ -122,10 +127,12 @@ class CombinePlugin(Plugin): | |||||
sort = True | sort = True | ||||
if sort: | 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: | 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] | resources = [r[1] for f in files for r in resources if f in r] | ||||
if not resources: | if not resources: | ||||
@@ -173,7 +180,7 @@ class CombinePlugin(Plugin): | |||||
except AttributeError: | except AttributeError: | ||||
pass | pass | ||||
if where not in [ "top", "bottom" ]: | |||||
if where not in ["top", "bottom"]: | |||||
raise ValueError("%r should be either `top` or `bottom`" % where) | raise ValueError("%r should be either `top` or `bottom`" % where) | ||||
self.logger.debug( | self.logger.debug( | ||||
@@ -190,11 +197,14 @@ class CombinePlugin(Plugin): | |||||
# | # | ||||
class Page: | class Page: | ||||
def __init__(self, posts, number): | def __init__(self, posts, number): | ||||
self.posts = posts | self.posts = posts | ||||
self.number = number | self.number = number | ||||
class Paginator: | class Paginator: | ||||
""" | """ | ||||
Iterates resources which have pages associated with them. | Iterates resources which have pages associated with them. | ||||
""" | """ | ||||
@@ -204,7 +214,8 @@ class Paginator: | |||||
def __init__(self, settings): | def __init__(self, settings): | ||||
self.sorter = getattr(settings, 'sorter', None) | self.sorter = getattr(settings, 'sorter', None) | ||||
self.size = getattr(settings, 'size', 10) | 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): | def _relative_url(self, source_path, number, basename, ext): | ||||
""" | """ | ||||
@@ -214,8 +225,8 @@ class Paginator: | |||||
path = File(source_path) | path = File(source_path) | ||||
if number != 1: | if number != 1: | ||||
filename = self.file_pattern.replace('$PAGE', str(number)) \ | 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)) | path = path.parent.child(os.path.normpath(filename)) | ||||
return path | return path | ||||
@@ -227,10 +238,11 @@ class Paginator: | |||||
res = Resource(base_resource.source_file, node) | res = Resource(base_resource.source_file, node) | ||||
res.node.meta = Metadata(node.meta) | res.node.meta = Metadata(node.meta) | ||||
res.meta = Metadata(base_resource.meta, res.node.meta) | res.meta = Metadata(base_resource.meta, res.node.meta) | ||||
brs = base_resource.source_file | |||||
path = self._relative_url(base_resource.relative_path, | 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) | res.set_relative_deploy_path(path) | ||||
return res | return res | ||||
@@ -250,7 +262,7 @@ class Paginator: | |||||
if not hasattr(resource, 'depends'): | if not hasattr(resource, 'depends'): | ||||
resource.depends = [] | resource.depends = [] | ||||
resource.depends.extend([dep.relative_path for dep in dependencies | 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): | def _walk_pages_in_node(self, node): | ||||
""" | """ | ||||
@@ -294,6 +306,7 @@ class Paginator: | |||||
class PaginatorPlugin(Plugin): | class PaginatorPlugin(Plugin): | ||||
""" | """ | ||||
Paginator plugin. | Paginator plugin. | ||||
@@ -315,6 +328,7 @@ class PaginatorPlugin(Plugin): | |||||
{{ resource.page.next }} | {{ resource.page.next }} | ||||
""" | """ | ||||
def __init__(self, site): | def __init__(self, site): | ||||
super(PaginatorPlugin, self).__init__(site) | super(PaginatorPlugin, self).__init__(site) | ||||
@@ -322,10 +336,10 @@ class PaginatorPlugin(Plugin): | |||||
for node in self.site.content.walk(): | for node in self.site.content.walk(): | ||||
added_resources = [] | added_resources = [] | ||||
paged_resources = (res for res in node.resources | paged_resources = (res for res in node.resources | ||||
if hasattr(res.meta, 'paginator')) | |||||
if hasattr(res.meta, 'paginator')) | |||||
for resource in paged_resources: | for resource in paged_resources: | ||||
paginator = Paginator(resource.meta.paginator) | 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 | node.resources += added_resources | ||||
@@ -3,7 +3,7 @@ | |||||
Text processing plugins | 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): | class BlockdownPlugin(TextyPlugin): | ||||
""" | """ | ||||
The plugin class for block text replacement. | The plugin class for block text replacement. | ||||
""" | """ | ||||
def __init__(self, site): | def __init__(self, site): | ||||
super(BlockdownPlugin, self).__init__(site) | super(BlockdownPlugin, self).__init__(site) | ||||
@@ -55,9 +57,11 @@ class BlockdownPlugin(TextyPlugin): | |||||
# | # | ||||
class MarkingsPlugin(TextyPlugin): | class MarkingsPlugin(TextyPlugin): | ||||
""" | """ | ||||
The plugin class for mark text replacement. | The plugin class for mark text replacement. | ||||
""" | """ | ||||
def __init__(self, site): | def __init__(self, site): | ||||
super(MarkingsPlugin, self).__init__(site) | super(MarkingsPlugin, self).__init__(site) | ||||
@@ -99,9 +103,11 @@ class MarkingsPlugin(TextyPlugin): | |||||
# | # | ||||
class ReferencePlugin(TextyPlugin): | class ReferencePlugin(TextyPlugin): | ||||
""" | """ | ||||
The plugin class for reference text replacement. | The plugin class for reference text replacement. | ||||
""" | """ | ||||
def __init__(self, site): | def __init__(self, site): | ||||
super(ReferencePlugin, self).__init__(site) | super(ReferencePlugin, self).__init__(site) | ||||
@@ -143,9 +149,11 @@ class ReferencePlugin(TextyPlugin): | |||||
# | # | ||||
class SyntextPlugin(TextyPlugin): | class SyntextPlugin(TextyPlugin): | ||||
""" | """ | ||||
The plugin class for syntax text replacement. | The plugin class for syntax text replacement. | ||||
""" | """ | ||||
def __init__(self, site): | def __init__(self, site): | ||||
super(SyntextPlugin, self).__init__(site) | super(SyntextPlugin, self).__init__(site) | ||||
@@ -170,7 +178,6 @@ class SyntextPlugin(TextyPlugin): | |||||
""" | """ | ||||
return '^\s*~~~+\s*$' | return '^\s*~~~+\s*$' | ||||
def get_params(self, match, start=True): | def get_params(self, match, start=True): | ||||
""" | """ | ||||
~~~css~~~ will return css | ~~~css~~~ will return css | ||||
@@ -199,16 +206,18 @@ class SyntextPlugin(TextyPlugin): | |||||
# | # | ||||
class TextlinksPlugin(Plugin): | class TextlinksPlugin(Plugin): | ||||
""" | """ | ||||
The plugin class for text link replacement. | The plugin class for text link replacement. | ||||
""" | """ | ||||
def __init__(self, site): | def __init__(self, site): | ||||
super(TextlinksPlugin, self).__init__(site) | super(TextlinksPlugin, self).__init__(site) | ||||
import re | import re | ||||
self.content_link = re.compile('\[\[([^\]^!][^\]]*)\]\]', | self.content_link = re.compile('\[\[([^\]^!][^\]]*)\]\]', | ||||
re.UNICODE|re.MULTILINE) | |||||
re.UNICODE | re.MULTILINE) | |||||
self.media_link = re.compile('\[\[\!\!([^\]]*)\]\]', | self.media_link = re.compile('\[\[\!\!([^\]]*)\]\]', | ||||
re.UNICODE|re.MULTILINE) | |||||
re.UNICODE | re.MULTILINE) | |||||
def begin_text_resource(self, resource, text): | def begin_text_resource(self, resource, text): | ||||
""" | """ | ||||
@@ -221,11 +230,12 @@ class TextlinksPlugin(Plugin): | |||||
""" | """ | ||||
if not resource.uses_template: | if not resource.uses_template: | ||||
return text | return text | ||||
def replace_content(match): | def replace_content(match): | ||||
return self.template.get_content_url_statement(match.groups(1)[0]) | return self.template.get_content_url_statement(match.groups(1)[0]) | ||||
def replace_media(match): | def replace_media(match): | ||||
return self.template.get_media_url_statement(match.groups(1)[0]) | return self.template.get_media_url_statement(match.groups(1)[0]) | ||||
text = self.content_link.sub(replace_content, text) | text = self.content_link.sub(replace_content, text) | ||||
text = self.media_link.sub(replace_media, text) | text = self.media_link.sub(replace_media, text) | ||||
return text | return text | ||||
@@ -8,7 +8,9 @@ from hyde.site import Site | |||||
from functools import wraps | from functools import wraps | ||||
from fswrap import File | from fswrap import File | ||||
class UrlCleanerPlugin(Plugin): | class UrlCleanerPlugin(Plugin): | ||||
""" | """ | ||||
Url Cleaner plugin for hyde. Adds to hyde the ability to generate clean | Url Cleaner plugin for hyde. Adds to hyde the ability to generate clean | ||||
urls. | urls. | ||||
@@ -53,13 +55,13 @@ class UrlCleanerPlugin(Plugin): | |||||
def wrapper(site, path, safe=None): | def wrapper(site, path, safe=None): | ||||
url = urlgetter(site, path, safe) | url = urlgetter(site, path, safe) | ||||
index_file_names = getattr(settings, | index_file_names = getattr(settings, | ||||
'index_file_names', | |||||
['index.html']) | |||||
'index_file_names', | |||||
['index.html']) | |||||
rep = File(url) | rep = File(url) | ||||
if rep.name in index_file_names: | if rep.name in index_file_names: | ||||
url = rep.parent.path.rstrip('/') | url = rep.parent.path.rstrip('/') | ||||
if hasattr(settings, 'append_slash') and \ | if hasattr(settings, 'append_slash') and \ | ||||
settings.append_slash: | |||||
settings.append_slash: | |||||
url += '/' | url += '/' | ||||
elif hasattr(settings, 'strip_extensions'): | elif hasattr(settings, 'strip_extensions'): | ||||
if rep.kind in settings.strip_extensions: | if rep.kind in settings.strip_extensions: | ||||
@@ -12,9 +12,11 @@ import subprocess | |||||
class VCSDatesPlugin(Plugin): | class VCSDatesPlugin(Plugin): | ||||
""" | """ | ||||
Base class for getting resource timestamps from VCS. | Base class for getting resource timestamps from VCS. | ||||
""" | """ | ||||
def __init__(self, site, vcs_name='vcs'): | def __init__(self, site, vcs_name='vcs'): | ||||
super(VCSDatesPlugin, self).__init__(site) | super(VCSDatesPlugin, self).__init__(site) | ||||
self.vcs_name = vcs_name | self.vcs_name = vcs_name | ||||
@@ -38,8 +40,8 @@ class VCSDatesPlugin(Plugin): | |||||
if created == "git": | if created == "git": | ||||
created = date_created or \ | created = date_created or \ | ||||
datetime.utcfromtimestamp( | |||||
os.path.getctime(resource.path)) | |||||
datetime.utcfromtimestamp( | |||||
os.path.getctime(resource.path)) | |||||
created = created.replace(tzinfo=None) | created = created.replace(tzinfo=None) | ||||
resource.meta.created = created | resource.meta.created = created | ||||
@@ -48,7 +50,6 @@ class VCSDatesPlugin(Plugin): | |||||
modified = modified.replace(tzinfo=None) | modified = modified.replace(tzinfo=None) | ||||
resource.meta.modified = modified | resource.meta.modified = modified | ||||
def get_dates(self): | def get_dates(self): | ||||
""" | """ | ||||
Extract creation and last modification date from the vcs and include | Extract creation and last modification date from the vcs and include | ||||
@@ -60,7 +61,10 @@ class VCSDatesPlugin(Plugin): | |||||
# | # | ||||
# Git Dates | # Git Dates | ||||
# | # | ||||
class GitDatesPlugin(VCSDatesPlugin): | class GitDatesPlugin(VCSDatesPlugin): | ||||
def __init__(self, site): | def __init__(self, site): | ||||
super(GitDatesPlugin, self).__init__(site, 'git') | super(GitDatesPlugin, self).__init__(site, 'git') | ||||
@@ -78,7 +82,8 @@ class GitDatesPlugin(VCSDatesPlugin): | |||||
]).split("\n") | ]).split("\n") | ||||
commits = commits[:-1] | commits = commits[:-1] | ||||
except subprocess.CalledProcessError: | 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 | commits = None | ||||
if commits: | if commits: | ||||
@@ -93,6 +98,8 @@ class GitDatesPlugin(VCSDatesPlugin): | |||||
# | # | ||||
# Mercurial Dates | # Mercurial Dates | ||||
# | # | ||||
class MercurialDatesPlugin(VCSDatesPlugin): | class MercurialDatesPlugin(VCSDatesPlugin): | ||||
def __init__(self, site): | def __init__(self, site): | ||||
@@ -105,12 +112,12 @@ class MercurialDatesPlugin(VCSDatesPlugin): | |||||
# Run hg log --template={date|isodatesec} | # Run hg log --template={date|isodatesec} | ||||
try: | try: | ||||
commits = subprocess.check_output([ | 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] | commits = commits[:-1] | ||||
except subprocess.CalledProcessError: | except subprocess.CalledProcessError: | ||||
self.logger.warning("Unable to get mercurial history for [%s]" | self.logger.warning("Unable to get mercurial history for [%s]" | ||||
% resource) | |||||
% resource) | |||||
commits = None | commits = None | ||||
if not commits: | if not commits: | ||||
@@ -8,6 +8,7 @@ from hyde.publisher import Publisher | |||||
import abc | import abc | ||||
from subprocess import Popen, PIPE | from subprocess import Popen, PIPE | ||||
class DVCS(Publisher): | class DVCS(Publisher): | ||||
__metaclass__ = abc.ABCMeta | __metaclass__ = abc.ABCMeta | ||||
@@ -19,23 +20,28 @@ class DVCS(Publisher): | |||||
self.switch(self.branch) | self.switch(self.branch) | ||||
@abc.abstractmethod | @abc.abstractmethod | ||||
def pull(self): pass | |||||
def pull(self): | |||||
pass | |||||
@abc.abstractmethod | @abc.abstractmethod | ||||
def push(self): pass | |||||
def push(self): | |||||
pass | |||||
@abc.abstractmethod | @abc.abstractmethod | ||||
def commit(self, message): pass | |||||
def commit(self, message): | |||||
pass | |||||
@abc.abstractmethod | @abc.abstractmethod | ||||
def switch(self, branch): pass | |||||
def switch(self, branch): | |||||
pass | |||||
@abc.abstractmethod | @abc.abstractmethod | ||||
def add(self, path="."): pass | |||||
def add(self, path="."): | |||||
pass | |||||
@abc.abstractmethod | @abc.abstractmethod | ||||
def merge(self, branch): pass | |||||
def merge(self, branch): | |||||
pass | |||||
def publish(self): | def publish(self): | ||||
super(DVCS, self).publish() | super(DVCS, self).publish() | ||||
@@ -47,8 +53,8 @@ class DVCS(Publisher): | |||||
self.push() | self.push() | ||||
class Git(DVCS): | class Git(DVCS): | ||||
""" | """ | ||||
Acts as a publisher to a git repository. Can be used to publish to | Acts as a publisher to a git repository. Can be used to publish to | ||||
github pages. | github pages. | ||||
@@ -56,7 +62,7 @@ class Git(DVCS): | |||||
def add(self, path="."): | def add(self, path="."): | ||||
cmd = Popen('git add "%s"' % 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] | cmdresult = cmd.communicate()[0] | ||||
if cmd.returncode: | if cmd.returncode: | ||||
raise Exception(cmdresult) | raise Exception(cmdresult) | ||||
@@ -79,7 +85,6 @@ class Git(DVCS): | |||||
if cmd.returncode: | if cmd.returncode: | ||||
raise Exception(cmdresult) | raise Exception(cmdresult) | ||||
def commit(self, message): | def commit(self, message): | ||||
cmd = Popen('git commit -a -m"%s"' % message, | cmd = Popen('git commit -a -m"%s"' % message, | ||||
cwd=unicode(self.path), stdout=PIPE, shell=True) | cwd=unicode(self.path), stdout=PIPE, shell=True) | ||||
@@ -100,4 +105,4 @@ class Git(DVCS): | |||||
cwd=unicode(self.path), stdout=PIPE, shell=True) | cwd=unicode(self.path), stdout=PIPE, shell=True) | ||||
cmdresult = cmd.communicate()[0] | cmdresult = cmd.communicate()[0] | ||||
if cmd.returncode: | if cmd.returncode: | ||||
raise Exception(cmdresult) | |||||
raise Exception(cmdresult) |
@@ -32,15 +32,14 @@ except ImportError: | |||||
raise | raise | ||||
class PyFS(Publisher): | class PyFS(Publisher): | ||||
def initialize(self, settings): | def initialize(self, settings): | ||||
self.settings = settings | self.settings = settings | ||||
self.url = settings.url | 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") | raise ValueError("check_etag must name the etag algorithm") | ||||
self.prompt_for_credentials() | self.prompt_for_credentials() | ||||
self.fs = fsopendir(self.url) | self.fs = fsopendir(self.url) | ||||
@@ -58,48 +57,47 @@ class PyFS(Publisher): | |||||
def publish(self): | def publish(self): | ||||
super(PyFS, self).publish() | super(PyFS, self).publish() | ||||
deploy_fs = OSFS(self.site.config.deploy_root_path.path) | 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. | # Process each local file, to see if it needs updating. | ||||
for filenm in local_filenms: | for filenm in local_filenms: | ||||
filepath = pathjoin(dirnm,filenm) | |||||
filepath = pathjoin(dirnm, filenm) | |||||
# Try to find an existing remote file, to compare metadata. | # 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: | if nm == filenm: | ||||
break | break | ||||
else: | else: | ||||
info = {} | info = {} | ||||
# Skip it if the etags match | # Skip it if the etags match | ||||
if self.check_etag and "etag" in info: | 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) | local_etag = self._calculate_etag(f) | ||||
if info["etag"] == local_etag: | if info["etag"] == local_etag: | ||||
logger.info("Skipping file [etag]: %s",filepath) | |||||
logger.info("Skipping file [etag]: %s", filepath) | |||||
continue | continue | ||||
# Skip it if the mtime is more recent remotely. | # Skip it if the mtime is more recent remotely. | ||||
if self.check_mtime and "modified_time" in info: | if self.check_mtime and "modified_time" in info: | ||||
local_mtime = deploy_fs.getinfo(filepath)["modified_time"] | local_mtime = deploy_fs.getinfo(filepath)["modified_time"] | ||||
if info["modified_time"] > local_mtime: | if info["modified_time"] > local_mtime: | ||||
logger.info("Skipping file [mtime]: %s",filepath) | |||||
logger.info("Skipping file [mtime]: %s", filepath) | |||||
continue | continue | ||||
# Upload it to the remote filesystem. | # 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. | # 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: | if filenm not in local_filenms: | ||||
logger.info("Removing file: %s",filepath) | |||||
logger.info("Removing file: %s", filepath) | |||||
self.fs.remove(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: | while data: | ||||
hasher.update(data) | hasher.update(data) | ||||
data = f.read(1024*64) | |||||
data = f.read(1024 * 64) | |||||
return hasher.hexdigest() | return hasher.hexdigest() | ||||
@@ -19,16 +19,14 @@ from commando.util import getLoggerWithNullHandler | |||||
logger = getLoggerWithNullHandler('hyde.ext.publishers.pypi') | logger = getLoggerWithNullHandler('hyde.ext.publishers.pypi') | ||||
class PyPI(Publisher): | class PyPI(Publisher): | ||||
def initialize(self, settings): | def initialize(self, settings): | ||||
self.settings = settings | self.settings = settings | ||||
self.project = settings.project | 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() | self.prompt_for_credentials() | ||||
def prompt_for_credentials(self): | def prompt_for_credentials(self): | ||||
@@ -38,12 +36,13 @@ class PyPI(Publisher): | |||||
else: | else: | ||||
pypirc = ConfigParser.RawConfigParser() | pypirc = ConfigParser.RawConfigParser() | ||||
pypirc.read([pypirc_file]) | pypirc.read([pypirc_file]) | ||||
missing_errs = (ConfigParser.NoSectionError,ConfigParser.NoOptionError) | |||||
missing_errs = ( | |||||
ConfigParser.NoSectionError, ConfigParser.NoOptionError) | |||||
# Try to find username in .pypirc | # Try to find username in .pypirc | ||||
if self.username is None: | if self.username is None: | ||||
if pypirc is not None: | if pypirc is not None: | ||||
try: | try: | ||||
self.username = pypirc.get("server-login","username") | |||||
self.username = pypirc.get("server-login", "username") | |||||
except missing_errs: | except missing_errs: | ||||
pass | pass | ||||
# Prompt for username on command-line | # Prompt for username on command-line | ||||
@@ -54,7 +53,7 @@ class PyPI(Publisher): | |||||
if self.password is None: | if self.password is None: | ||||
if pypirc is not None: | if pypirc is not None: | ||||
try: | try: | ||||
self.password = pypirc.get("server-login","password") | |||||
self.password = pypirc.get("server-login", "password") | |||||
except missing_errs: | except missing_errs: | ||||
pass | pass | ||||
# Prompt for username on command-line | # Prompt for username on command-line | ||||
@@ -73,11 +72,11 @@ class PyPI(Publisher): | |||||
# Bundle it up into a zipfile | # Bundle it up into a zipfile | ||||
logger.info("building the zipfile") | logger.info("building the zipfile") | ||||
root = self.site.config.deploy_root_path | root = self.site.config.deploy_root_path | ||||
zf = zipfile.ZipFile(tf,"w",zipfile.ZIP_DEFLATED) | |||||
zf = zipfile.ZipFile(tf, "w", zipfile.ZIP_DEFLATED) | |||||
try: | try: | ||||
for item in root.walker.walk_files(): | 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: | finally: | ||||
zf.close() | zf.close() | ||||
# Formulate the necessary bits for the HTTP POST. | # Formulate the necessary bits for the HTTP POST. | ||||
@@ -85,12 +84,14 @@ class PyPI(Publisher): | |||||
authz = self.username + ":" + self.password | authz = self.username + ":" + self.password | ||||
authz = "Basic " + standard_b64encode(authz) | authz = "Basic " + standard_b64encode(authz) | ||||
boundary = "-----------" + os.urandom(20).encode("hex") | 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,) | content_type = "multipart/form-data; boundary=%s" % (boundary,) | ||||
items = ((":action","doc_upload"),("name",self.project)) | |||||
items = ((":action", "doc_upload"), ("name", self.project)) | |||||
body_prefix = "" | body_prefix = "" | ||||
for (name,value) in items: | |||||
for (name, value) in items: | |||||
body_prefix += "--" + boundary + "\r\n" | body_prefix += "--" + boundary + "\r\n" | ||||
body_prefix += "Content-Disposition: form-data; name=\"" | body_prefix += "Content-Disposition: form-data; name=\"" | ||||
body_prefix += name + "\"\r\n\r\n" | body_prefix += name + "\"\r\n\r\n" | ||||
@@ -110,24 +111,24 @@ class PyPI(Publisher): | |||||
con.connect() | con.connect() | ||||
try: | try: | ||||
con.putrequest("POST", self.url) | 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.endheaders() | ||||
con.send(body_prefix) | con.send(body_prefix) | ||||
tf.seek(0) | tf.seek(0) | ||||
data = tf.read(1024*32) | |||||
data = tf.read(1024 * 32) | |||||
while data: | while data: | ||||
con.send(data) | con.send(data) | ||||
data = tf.read(1024*32) | |||||
data = tf.read(1024 * 32) | |||||
con.send(body_suffix) | con.send(body_suffix) | ||||
r = con.getresponse() | r = con.getresponse() | ||||
try: | try: | ||||
# PyPI tries to redirect to the page on success. | # PyPI tries to redirect to the page on success. | ||||
if r.status in (200,301,): | |||||
if r.status in (200, 301,): | |||||
logger.info("success!") | logger.info("success!") | ||||
else: | else: | ||||
msg = "Upload failed: %s %s" % (r.status,r.reason,) | |||||
msg = "Upload failed: %s %s" % (r.status, r.reason,) | |||||
raise Exception(msg) | raise Exception(msg) | ||||
finally: | finally: | ||||
r.close() | r.close() | ||||
@@ -135,5 +136,3 @@ class PyPI(Publisher): | |||||
con.close() | con.close() | ||||
finally: | finally: | ||||
tf.close() | tf.close() | ||||
@@ -34,7 +34,9 @@ from hyde.publisher import Publisher | |||||
from subprocess import Popen, PIPE | from subprocess import Popen, PIPE | ||||
class SSH(Publisher): | class SSH(Publisher): | ||||
def initialize(self, settings): | def initialize(self, settings): | ||||
self.settings = settings | self.settings = settings | ||||
self.username = settings.username | self.username = settings.username | ||||
@@ -47,7 +49,7 @@ class SSH(Publisher): | |||||
command = "{command} {opts} ./ {username}{server}:{target}".format( | command = "{command} {opts} ./ {username}{server}:{target}".format( | ||||
command=self.command, | command=self.command, | ||||
opts=self.opts, | opts=self.opts, | ||||
username=self.username+'@' if self.username else '', | |||||
username=self.username + '@' if self.username else '', | |||||
server=self.server, | server=self.server, | ||||
target=self.target) | target=self.target) | ||||
deploy_path = self.site.config.deploy_root_path.path | deploy_path = self.site.config.deploy_root_path.path | ||||
@@ -29,10 +29,13 @@ from commando.util import getLoggerWithNullHandler | |||||
logger = getLoggerWithNullHandler('hyde.engine.Jinja2') | logger = getLoggerWithNullHandler('hyde.engine.Jinja2') | ||||
class SilentUndefined(Undefined): | class SilentUndefined(Undefined): | ||||
""" | """ | ||||
A redefinition of undefined that eats errors. | A redefinition of undefined that eats errors. | ||||
""" | """ | ||||
def __getattr__(self, name): | def __getattr__(self, name): | ||||
return self | return self | ||||
@@ -41,6 +44,7 @@ class SilentUndefined(Undefined): | |||||
def __call__(self, *args, **kwargs): | def __call__(self, *args, **kwargs): | ||||
return self | return self | ||||
@contextfunction | @contextfunction | ||||
def media_url(context, path, safe=None): | 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) | return context['site'].media_url(path, safe) | ||||
@contextfunction | @contextfunction | ||||
def content_url(context, path, safe=None): | 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) | return context['site'].content_url(path, safe) | ||||
@contextfunction | @contextfunction | ||||
def full_url(context, path, safe=None): | 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) | return context['site'].full_url(path, safe) | ||||
@contextfilter | @contextfilter | ||||
def urlencode(ctx, url, safe=None): | def urlencode(ctx, url, safe=None): | ||||
if safe is not None: | if safe is not None: | ||||
@@ -69,16 +76,18 @@ def urlencode(ctx, url, safe=None): | |||||
else: | else: | ||||
return quote(url.encode('utf8')) | return quote(url.encode('utf8')) | ||||
@contextfilter | @contextfilter | ||||
def urldecode(ctx, url): | def urldecode(ctx, url): | ||||
return unquote(url).decode('utf8') | return unquote(url).decode('utf8') | ||||
@contextfilter | @contextfilter | ||||
def date_format(ctx, dt, fmt=None): | def date_format(ctx, dt, fmt=None): | ||||
if not dt: | if not dt: | ||||
dt = datetime.now() | dt = datetime.now() | ||||
if not isinstance(dt, datetime) or \ | if not isinstance(dt, datetime) or \ | ||||
not isinstance(dt, date): | |||||
not isinstance(dt, date): | |||||
logger.error("Date format called on a non date object") | logger.error("Date format called on a non date object") | ||||
return dt | return dt | ||||
@@ -93,9 +102,11 @@ def date_format(ctx, dt, fmt=None): | |||||
def islice(iterable, start=0, stop=3, step=1): | def islice(iterable, start=0, stop=3, step=1): | ||||
return itertools.islice(iterable, start, stop, step) | return itertools.islice(iterable, start, stop, step) | ||||
def top(iterable, count=3): | def top(iterable, count=3): | ||||
return islice(iterable, stop=count) | return islice(iterable, stop=count) | ||||
def xmldatetime(dt): | def xmldatetime(dt): | ||||
if not dt: | if not dt: | ||||
dt = datetime.now() | dt = datetime.now() | ||||
@@ -105,6 +116,7 @@ def xmldatetime(dt): | |||||
zprefix = tz[:3] + ":" + tz[3:] | zprefix = tz[:3] + ":" + tz[3:] | ||||
return dt.strftime("%Y-%m-%dT%H:%M:%S") + zprefix | return dt.strftime("%Y-%m-%dT%H:%M:%S") + zprefix | ||||
@environmentfilter | @environmentfilter | ||||
def asciidoc(env, value): | def asciidoc(env, value): | ||||
""" | """ | ||||
@@ -122,9 +134,11 @@ def asciidoc(env, value): | |||||
asciidoc = AsciiDocAPI() | asciidoc = AsciiDocAPI() | ||||
asciidoc.options('--no-header-footer') | asciidoc.options('--no-header-footer') | ||||
result = StringIO.StringIO() | 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") | return unicode(result.getvalue(), "utf-8") | ||||
@environmentfilter | @environmentfilter | ||||
def markdown(env, value): | def markdown(env, value): | ||||
""" | """ | ||||
@@ -140,14 +154,15 @@ def markdown(env, value): | |||||
if hasattr(env.config, 'markdown'): | if hasattr(env.config, 'markdown'): | ||||
d['extensions'] = getattr(env.config.markdown, 'extensions', []) | d['extensions'] = getattr(env.config.markdown, 'extensions', []) | ||||
d['extension_configs'] = getattr(env.config.markdown, | d['extension_configs'] = getattr(env.config.markdown, | ||||
'extension_configs', | |||||
Expando({})).to_dict() | |||||
'extension_configs', | |||||
Expando({})).to_dict() | |||||
if hasattr(env.config.markdown, 'output_format'): | if hasattr(env.config.markdown, 'output_format'): | ||||
d['output_format'] = env.config.markdown.output_format | d['output_format'] = env.config.markdown.output_format | ||||
marked = md.Markdown(**d) | marked = md.Markdown(**d) | ||||
return marked.convert(output) | return marked.convert(output) | ||||
@environmentfilter | @environmentfilter | ||||
def restructuredtext(env, value): | def restructuredtext(env, value): | ||||
""" | """ | ||||
@@ -161,18 +176,20 @@ def restructuredtext(env, value): | |||||
highlight_source = False | highlight_source = False | ||||
if hasattr(env.config, 'restructuredtext'): | 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', []) | extensions = getattr(env.config.restructuredtext, 'extensions', []) | ||||
import imp | import imp | ||||
for extension in extensions: | for extension in extensions: | ||||
imp.load_module(extension, *imp.find_module(extension)) | imp.load_module(extension, *imp.find_module(extension)) | ||||
if highlight_source: | if highlight_source: | ||||
import hyde.lib.pygments.rst_directive | |||||
import hyde.lib.pygments.rst_directive # noqa | |||||
parts = publish_parts(source=value, writer_name="html") | parts = publish_parts(source=value, writer_name="html") | ||||
return parts['html_body'] | return parts['html_body'] | ||||
@environmentfilter | @environmentfilter | ||||
def syntax(env, value, lexer=None, filename=None): | def syntax(env, value, lexer=None, filename=None): | ||||
""" | """ | ||||
@@ -184,17 +201,17 @@ def syntax(env, value, lexer=None, filename=None): | |||||
from pygments import formatters | from pygments import formatters | ||||
except ImportError: | except ImportError: | ||||
logger.error(u"pygments library is required to" | logger.error(u"pygments library is required to" | ||||
" use syntax highlighting tags.") | |||||
" use syntax highlighting tags.") | |||||
raise TemplateError("Cannot load pygments") | raise TemplateError("Cannot load pygments") | ||||
pyg = (lexers.get_lexer_by_name(lexer) | pyg = (lexers.get_lexer_by_name(lexer) | ||||
if lexer else | |||||
lexers.guess_lexer(value)) | |||||
if lexer else | |||||
lexers.guess_lexer(value)) | |||||
settings = {} | settings = {} | ||||
if hasattr(env.config, 'syntax'): | if hasattr(env.config, 'syntax'): | ||||
settings = getattr(env.config.syntax, | settings = getattr(env.config.syntax, | ||||
'options', | |||||
Expando({})).to_dict() | |||||
'options', | |||||
Expando({})).to_dict() | |||||
formatter = formatters.HtmlFormatter(**settings) | formatter = formatters.HtmlFormatter(**settings) | ||||
code = pygments.highlight(value, pyg, formatter) | 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): | if not getattr(env.config.syntax, 'use_figure', True): | ||||
return Markup(code) | return Markup(code) | ||||
return Markup( | 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): | class Spaceless(Extension): | ||||
""" | """ | ||||
Emulates the django spaceless template tag. | Emulates the django spaceless template tag. | ||||
""" | """ | ||||
@@ -220,10 +240,10 @@ class Spaceless(Extension): | |||||
""" | """ | ||||
lineno = parser.stream.next().lineno | lineno = parser.stream.next().lineno | ||||
body = parser.parse_statements(['name:endspaceless'], | body = parser.parse_statements(['name:endspaceless'], | ||||
drop_needle=True) | |||||
drop_needle=True) | |||||
return nodes.CallBlock( | 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): | def _render_spaceless(self, caller=None): | ||||
""" | """ | ||||
@@ -235,7 +255,9 @@ class Spaceless(Extension): | |||||
return '' | return '' | ||||
return re.sub(r'>\s+<', '><', unicode(caller().strip())) | return re.sub(r'>\s+<', '><', unicode(caller().strip())) | ||||
class Asciidoc(Extension): | class Asciidoc(Extension): | ||||
""" | """ | ||||
A wrapper around the asciidoc filter for syntactic sugar. | A wrapper around the asciidoc filter for syntactic sugar. | ||||
""" | """ | ||||
@@ -243,14 +265,15 @@ class Asciidoc(Extension): | |||||
def parse(self, parser): | 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 | lineno = parser.stream.next().lineno | ||||
body = parser.parse_statements(['name:endasciidoc'], drop_needle=True) | body = parser.parse_statements(['name:endasciidoc'], drop_needle=True) | ||||
return nodes.CallBlock( | 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): | def _render_asciidoc(self, caller=None): | ||||
""" | """ | ||||
@@ -261,7 +284,9 @@ class Asciidoc(Extension): | |||||
output = caller().strip() | output = caller().strip() | ||||
return asciidoc(self.environment, output) | return asciidoc(self.environment, output) | ||||
class Markdown(Extension): | class Markdown(Extension): | ||||
""" | """ | ||||
A wrapper around the markdown filter for syntactic sugar. | A wrapper around the markdown filter for syntactic sugar. | ||||
""" | """ | ||||
@@ -269,14 +294,15 @@ class Markdown(Extension): | |||||
def parse(self, parser): | 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 | lineno = parser.stream.next().lineno | ||||
body = parser.parse_statements(['name:endmarkdown'], drop_needle=True) | body = parser.parse_statements(['name:endmarkdown'], drop_needle=True) | ||||
return nodes.CallBlock( | 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): | def _render_markdown(self, caller=None): | ||||
""" | """ | ||||
@@ -287,7 +313,9 @@ class Markdown(Extension): | |||||
output = caller().strip() | output = caller().strip() | ||||
return markdown(self.environment, output) | return markdown(self.environment, output) | ||||
class restructuredText(Extension): | class restructuredText(Extension): | ||||
""" | """ | ||||
A wrapper around the restructuredtext filter for syntactic sugar | A wrapper around the restructuredtext filter for syntactic sugar | ||||
""" | """ | ||||
@@ -298,10 +326,11 @@ class restructuredText(Extension): | |||||
Simply extract our content | Simply extract our content | ||||
""" | """ | ||||
lineno = parser.stream.next().lineno | 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 | return nodes.CallBlock(self.call_method('_render_rst'), [], [], body | ||||
).set_lineno(lineno) | |||||
).set_lineno(lineno) | |||||
def _render_rst(self, caller=None): | def _render_rst(self, caller=None): | ||||
""" | """ | ||||
@@ -312,7 +341,9 @@ class restructuredText(Extension): | |||||
output = caller().strip() | output = caller().strip() | ||||
return restructuredtext(self.environment, output) | return restructuredtext(self.environment, output) | ||||
class YamlVar(Extension): | class YamlVar(Extension): | ||||
""" | """ | ||||
An extension that converts the content between the tags | An extension that converts the content between the tags | ||||
into an yaml object and sets the value in the given | into an yaml object and sets the value in the given | ||||
@@ -330,16 +361,15 @@ class YamlVar(Extension): | |||||
var = parser.stream.expect('name').value | var = parser.stream.expect('name').value | ||||
body = parser.parse_statements(['name:endyaml'], drop_needle=True) | body = parser.parse_statements(['name:endyaml'], drop_needle=True) | ||||
return [ | 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): | def _set_yaml(self, var, caller=None): | ||||
""" | """ | ||||
@@ -356,6 +386,7 @@ class YamlVar(Extension): | |||||
var.update(yaml.load(out)) | var.update(yaml.load(out)) | ||||
return '' | return '' | ||||
def parse_kwargs(parser): | def parse_kwargs(parser): | ||||
""" | """ | ||||
Parses keyword arguments in tags. | Parses keyword arguments in tags. | ||||
@@ -368,17 +399,19 @@ def parse_kwargs(parser): | |||||
value = nodes.Const(parser.stream.next().value) | value = nodes.Const(parser.stream.next().value) | ||||
return (name, value) | return (name, value) | ||||
class Syntax(Extension): | class Syntax(Extension): | ||||
""" | """ | ||||
A wrapper around the syntax filter for syntactic sugar. | A wrapper around the syntax filter for syntactic sugar. | ||||
""" | """ | ||||
tags = set(['syntax']) | tags = set(['syntax']) | ||||
def parse(self, parser): | 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 | lineno = parser.stream.next().lineno | ||||
lex = nodes.Const(None) | lex = nodes.Const(None) | ||||
@@ -392,8 +425,8 @@ class Syntax(Extension): | |||||
(_, value1) = parse_kwargs(parser) | (_, value1) = parse_kwargs(parser) | ||||
(lex, filename) = (value, value1) \ | (lex, filename) = (value, value1) \ | ||||
if name == 'lex' \ | |||||
else (value1, value) | |||||
if name == 'lex' \ | |||||
else (value1, value) | |||||
else: | else: | ||||
lex = nodes.Const(parser.stream.next().value) | lex = nodes.Const(parser.stream.next().value) | ||||
if parser.stream.skip_if('comma'): | if parser.stream.skip_if('comma'): | ||||
@@ -401,10 +434,9 @@ class Syntax(Extension): | |||||
body = parser.parse_statements(['name:endsyntax'], drop_needle=True) | body = parser.parse_statements(['name:endsyntax'], drop_needle=True) | ||||
return nodes.CallBlock( | 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): | def _render_syntax(self, lex, filename, caller=None): | ||||
""" | """ | ||||
@@ -415,7 +447,9 @@ class Syntax(Extension): | |||||
output = caller().strip() | output = caller().strip() | ||||
return syntax(self.environment, output, lex, filename) | return syntax(self.environment, output, lex, filename) | ||||
class IncludeText(Extension): | class IncludeText(Extension): | ||||
""" | """ | ||||
Automatically runs `markdown` and `typogrify` on included | Automatically runs `markdown` and `typogrify` on included | ||||
files. | files. | ||||
@@ -429,8 +463,8 @@ class IncludeText(Extension): | |||||
""" | """ | ||||
node = parser.parse_include() | node = parser.parse_include() | ||||
return nodes.CallBlock( | 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): | def _render_include_text(self, caller=None): | ||||
""" | """ | ||||
@@ -448,7 +482,9 @@ class IncludeText(Extension): | |||||
MARKINGS = '_markings_' | MARKINGS = '_markings_' | ||||
class Reference(Extension): | class Reference(Extension): | ||||
""" | """ | ||||
Marks a block in a template such that its available for use | Marks a block in a template such that its available for use | ||||
when referenced using a `refer` tag. | when referenced using a `refer` tag. | ||||
@@ -465,11 +501,13 @@ class Reference(Extension): | |||||
tag = token.value | tag = token.value | ||||
name = parser.stream.next().value | name = parser.stream.next().value | ||||
body = parser.parse_statements(['name:end%s' % tag], drop_needle=True) | 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): | def _render_output(self, markings, name, caller=None): | ||||
""" | """ | ||||
@@ -482,7 +520,9 @@ class Reference(Extension): | |||||
markings[name] = out | markings[name] = out | ||||
return out | return out | ||||
class Refer(Extension): | class Refer(Extension): | ||||
""" | """ | ||||
Imports content blocks specified in the referred template as | Imports content blocks specified in the referred template as | ||||
variables in a given namespace. | variables in a given namespace. | ||||
@@ -507,43 +547,43 @@ class Refer(Extension): | |||||
temp = parser.free_identifier(lineno) | temp = parser.free_identifier(lineno) | ||||
return [ | 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): | def _push_resource(self, namespace, site, resource, template, caller): | ||||
@@ -553,10 +593,10 @@ class Refer(Extension): | |||||
namespace['parent_resource'] = resource | namespace['parent_resource'] = resource | ||||
if not hasattr(resource, 'depends'): | if not hasattr(resource, 'depends'): | ||||
resource.depends = [] | resource.depends = [] | ||||
if not template in resource.depends: | |||||
if template not in resource.depends: | |||||
resource.depends.append(template) | resource.depends.append(template) | ||||
namespace['resource'] = site.content.resource_from_relative_path( | namespace['resource'] = site.content.resource_from_relative_path( | ||||
template) | |||||
template) | |||||
return '' | return '' | ||||
def _assign_reference(self, markings, namespace, caller): | def _assign_reference(self, markings, namespace, caller): | ||||
@@ -573,6 +613,7 @@ class Refer(Extension): | |||||
class HydeLoader(FileSystemLoader): | class HydeLoader(FileSystemLoader): | ||||
""" | """ | ||||
A wrapper around the file system loader that performs | A wrapper around the file system loader that performs | ||||
hyde specific tweaks. | hyde specific tweaks. | ||||
@@ -582,9 +623,9 @@ class HydeLoader(FileSystemLoader): | |||||
config = site.config if hasattr(site, 'config') else None | config = site.config if hasattr(site, 'config') else None | ||||
if config: | if config: | ||||
super(HydeLoader, self).__init__([ | 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: | else: | ||||
super(HydeLoader, self).__init__(unicode(sitepath)) | super(HydeLoader, self).__init__(unicode(sitepath)) | ||||
@@ -604,8 +645,8 @@ class HydeLoader(FileSystemLoader): | |||||
try: | try: | ||||
(contents, | (contents, | ||||
filename, | filename, | ||||
date) = super(HydeLoader, self).get_source( | |||||
environment, template) | |||||
date) = super(HydeLoader, self).get_source( | |||||
environment, template) | |||||
except UnicodeDecodeError: | except UnicodeDecodeError: | ||||
HydeException.reraise( | HydeException.reraise( | ||||
"Unicode error when processing %s" % template, sys.exc_info()) | "Unicode error when processing %s" % template, sys.exc_info()) | ||||
@@ -624,6 +665,7 @@ class HydeLoader(FileSystemLoader): | |||||
# pylint: disable-msg=W0104,E0602,W0613,R0201 | # pylint: disable-msg=W0104,E0602,W0613,R0201 | ||||
class Jinja2Template(Template): | class Jinja2Template(Template): | ||||
""" | """ | ||||
The Jinja2 Template implementation | The Jinja2 Template implementation | ||||
""" | """ | ||||
@@ -638,23 +680,23 @@ class Jinja2Template(Template): | |||||
self.site = site | self.site = site | ||||
self.engine = engine | self.engine = engine | ||||
self.preprocessor = (engine.preprocessor | self.preprocessor = (engine.preprocessor | ||||
if hasattr(engine, 'preprocessor') else None) | |||||
if hasattr(engine, 'preprocessor') else None) | |||||
self.loader = HydeLoader(self.sitepath, site, self.preprocessor) | self.loader = HydeLoader(self.sitepath, site, self.preprocessor) | ||||
default_extensions = [ | 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 = { | defaults = { | ||||
@@ -694,12 +736,12 @@ class Jinja2Template(Template): | |||||
settings['filters'][name] = getattr(module, function_name) | settings['filters'][name] = getattr(module, function_name) | ||||
self.env = Environment( | 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['media_url'] = media_url | ||||
self.env.globals['content_url'] = content_url | self.env.globals['content_url'] = content_url | ||||
self.env.globals['full_url'] = full_url | self.env.globals['full_url'] = full_url | ||||
@@ -738,7 +780,6 @@ class Jinja2Template(Template): | |||||
if self.env.bytecode_cache: | if self.env.bytecode_cache: | ||||
self.env.bytecode_cache.clear() | self.env.bytecode_cache.clear() | ||||
def get_dependencies(self, path): | def get_dependencies(self, path): | ||||
""" | """ | ||||
Finds dependencies hierarchically based on the included | Finds dependencies hierarchically based on the included | ||||
@@ -774,10 +815,12 @@ class Jinja2Template(Template): | |||||
The pattern for matching selected template statements. | The pattern for matching selected template statements. | ||||
""" | """ | ||||
return { | 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): | def get_include_statement(self, path_to_include): | ||||
@@ -43,7 +43,6 @@ class Generator(object): | |||||
site.context = Context.load(site.sitepath, site.config.context) | site.context = Context.load(site.sitepath, site.config.context) | ||||
self.__context__.update(site.context) | self.__context__.update(site.context) | ||||
@contextmanager | @contextmanager | ||||
def context_for_resource(self, resource): | def context_for_resource(self, resource): | ||||
""" | """ | ||||
@@ -77,7 +76,8 @@ class Generator(object): | |||||
providing restricted access to the methods. | 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.preprocessor = preprocessor | ||||
self.postprocessor = postprocessor | self.postprocessor = postprocessor | ||||
self.context_for_path = context_for_path | self.context_for_path = context_for_path | ||||
@@ -86,14 +86,16 @@ class Generator(object): | |||||
logger.info("Generating site at [%s]" % self.site.sitepath) | logger.info("Generating site at [%s]" % self.site.sitepath) | ||||
self.template = Template.find_template(self.site) | self.template = Template.find_template(self.site) | ||||
logger.debug("Using [%s] as the template", | logger.debug("Using [%s] as the template", | ||||
self.template.__class__.__name__) | |||||
self.template.__class__.__name__) | |||||
logger.info("Configuring the template environment") | 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, | 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) | self.events.template_loaded(self.template) | ||||
def initialize(self): | def initialize(self): | ||||
@@ -124,7 +126,7 @@ class Generator(object): | |||||
rel_path = resource.relative_path | rel_path = resource.relative_path | ||||
deps = self.deps[rel_path] if rel_path in self.deps \ | deps = self.deps[rel_path] if rel_path in self.deps \ | ||||
else self.update_deps(resource) | |||||
else self.update_deps(resource) | |||||
return deps | return deps | ||||
def update_deps(self, resource): | def update_deps(self, resource): | ||||
@@ -143,7 +145,8 @@ class Generator(object): | |||||
dep_res = self.site.content.resource_from_relative_path(dep) | dep_res = self.site.content.resource_from_relative_path(dep) | ||||
if dep_res: | if dep_res: | ||||
if dep_res.relative_path in self.waiting_deps.keys(): | 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: | else: | ||||
deps.extend(self.get_dependencies(dep_res)) | deps.extend(self.get_dependencies(dep_res)) | ||||
if resource.uses_template: | if resource.uses_template: | ||||
@@ -166,7 +169,7 @@ class Generator(object): | |||||
self.load_site_if_needed() | self.load_site_if_needed() | ||||
target = File(self.site.config.deploy_root_path.child( | 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): | if not target.exists or target.older_than(resource.source_file): | ||||
logger.debug("Found changes in %s" % resource) | logger.debug("Found changes in %s" % resource) | ||||
return True | return True | ||||
@@ -209,7 +212,7 @@ class Generator(object): | |||||
self.load_site_if_needed() | self.load_site_if_needed() | ||||
self.events.begin_site() | self.events.begin_site() | ||||
logger.info("Generating site to [%s]" % | 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.__generate_node__(self.site.content, incremental) | ||||
self.events.site_complete() | self.events.site_complete() | ||||
self.finalize() | self.finalize() | ||||
@@ -261,9 +264,8 @@ class Generator(object): | |||||
except HydeException: | except HydeException: | ||||
self.generate_all() | 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 a single resource. If resource_path is non-existent or empty, | ||||
generates the entire website. | generates the entire website. | ||||
@@ -311,7 +313,6 @@ class Generator(object): | |||||
self.__generate_resource__(resource, incremental) | self.__generate_resource__(resource, incremental) | ||||
self.events.node_complete(node) | self.events.node_complete(node) | ||||
def __generate_resource__(self, resource, incremental=False): | def __generate_resource__(self, resource, incremental=False): | ||||
self.refresh_config() | self.refresh_config() | ||||
if not resource.is_processable: | if not resource.is_processable: | ||||
@@ -323,7 +324,7 @@ class Generator(object): | |||||
logger.debug("Processing [%s]", resource) | logger.debug("Processing [%s]", resource) | ||||
with self.context_for_resource(resource) as context: | with self.context_for_resource(resource) as context: | ||||
target = File(self.site.config.deploy_root_path.child( | target = File(self.site.config.deploy_root_path.child( | ||||
resource.relative_deploy_path)) | |||||
resource.relative_deploy_path)) | |||||
target.parent.make() | target.parent.make() | ||||
if resource.simple_copy: | if resource.simple_copy: | ||||
logger.debug("Simply Copying [%s]", resource) | logger.debug("Simply Copying [%s]", resource) | ||||
@@ -334,19 +335,19 @@ class Generator(object): | |||||
logger.debug("Rendering [%s]", resource) | logger.debug("Rendering [%s]", resource) | ||||
try: | try: | ||||
text = self.template.render_resource(resource, | text = self.template.render_resource(resource, | ||||
context) | |||||
context) | |||||
except Exception, e: | 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: | else: | ||||
text = resource.source_file.read_all() | 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( | text = self.events.text_resource_complete( | ||||
resource, text) or text | |||||
resource, text) or text | |||||
target.write(text) | target.write(text) | ||||
copymode(resource.source_file.path, target.path) | copymode(resource.source_file.path, target.path) | ||||
else: | else: | ||||
@@ -11,6 +11,7 @@ LAYOUTS = "layouts" | |||||
class Layout(object): | class Layout(object): | ||||
""" | """ | ||||
Represents a layout package | Represents a layout package | ||||
""" | """ | ||||
@@ -26,10 +27,10 @@ class Layout(object): | |||||
layout_folder = None | layout_folder = None | ||||
if HYDE_DATA in os.environ: | if HYDE_DATA in os.environ: | ||||
layout_folder = Layout._get_layout_folder( | layout_folder = Layout._get_layout_folder( | ||||
os.environ[HYDE_DATA], layout_name) | |||||
os.environ[HYDE_DATA], layout_name) | |||||
if not layout_folder: | if not layout_folder: | ||||
layout_folder = Layout._get_layout_folder( | layout_folder = Layout._get_layout_folder( | ||||
File(__file__).parent, layout_name) | |||||
File(__file__).parent, layout_name) | |||||
return layout_folder | return layout_folder | ||||
@staticmethod | @staticmethod | ||||
@@ -58,7 +58,9 @@ from docutils.parsers.rst import directives, Directive | |||||
from pygments import highlight | from pygments import highlight | ||||
from pygments.lexers import get_lexer_by_name, TextLexer | from pygments.lexers import get_lexer_by_name, TextLexer | ||||
class Pygments(Directive): | class Pygments(Directive): | ||||
""" Source code syntax hightlighting. | """ Source code syntax hightlighting. | ||||
""" | """ | ||||
required_arguments = 1 | required_arguments = 1 | ||||
@@ -75,7 +77,8 @@ class Pygments(Directive): | |||||
# no lexer found - use the text one instead of an exception | # no lexer found - use the text one instead of an exception | ||||
lexer = TextLexer() | lexer = TextLexer() | ||||
# take an arbitrary option if more than one is given | # 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) | parsed = highlight(u'\n'.join(self.content), lexer, formatter) | ||||
return [nodes.raw('', parsed, format='html')] | return [nodes.raw('', parsed, format='html')] | ||||
@@ -5,9 +5,10 @@ The hyde executable | |||||
""" | """ | ||||
from hyde.engine import Engine | from hyde.engine import Engine | ||||
def main(): | def main(): | ||||
"""Main""" | """Main""" | ||||
Engine().run() | Engine().run() | ||||
if __name__ == "__main__": | if __name__ == "__main__": | ||||
main() | |||||
main() |
@@ -14,6 +14,7 @@ logger = getLoggerWithNullHandler('hyde.engine') | |||||
SEQS = (tuple, list, set, frozenset) | SEQS = (tuple, list, set, frozenset) | ||||
def make_expando(primitive): | def make_expando(primitive): | ||||
""" | """ | ||||
Creates an expando object, a sequence of expando objects or just | Creates an expando object, a sequence of expando objects or just | ||||
@@ -29,6 +30,7 @@ def make_expando(primitive): | |||||
class Expando(object): | class Expando(object): | ||||
""" | """ | ||||
A generic expando class that creates attributes from | A generic expando class that creates attributes from | ||||
the passed in dictionary. | the passed in dictionary. | ||||
@@ -63,7 +65,6 @@ class Expando(object): | |||||
""" | """ | ||||
setattr(self, unicode(key).encode('utf-8'), make_expando(value)) | setattr(self, unicode(key).encode('utf-8'), make_expando(value)) | ||||
def __repr__(self): | def __repr__(self): | ||||
return unicode(self.to_dict()) | return unicode(self.to_dict()) | ||||
@@ -79,9 +80,9 @@ class Expando(object): | |||||
elif isinstance(v, SEQS): | elif isinstance(v, SEQS): | ||||
seq = type(v) | seq = type(v) | ||||
result[k] = seq(item.to_dict() | 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: | else: | ||||
result[k] = v | result[k] = v | ||||
return result | return result | ||||
@@ -94,6 +95,7 @@ class Expando(object): | |||||
class Context(object): | class Context(object): | ||||
""" | """ | ||||
Wraps the context related functions and utilities. | Wraps the context related functions and utilities. | ||||
""" | """ | ||||
@@ -125,7 +127,9 @@ class Context(object): | |||||
return context | return context | ||||
class Dependents(IterableUserDict): | class Dependents(IterableUserDict): | ||||
""" | """ | ||||
Represents the dependency graph for hyde. | Represents the dependency graph for hyde. | ||||
""" | """ | ||||
@@ -146,11 +150,14 @@ class Dependents(IterableUserDict): | |||||
if self.deps_file.parent.exists: | if self.deps_file.parent.exists: | ||||
self.deps_file.write(yaml.dump(self.data)) | self.deps_file.write(yaml.dump(self.data)) | ||||
def _expand_path(sitepath, path): | def _expand_path(sitepath, path): | ||||
child = sitepath.child_folder(path) | child = sitepath.child_folder(path) | ||||
return Folder(child.fully_expanded_path) | return Folder(child.fully_expanded_path) | ||||
class Config(Expando): | class Config(Expando): | ||||
""" | """ | ||||
Represents the hyde configuration file | Represents the hyde configuration file | ||||
""" | """ | ||||
@@ -158,7 +165,7 @@ class Config(Expando): | |||||
def __init__(self, sitepath, config_file=None, config_dict=None): | def __init__(self, sitepath, config_file=None, config_dict=None): | ||||
self.default_config = dict( | self.default_config = dict( | ||||
mode='production', | mode='production', | ||||
simple_copy = [], | |||||
simple_copy=[], | |||||
content_root='content', | content_root='content', | ||||
deploy_root='deploy', | deploy_root='deploy', | ||||
media_root='media', | media_root='media', | ||||
@@ -167,9 +174,9 @@ class Config(Expando): | |||||
base_url="/", | base_url="/", | ||||
encode_safe=None, | encode_safe=None, | ||||
not_found='404.html', | not_found='404.html', | ||||
plugins = [], | |||||
ignore = [ "*~", "*.bak", ".hg", ".git", ".svn"], | |||||
meta = { | |||||
plugins=[], | |||||
ignore=["*~", "*.bak", ".hg", ".git", ".svn"], | |||||
meta={ | |||||
"nodemeta": 'meta.yaml' | "nodemeta": 'meta.yaml' | ||||
} | } | ||||
) | ) | ||||
@@ -188,7 +195,7 @@ class Config(Expando): | |||||
if not self.config_files: | if not self.config_files: | ||||
return True | return True | ||||
return any((conf.has_changed_since(self.load_time) | return any((conf.has_changed_since(self.load_time) | ||||
for conf in self.config_files)) | |||||
for conf in self.config_files)) | |||||
def load(self): | def load(self): | ||||
conf = dict(**self.default_config) | conf = dict(**self.default_config) | ||||
@@ -202,15 +209,14 @@ class Config(Expando): | |||||
return | return | ||||
self.update(self.load()) | self.update(self.load()) | ||||
def read_config(self, config_file): | def read_config(self, config_file): | ||||
""" | """ | ||||
Reads the configuration file and updates this | Reads the configuration file and updates this | ||||
object while allowing for inherited configurations. | object while allowing for inherited configurations. | ||||
""" | """ | ||||
conf_file = self.sitepath.child( | conf_file = self.sitepath.child( | ||||
config_file if | |||||
config_file else 'site.yaml') | |||||
config_file if | |||||
config_file else 'site.yaml') | |||||
conf = {} | conf = {} | ||||
if File(conf_file).exists: | if File(conf_file).exists: | ||||
self.config_files.append(File(conf_file)) | self.config_files.append(File(conf_file)) | ||||
@@ -224,7 +230,6 @@ class Config(Expando): | |||||
self.load_time = datetime.now() | self.load_time = datetime.now() | ||||
return conf | return conf | ||||
@property | @property | ||||
def deploy_root_path(self): | def deploy_root_path(self): | ||||
""" | """ | ||||
@@ -13,7 +13,6 @@ import os | |||||
import re | import re | ||||
import subprocess | import subprocess | ||||
import sys | import sys | ||||
import traceback | |||||
from commando.util import getLoggerWithNullHandler, load_python_object | from commando.util import getLoggerWithNullHandler, load_python_object | ||||
from fswrap import File | from fswrap import File | ||||
@@ -22,30 +21,53 @@ logger = getLoggerWithNullHandler('hyde.engine') | |||||
# Plugins have been reorganized. Map old plugin paths to new. | # Plugins have been reorganized. Map old plugin paths to new. | ||||
PLUGINS_OLD_AND_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): | class PluginProxy(object): | ||||
""" | """ | ||||
A proxy class to raise events in registered plugins | A proxy class to raise events in registered plugins | ||||
""" | """ | ||||
@@ -61,7 +83,8 @@ class PluginProxy(object): | |||||
if self.site.plugins: | if self.site.plugins: | ||||
for plugin in self.site.plugins: | for plugin in self.site.plugins: | ||||
if hasattr(plugin, method_name): | if hasattr(plugin, method_name): | ||||
checker = getattr(plugin, 'should_call__' + method_name) | |||||
checker = getattr( | |||||
plugin, 'should_call__' + method_name) | |||||
if checker(*args): | if checker(*args): | ||||
function = getattr(plugin, method_name) | function = getattr(plugin, method_name) | ||||
try: | try: | ||||
@@ -80,9 +103,11 @@ class PluginProxy(object): | |||||
return __call_plugins__ | return __call_plugins__ | ||||
raise HydeException( | raise HydeException( | ||||
"Unknown plugin method [%s] called." % method_name) | |||||
"Unknown plugin method [%s] called." % method_name) | |||||
class Plugin(object): | class Plugin(object): | ||||
""" | """ | ||||
The plugin protocol | The plugin protocol | ||||
""" | """ | ||||
@@ -92,10 +117,9 @@ class Plugin(object): | |||||
super(Plugin, self).__init__() | super(Plugin, self).__init__() | ||||
self.site = site | self.site = site | ||||
self.logger = getLoggerWithNullHandler( | self.logger = getLoggerWithNullHandler( | ||||
'hyde.engine.%s' % self.__class__.__name__) | |||||
'hyde.engine.%s' % self.__class__.__name__) | |||||
self.template = None | self.template = None | ||||
def template_loaded(self, template): | def template_loaded(self, template): | ||||
""" | """ | ||||
Called when the template for the site has been identified. | Called when the template for the site has been identified. | ||||
@@ -123,7 +147,8 @@ class Plugin(object): | |||||
elif name.startswith('should_call__'): | elif name.startswith('should_call__'): | ||||
(_, _, method) = name.rpartition('__') | (_, _, method) = name.rpartition('__') | ||||
if (method in ('begin_text_resource', 'text_resource_complete', | 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 | result = self._file_filter | ||||
elif (method in ('begin_node', 'node_complete')): | elif (method in ('begin_node', 'node_complete')): | ||||
result = self._dir_filter | result = self._dir_filter | ||||
@@ -132,7 +157,7 @@ class Plugin(object): | |||||
return True | return True | ||||
result = always_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 | @property | ||||
def settings(self): | def settings(self): | ||||
@@ -147,7 +172,6 @@ class Plugin(object): | |||||
pass | pass | ||||
return opts | return opts | ||||
@property | @property | ||||
def plugin_name(self): | def plugin_name(self): | ||||
""" | """ | ||||
@@ -195,26 +219,26 @@ class Plugin(object): | |||||
except AttributeError: | except AttributeError: | ||||
filters = None | filters = None | ||||
result = any(fnmatch.fnmatch(resource.path, f) | 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 | return result | ||||
def _dir_filter(self, node, *args, **kwargs): | 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: | try: | ||||
node_filters = self.settings.include_paths | node_filters = self.settings.include_paths | ||||
if not isinstance(node_filters, list): | if not isinstance(node_filters, list): | ||||
node_filters = [node_filters] | node_filters = [node_filters] | ||||
node_filters = [self.site.content.node_from_relative_path(f) | node_filters = [self.site.content.node_from_relative_path(f) | ||||
for f in node_filters] | |||||
for f in node_filters] | |||||
except AttributeError: | except AttributeError: | ||||
node_filters = None | node_filters = None | ||||
result = any(node.source == f.source or | 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 | return result | ||||
def begin_text_resource(self, resource, text): | def begin_text_resource(self, resource, text): | ||||
@@ -293,7 +317,7 @@ class Plugin(object): | |||||
return load_python_object(plugin_name)(site) | return load_python_object(plugin_name)(site) | ||||
site.plugins = [load_plugin(name) | site.plugins = [load_plugin(name) | ||||
for name in site.config.plugins] | |||||
for name in site.config.plugins] | |||||
@staticmethod | @staticmethod | ||||
def get_proxy(site): | def get_proxy(site): | ||||
@@ -302,7 +326,9 @@ class Plugin(object): | |||||
""" | """ | ||||
return PluginProxy(site) | return PluginProxy(site) | ||||
class CLTransformer(Plugin): | class CLTransformer(Plugin): | ||||
""" | """ | ||||
Handy class for plugins that simply call a command line app to | Handy class for plugins that simply call a command line app to | ||||
transform resources. | transform resources. | ||||
@@ -333,11 +359,11 @@ class CLTransformer(Plugin): | |||||
""" | """ | ||||
return ("%(name)s executable path not configured properly. " | 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 | @property | ||||
def app(self): | def app(self): | ||||
@@ -398,7 +424,7 @@ class CLTransformer(Plugin): | |||||
if match: | if match: | ||||
val = args[match] | val = args[match] | ||||
param = "%s%s" % (self.option_prefix(descriptive), | param = "%s%s" % (self.option_prefix(descriptive), | ||||
descriptive) | |||||
descriptive) | |||||
if descriptive.endswith("="): | if descriptive.endswith("="): | ||||
param += val | param += val | ||||
val = None | val = None | ||||
@@ -414,13 +440,15 @@ class CLTransformer(Plugin): | |||||
try: | try: | ||||
self.logger.debug( | self.logger.debug( | ||||
"Calling executable [%s] with arguments %s" % | "Calling executable [%s] with arguments %s" % | ||||
(args[0], unicode(args[1:]))) | |||||
(args[0], unicode(args[1:]))) | |||||
return subprocess.check_output(args) | return subprocess.check_output(args) | ||||
except subprocess.CalledProcessError, error: | except subprocess.CalledProcessError, error: | ||||
self.logger.error(error.output) | self.logger.error(error.output) | ||||
raise | raise | ||||
class TextyPlugin(Plugin): | class TextyPlugin(Plugin): | ||||
""" | """ | ||||
Base class for text preprocessing plugins. | Base class for text preprocessing plugins. | ||||
@@ -493,10 +521,11 @@ class TextyPlugin(Plugin): | |||||
""" | """ | ||||
Replace a text base pattern with a template statement. | 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) | text = text_open.sub(self.text_to_tag, text) | ||||
if self.close_pattern: | 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( | text = text_close.sub( | ||||
partial(self.text_to_tag, start=False), text) | |||||
partial(self.text_to_tag, start=False), text) | |||||
return text | return text |
@@ -8,7 +8,9 @@ Contains abstract classes and utilities that help publishing a website to a | |||||
server. | server. | ||||
""" | """ | ||||
class Publisher(object): | class Publisher(object): | ||||
""" | """ | ||||
The abstract base class for publishers. | The abstract base class for publishers. | ||||
""" | """ | ||||
@@ -18,13 +20,14 @@ class Publisher(object): | |||||
def __init__(self, site, settings, message): | def __init__(self, site, settings, message): | ||||
super(Publisher, self).__init__() | super(Publisher, self).__init__() | ||||
self.logger = getLoggerWithNullHandler( | self.logger = getLoggerWithNullHandler( | ||||
'hyde.engine.%s' % self.__class__.__name__) | |||||
'hyde.engine.%s' % self.__class__.__name__) | |||||
self.site = site | self.site = site | ||||
self.message = message | self.message = message | ||||
self.initialize(settings) | self.initialize(settings) | ||||
@abc.abstractmethod | @abc.abstractmethod | ||||
def initialize(self, settings): pass | |||||
def initialize(self, settings): | |||||
pass | |||||
@abc.abstractmethod | @abc.abstractmethod | ||||
def publish(self): | def publish(self): | ||||
@@ -43,7 +46,8 @@ class Publisher(object): | |||||
# Find the first configured publisher | # Find the first configured publisher | ||||
try: | try: | ||||
publisher = site.config.publisher.__dict__.iterkeys().next() | 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) | settings = attrgetter("publisher.%s" % publisher)(site.config) | ||||
except (AttributeError, StopIteration): | except (AttributeError, StopIteration): | ||||
logger.error( | logger.error( | ||||
@@ -56,4 +60,4 @@ class Publisher(object): | |||||
raise Exception("Please specify the publisher type in config.") | raise Exception("Please specify the publisher type in config.") | ||||
pub_class = load_python_object(settings.type) | 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 | from commando.util import getLoggerWithNullHandler | ||||
logger = getLoggerWithNullHandler('hyde.server') | logger = getLoggerWithNullHandler('hyde.server') | ||||
class HydeRequestHandler(SimpleHTTPRequestHandler): | class HydeRequestHandler(SimpleHTTPRequestHandler): | ||||
""" | """ | ||||
Serves files by regenerating the resource (or) | Serves files by regenerating the resource (or) | ||||
everything when a request is issued. | everything when a request is issued. | ||||
@@ -35,7 +37,7 @@ class HydeRequestHandler(SimpleHTTPRequestHandler): | |||||
logger.debug("Processing request: [%s]" % self.path) | logger.debug("Processing request: [%s]" % self.path) | ||||
result = urlparse.urlparse(self.path) | result = urlparse.urlparse(self.path) | ||||
query = urlparse.parse_qs(result.query) | 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() | self.server.regenerate() | ||||
if 'refresh' in query: | if 'refresh' in query: | ||||
del query['refresh'] | del query['refresh'] | ||||
@@ -48,7 +50,6 @@ class HydeRequestHandler(SimpleHTTPRequestHandler): | |||||
else: | else: | ||||
SimpleHTTPRequestHandler.do_GET(self) | SimpleHTTPRequestHandler.do_GET(self) | ||||
def translate_path(self, path): | def translate_path(self, path): | ||||
""" | """ | ||||
Finds the absolute path of the requested file by | Finds the absolute path of the requested file by | ||||
@@ -56,7 +57,8 @@ class HydeRequestHandler(SimpleHTTPRequestHandler): | |||||
""" | """ | ||||
site = self.server.site | site = self.server.site | ||||
result = urlparse.urlparse(urllib.unquote(self.path).decode('utf-8')) | 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('/') | path = result.path.lstrip('/') | ||||
res = None | res = None | ||||
if path.strip() == "" or File(path).kind.strip() == "": | if path.strip() == "" or File(path).kind.strip() == "": | ||||
@@ -65,13 +67,16 @@ class HydeRequestHandler(SimpleHTTPRequestHandler): | |||||
if isinstance(deployed, Folder): | if isinstance(deployed, Folder): | ||||
node = site.content.node_from_relative_path(path) | node = site.content.node_from_relative_path(path) | ||||
res = node.get_resource('index.html') | 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: | 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: | if res: | ||||
break | break | ||||
for ext in site.config.urlcleaner.strip_extensions: | 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: | if File(new_path).exists: | ||||
return new_path | return new_path | ||||
else: | else: | ||||
@@ -83,7 +88,7 @@ class HydeRequestHandler(SimpleHTTPRequestHandler): | |||||
else: | else: | ||||
self.server.generate_resource(res) | self.server.generate_resource(res) | ||||
new_path = site.config.deploy_root_path.child( | new_path = site.config.deploy_root_path.child( | ||||
res.relative_deploy_path) | |||||
res.relative_deploy_path) | |||||
return new_path | return new_path | ||||
def do_404(self): | def do_404(self): | ||||
@@ -95,13 +100,13 @@ class HydeRequestHandler(SimpleHTTPRequestHandler): | |||||
self.redirect(site.config.not_found) | self.redirect(site.config.not_found) | ||||
else: | else: | ||||
res = site.content.resource_from_relative_deploy_path( | res = site.content.resource_from_relative_deploy_path( | ||||
site.config.not_found) | |||||
site.config.not_found) | |||||
message = "Requested resource not found" | message = "Requested resource not found" | ||||
if not res: | if not res: | ||||
logger.error( | logger.error( | ||||
"Cannot find the 404 template [%s]." | "Cannot find the 404 template [%s]." | ||||
% site.config.not_found) | |||||
% site.config.not_found) | |||||
else: | else: | ||||
f404 = File(self.translate_path(site.config.not_found)) | f404 = File(self.translate_path(site.config.not_found)) | ||||
if f404.exists: | if f404.exists: | ||||
@@ -118,6 +123,7 @@ class HydeRequestHandler(SimpleHTTPRequestHandler): | |||||
class HydeWebServer(HTTPServer): | class HydeWebServer(HTTPServer): | ||||
""" | """ | ||||
The hyde web server that regenerates the resource, node or site when | The hyde web server that regenerates the resource, node or site when | ||||
a request is issued. | a request is issued. | ||||
@@ -133,7 +139,7 @@ class HydeWebServer(HTTPServer): | |||||
self.__shutdown_request = False | self.__shutdown_request = False | ||||
self.map_extensions() | self.map_extensions() | ||||
HTTPServer.__init__(self, (address, port), | HTTPServer.__init__(self, (address, port), | ||||
HydeRequestHandler) | |||||
HydeRequestHandler) | |||||
def map_extensions(self): | def map_extensions(self): | ||||
""" | """ | ||||
@@ -161,7 +167,7 @@ class HydeWebServer(HTTPServer): | |||||
self.generator.generate_all(incremental=False) | self.generator.generate_all(incremental=False) | ||||
except Exception, exception: | except Exception, exception: | ||||
logger.error('Error occured when regenerating the site [%s]' | logger.error('Error occured when regenerating the site [%s]' | ||||
% exception.message) | |||||
% exception.message) | |||||
logger.debug(traceback.format_exc()) | logger.debug(traceback.format_exc()) | ||||
def generate_node(self, node): | def generate_node(self, node): | ||||
@@ -179,7 +185,7 @@ class HydeWebServer(HTTPServer): | |||||
except Exception, exception: | except Exception, exception: | ||||
logger.error( | logger.error( | ||||
'Error [%s] occured when generating the node [%s]' | 'Error [%s] occured when generating the node [%s]' | ||||
% (repr(exception), node)) | |||||
% (repr(exception), node)) | |||||
logger.debug(traceback.format_exc()) | logger.debug(traceback.format_exc()) | ||||
def generate_resource(self, resource): | def generate_resource(self, resource): | ||||
@@ -198,5 +204,5 @@ class HydeWebServer(HTTPServer): | |||||
except Exception, exception: | except Exception, exception: | ||||
logger.error( | logger.error( | ||||
'Error [%s] occured when serving the resource [%s]' | 'Error [%s] occured when serving the resource [%s]' | ||||
% (repr(exception), resource)) | |||||
% (repr(exception), resource)) | |||||
logger.debug(traceback.format_exc()) | logger.debug(traceback.format_exc()) |
@@ -15,6 +15,7 @@ from hyde.model import Config | |||||
from commando.util import getLoggerWithNullHandler | from commando.util import getLoggerWithNullHandler | ||||
from fswrap import FS, File, Folder | from fswrap import FS, File, Folder | ||||
def path_normalized(f): | def path_normalized(f): | ||||
@wraps(f) | @wraps(f) | ||||
def wrapper(self, path): | def wrapper(self, path): | ||||
@@ -23,6 +24,7 @@ def path_normalized(f): | |||||
logger = getLoggerWithNullHandler('hyde.engine') | logger = getLoggerWithNullHandler('hyde.engine') | ||||
class Processable(object): | class Processable(object): | ||||
""" | """ | ||||
A node or resource. | A node or resource. | ||||
@@ -64,8 +66,8 @@ class Processable(object): | |||||
after its been processed. | after its been processed. | ||||
""" | """ | ||||
return self._relative_deploy_path \ | 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): | def set_relative_deploy_path(self, path): | ||||
""" | """ | ||||
@@ -75,7 +77,8 @@ class Processable(object): | |||||
self._relative_deploy_path = path | self._relative_deploy_path = path | ||||
self.site.content.deploy_path_changed(self) | 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 | @property | ||||
def url(self): | def url(self): | ||||
@@ -118,9 +121,10 @@ class Resource(Processable): | |||||
@property | @property | ||||
def slug(self): | def slug(self): | ||||
#TODO: Add a more sophisticated slugify method | |||||
# TODO: Add a more sophisticated slugify method | |||||
return self.source.name_without_extension | return self.source.name_without_extension | ||||
class Node(Processable): | class Node(Processable): | ||||
""" | """ | ||||
Represents any folder that is processed by hyde | Represents any folder that is processed by hyde | ||||
@@ -159,7 +163,7 @@ class Node(Processable): | |||||
if self.contains_resource(resource_name): | if self.contains_resource(resource_name): | ||||
return self.root.resource_from_path( | return self.root.resource_from_path( | ||||
self.source_folder.child(resource_name)) | |||||
self.source_folder.child(resource_name)) | |||||
return None | return None | ||||
def add_child_node(self, folder): | def add_child_node(self, folder): | ||||
@@ -223,6 +227,7 @@ class Node(Processable): | |||||
""" | """ | ||||
return self.source_folder.get_relative_path(self.root.source_folder) | return self.source_folder.get_relative_path(self.root.source_folder) | ||||
class RootNode(Node): | class RootNode(Node): | ||||
""" | """ | ||||
Represents one of the roots of site: Content, Media or Layout | 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. | If no match is found it returns None. | ||||
""" | """ | ||||
return self.node_from_path( | return self.node_from_path( | ||||
self.source_folder.child(unicode(relative_path))) | |||||
self.source_folder.child(unicode(relative_path))) | |||||
@path_normalized | @path_normalized | ||||
def resource_from_path(self, path): | def resource_from_path(self, path): | ||||
@@ -270,7 +275,7 @@ class RootNode(Node): | |||||
If no match is found it returns None. | If no match is found it returns None. | ||||
""" | """ | ||||
return self.resource_from_path( | return self.resource_from_path( | ||||
self.source_folder.child(relative_path)) | |||||
self.source_folder.child(relative_path)) | |||||
def deploy_path_changed(self, item): | def deploy_path_changed(self, item): | ||||
""" | """ | ||||
@@ -320,7 +325,7 @@ class RootNode(Node): | |||||
node = node.add_child_node(h_folder) | node = node.add_child_node(h_folder) | ||||
self.node_map[unicode(h_folder)] = node | self.node_map[unicode(h_folder)] = node | ||||
logger.debug("Added node [%s] to [%s]" % ( | logger.debug("Added node [%s] to [%s]" % ( | ||||
node.relative_path, self.source_folder)) | |||||
node.relative_path, self.source_folder)) | |||||
return node | return node | ||||
@@ -350,11 +355,10 @@ class RootNode(Node): | |||||
self.resource_map[unicode(afile)] = resource | self.resource_map[unicode(afile)] = resource | ||||
relative_path = resource.relative_path | relative_path = resource.relative_path | ||||
resource.simple_copy = any(fnmatch.fnmatch(relative_path, pattern) | 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]" % | logger.debug("Added resource [%s] to [%s]" % | ||||
(resource.relative_path, self.source_folder)) | |||||
(resource.relative_path, self.source_folder)) | |||||
return resource | return resource | ||||
def load(self): | 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) | path = quote(path, safe) if safe is not None else quote(path) | ||||
return base.rstrip('/') + '/' + path.lstrip('/') | return base.rstrip('/') + '/' + path.lstrip('/') | ||||
class Site(object): | class Site(object): | ||||
""" | """ | ||||
Represents the site to be generated. | Represents the site to be generated. | ||||
@@ -420,9 +425,8 @@ class Site(object): | |||||
""" | """ | ||||
if self.config.needs_refresh(): | if self.config.needs_refresh(): | ||||
logger.debug("Refreshing config data") | 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): | def reload_if_needed(self): | ||||
""" | """ | ||||
@@ -453,13 +457,13 @@ class Site(object): | |||||
""" | """ | ||||
return _encode_path(self.config.base_url, path, self._safe_chars(safe)) | return _encode_path(self.config.base_url, path, self._safe_chars(safe)) | ||||
def media_url(self, path, safe=None): | def media_url(self, path, safe=None): | ||||
""" | """ | ||||
Returns the media url by appending the media base url from the config | Returns the media url by appending the media base url from the config | ||||
with the given path. The return value is url encoded. | 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): | def full_url(self, path, safe=None): | ||||
""" | """ | ||||
@@ -467,12 +471,12 @@ class Site(object): | |||||
configuration and returns the appropriate url. The return value | configuration and returns the appropriate url. The return value | ||||
is url encoded. | is url encoded. | ||||
""" | """ | ||||
if urlparse.urlparse(path)[:2] != ("",""): | |||||
if urlparse.urlparse(path)[:2] != ("", ""): | |||||
return path | return path | ||||
if self.is_media(path): | if self.is_media(path): | ||||
relative_path = File(path).get_relative_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) | return self.media_url(relative_path, safe) | ||||
else: | else: | ||||
return self.content_url(path, safe) | return self.content_url(path, safe) | ||||
@@ -482,4 +486,4 @@ class Site(object): | |||||
Given the relative path, determines if it is content or media. | Given the relative path, determines if it is content or media. | ||||
""" | """ | ||||
folder = self.content.source.child_folder(path) | 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): | class HtmlWrap(object): | ||||
""" | """ | ||||
A wrapper class for raw html. | A wrapper class for raw html. | ||||
@@ -36,7 +37,9 @@ class HtmlWrap(object): | |||||
return self.raw | return self.raw | ||||
return self.q(selector).html() | return self.q(selector).html() | ||||
class Template(object): | class Template(object): | ||||
""" | """ | ||||
Interface for hyde template engines. To use a different template engine, | Interface for hyde template engines. To use a different template engine, | ||||
the following interface must be implemented. | the following interface must be implemented. | ||||
@@ -50,7 +53,6 @@ class Template(object): | |||||
@abc.abstractmethod | @abc.abstractmethod | ||||
def configure(self, site, engine): | def configure(self, site, engine): | ||||
""" | """ | ||||
The site object should contain a config attribute. The config object | The site object should contain a config attribute. The config object | ||||
is a simple YAML object with required settings. The template | is a simple YAML object with required settings. The template | ||||
@@ -20,7 +20,7 @@ class TestAutoExtend(object): | |||||
def setUp(self): | def setUp(self): | ||||
TEST_SITE.make() | TEST_SITE.make() | ||||
TEST_SITE.parent.child_folder( | TEST_SITE.parent.child_folder( | ||||
'sites/test_jinja').copy_contents_to(TEST_SITE) | |||||
'sites/test_jinja').copy_contents_to(TEST_SITE) | |||||
def tearDown(self): | def tearDown(self): | ||||
TEST_SITE.delete() | TEST_SITE.delete() | ||||
@@ -33,7 +33,8 @@ class TestAutoExtend(object): | |||||
gen = Generator(s) | gen = Generator(s) | ||||
gen.generate_resource_at_path(bd.path) | gen.generate_resource_at_path(bd.path) | ||||
res = s.content.resource_from_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 | assert target.exists | ||||
text = target.read_all() | text = target.read_all() | ||||
q = PyQuery(text) | q = PyQuery(text) | ||||
@@ -44,7 +45,8 @@ class TestAutoExtend(object): | |||||
s.config.plugins = ['hyde.ext.plugins.meta.MetaPlugin', | s.config.plugins = ['hyde.ext.plugins.meta.MetaPlugin', | ||||
'hyde.ext.plugins.meta.AutoExtendPlugin', | 'hyde.ext.plugins.meta.AutoExtendPlugin', | ||||
'hyde.ext.plugins.text.BlockdownPlugin'] | '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 = """ | templ = """ | ||||
--- | --- | ||||
extends: base.html | extends: base.html | ||||
@@ -54,14 +56,13 @@ extends: base.html | |||||
====/title========""" | ====/title========""" | ||||
self.assert_extended(s, txt, templ) | self.assert_extended(s, txt, templ) | ||||
def test_can_auto_extend_with_default_blocks(self): | def test_can_auto_extend_with_default_blocks(self): | ||||
s = Site(TEST_SITE) | s = Site(TEST_SITE) | ||||
s.config.plugins = ['hyde.ext.plugins.meta.MetaPlugin', | s.config.plugins = ['hyde.ext.plugins.meta.MetaPlugin', | ||||
'hyde.ext.plugins.meta.AutoExtendPlugin', | 'hyde.ext.plugins.meta.AutoExtendPlugin', | ||||
'hyde.ext.plugins.text.BlockdownPlugin'] | '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 = """ | templ = """ | ||||
--- | --- | ||||
extends: base.html | extends: base.html | ||||
@@ -18,7 +18,7 @@ class TestBlockdown(object): | |||||
def setUp(self): | def setUp(self): | ||||
TEST_SITE.make() | TEST_SITE.make() | ||||
TEST_SITE.parent.child_folder( | TEST_SITE.parent.child_folder( | ||||
'sites/test_jinja').copy_contents_to(TEST_SITE) | |||||
'sites/test_jinja').copy_contents_to(TEST_SITE) | |||||
def tearDown(self): | def tearDown(self): | ||||
TEST_SITE.delete() | TEST_SITE.delete() | ||||
@@ -26,7 +26,8 @@ class TestBlockdown(object): | |||||
def test_can_parse_blockdown(self): | def test_can_parse_blockdown(self): | ||||
s = Site(TEST_SITE) | s = Site(TEST_SITE) | ||||
s.config.plugins = ['hyde.ext.plugins.text.BlockdownPlugin'] | 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 = """ | templ = """ | ||||
{%% extends "base.html" %%} | {%% extends "base.html" %%} | ||||
=====title======== | =====title======== | ||||
@@ -39,7 +40,8 @@ class TestBlockdown(object): | |||||
gen = Generator(s) | gen = Generator(s) | ||||
gen.generate_resource_at_path(bd.path) | gen.generate_resource_at_path(bd.path) | ||||
res = s.content.resource_from_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 | assert target.exists | ||||
text = target.read_all() | text = target.read_all() | ||||
q = PyQuery(text) | q = PyQuery(text) | ||||
@@ -12,14 +12,17 @@ from fswrap import File, Folder | |||||
COMBINE_SOURCE = File(__file__).parent.child_folder('combine') | COMBINE_SOURCE = File(__file__).parent.child_folder('combine') | ||||
TEST_SITE = File(__file__).parent.parent.child_folder('_test') | TEST_SITE = File(__file__).parent.parent.child_folder('_test') | ||||
class CombineTester(object): | class CombineTester(object): | ||||
def _test_combine(self, content): | def _test_combine(self, content): | ||||
s = Site(TEST_SITE) | s = Site(TEST_SITE) | ||||
s.config.plugins = [ | s.config.plugins = [ | ||||
'hyde.ext.plugins.meta.MetaPlugin', | 'hyde.ext.plugins.meta.MetaPlugin', | ||||
'hyde.ext.plugins.structure.CombinePlugin'] | 'hyde.ext.plugins.structure.CombinePlugin'] | ||||
source = TEST_SITE.child('content/media/js/script.js') | 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) | File(source).write(content) | ||||
gen = Generator(s) | gen = Generator(s) | ||||
@@ -29,12 +32,13 @@ class CombineTester(object): | |||||
text = target.read_all() | text = target.read_all() | ||||
return text, s | return text, s | ||||
class TestCombine(CombineTester): | class TestCombine(CombineTester): | ||||
def setUp(self): | def setUp(self): | ||||
TEST_SITE.make() | TEST_SITE.make() | ||||
TEST_SITE.parent.child_folder( | 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() | TEST_SITE.child_folder('content/media/js').make() | ||||
COMBINE_SOURCE.copy_contents_to(TEST_SITE.child('content/media/js')) | COMBINE_SOURCE.copy_contents_to(TEST_SITE.child('content/media/js')) | ||||
@@ -107,7 +111,7 @@ combine: | |||||
--- | --- | ||||
First line""") | First line""") | ||||
for i in range(1,4): | |||||
for i in range(1, 4): | |||||
assert not File(Folder(s.config.deploy_root_path). | assert not File(Folder(s.config.deploy_root_path). | ||||
child('media/js/script.%d.js' % i)).exists | child('media/js/script.%d.js' % i)).exists | ||||
@@ -117,7 +121,7 @@ class TestCombinePaths(CombineTester): | |||||
def setUp(self): | def setUp(self): | ||||
TEST_SITE.make() | TEST_SITE.make() | ||||
TEST_SITE.parent.child_folder( | 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() | TEST_SITE.child_folder('content/media/js').make() | ||||
JS = TEST_SITE.child_folder('content/scripts').make() | JS = TEST_SITE.child_folder('content/scripts').make() | ||||
S1 = JS.child_folder('s1').make() | S1 = JS.child_folder('s1').make() | ||||
@@ -17,10 +17,10 @@ class TestDepends(object): | |||||
def setUp(self): | def setUp(self): | ||||
TEST_SITE.make() | TEST_SITE.make() | ||||
TEST_SITE.parent.child_folder( | 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( | 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): | def tearDown(self): | ||||
TEST_SITE.delete() | TEST_SITE.delete() | ||||
@@ -40,6 +40,7 @@ depends: index.html | |||||
gen = Generator(s) | gen = Generator(s) | ||||
gen.load_site_if_needed() | gen.load_site_if_needed() | ||||
gen.load_template_if_needed() | gen.load_template_if_needed() | ||||
def dateformat(x): | def dateformat(x): | ||||
return x.strftime('%Y-%m-%d') | return x.strftime('%Y-%m-%d') | ||||
gen.template.env.filters['dateformat'] = dateformat | gen.template.env.filters['dateformat'] = dateformat | ||||
@@ -52,4 +53,4 @@ depends: index.html | |||||
assert 'helpers.html' in deps | assert 'helpers.html' in deps | ||||
assert 'layout.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): | class TestDrafts(object): | ||||
def setUp(self): | def setUp(self): | ||||
TEST_SITE.make() | TEST_SITE.make() | ||||
TEST_SITE.parent.child_folder( | 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 = TEST_SITE.child_file('content/blog/2013/may/draft-post.html') | ||||
draft.parent.make() | draft.parent.make() | ||||
draft.write(DRAFT_POST) | draft.write(DRAFT_POST) | ||||
@@ -50,7 +51,7 @@ class TestDrafts(object): | |||||
gen = Generator(s) | gen = Generator(s) | ||||
gen.generate_all() | gen.generate_all() | ||||
assert not s.config.deploy_root_path.child_file( | 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): | def test_drafts_are_published_in_development(self): | ||||
s = Site(TEST_SITE) | s = Site(TEST_SITE) | ||||
@@ -66,6 +67,4 @@ class TestDrafts(object): | |||||
gen = Generator(s) | gen = Generator(s) | ||||
gen.generate_all() | gen.generate_all() | ||||
assert s.config.deploy_root_path.child_file( | 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): | def setUp(self): | ||||
TEST_SITE.make() | TEST_SITE.make() | ||||
TEST_SITE.parent.child_folder( | TEST_SITE.parent.child_folder( | ||||
'sites/test_jinja').copy_contents_to(TEST_SITE) | |||||
'sites/test_jinja').copy_contents_to(TEST_SITE) | |||||
def tearDown(self): | def tearDown(self): | ||||
TEST_SITE.delete() | TEST_SITE.delete() | ||||
@@ -42,7 +42,8 @@ class TestFlattner(object): | |||||
gen.generate_all() | gen.generate_all() | ||||
assert not s.config.deploy_root_path.child_folder('blog').exists | 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): | def test_flattener_fixes_nodes(self): | ||||
s = Site(TEST_SITE) | s = Site(TEST_SITE) | ||||
@@ -64,5 +65,3 @@ class TestFlattner(object): | |||||
assert blog_node | assert blog_node | ||||
assert blog_node.url == '/' | assert blog_node.url == '/' | ||||
@@ -21,7 +21,7 @@ class TestGrouperSingleLevel(object): | |||||
def setUp(self): | def setUp(self): | ||||
TEST_SITE.make() | TEST_SITE.make() | ||||
TEST_SITE.parent.child_folder( | 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) | self.s = Site(TEST_SITE) | ||||
cfg = """ | cfg = """ | ||||
@@ -55,7 +55,8 @@ class TestGrouperSingleLevel(object): | |||||
SorterPlugin(self.s).begin_site() | SorterPlugin(self.s).begin_site() | ||||
GrouperPlugin(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.start = ['installation.html', 'overview.html', 'templating.html'] | ||||
self.plugins = ['plugins.html', 'tags.html'] | self.plugins = ['plugins.html', 'tags.html'] | ||||
self.section = self.all | self.section = self.all | ||||
@@ -72,7 +73,8 @@ class TestGrouperSingleLevel(object): | |||||
def test_site_grouper_walk_groups(self): | 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 len(groups) == 3 | ||||
assert 'section' in groups | assert 'section' in groups | ||||
assert 'start' in groups | assert 'start' in groups | ||||
@@ -81,7 +83,8 @@ class TestGrouperSingleLevel(object): | |||||
def test_walk_section_groups(self): | def test_walk_section_groups(self): | ||||
assert hasattr(self.s.content, 'walk_section_groups') | 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 len(groups) == 3 | ||||
assert 'section' in groups | assert 'section' in groups | ||||
assert 'start' in groups | assert 'start' in groups | ||||
@@ -93,15 +96,16 @@ class TestGrouperSingleLevel(object): | |||||
def test_walk_start_groups(self): | def test_walk_start_groups(self): | ||||
assert hasattr(self.s.content, 'walk_start_groups') | 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 len(groups) == 1 | ||||
assert 'start' in groups | assert 'start' in groups | ||||
def test_walk_plugins_groups(self): | def test_walk_plugins_groups(self): | ||||
assert hasattr(self.s.content, 'walk_plugins_groups') | 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 len(groups) == 1 | ||||
assert 'plugins' in groups | assert 'plugins' in groups | ||||
@@ -109,22 +113,24 @@ class TestGrouperSingleLevel(object): | |||||
assert hasattr(self.s.content, 'walk_resources_grouped_by_section') | 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 | assert resources == self.all | ||||
def test_walk_start_resources(self): | def test_walk_start_resources(self): | ||||
assert hasattr(self.s.content, 'walk_resources_grouped_by_start') | 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 | assert start_resources == self.start | ||||
def test_walk_plugins_resources(self): | def test_walk_plugins_resources(self): | ||||
assert hasattr(self.s.content, 'walk_resources_grouped_by_plugins') | 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 | assert plugin_resources == self.plugins | ||||
def test_resource_group(self): | def test_resource_group(self): | ||||
@@ -134,7 +140,8 @@ class TestGrouperSingleLevel(object): | |||||
for name, group in groups.items(): | for name, group in groups.items(): | ||||
pages = getattr(self, name) | pages = getattr(self, name) | ||||
for page in pages: | 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') | assert hasattr(res, 'section_group') | ||||
res_group = getattr(res, 'section_group') | res_group = getattr(res, 'section_group') | ||||
assert res_group == group | assert res_group == group | ||||
@@ -146,7 +153,8 @@ class TestGrouperSingleLevel(object): | |||||
for name, group in groups.items(): | for name, group in groups.items(): | ||||
pages = getattr(self, name) | pages = getattr(self, name) | ||||
for page in pages: | 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)() | res_groups = getattr(res, 'walk_%s_groups' % name)() | ||||
assert group in res_groups | assert group in res_groups | ||||
@@ -154,7 +162,8 @@ class TestGrouperSingleLevel(object): | |||||
resources = [] | resources = [] | ||||
for page in self.all: | 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 | index = 0 | ||||
for res in resources: | for res in resources: | ||||
@@ -174,7 +183,7 @@ class TestGrouperSingleLevel(object): | |||||
def test_nav_with_grouper(self): | def test_nav_with_grouper(self): | ||||
text =""" | |||||
text = """ | |||||
{% for group, resources in site.content.walk_section_groups() %} | {% for group, resources in site.content.walk_section_groups() %} | ||||
<ul> | <ul> | ||||
<li> | <li> | ||||
@@ -225,7 +234,7 @@ class TestGrouperSingleLevel(object): | |||||
gen = Generator(self.s) | gen = Generator(self.s) | ||||
gen.load_site_if_needed() | gen.load_site_if_needed() | ||||
gen.load_template_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) | assert_html_equals(out, expected) | ||||
def test_nav_with_grouper_sorted(self): | def test_nav_with_grouper_sorted(self): | ||||
@@ -264,7 +273,7 @@ class TestGrouperSingleLevel(object): | |||||
SorterPlugin(self.s).begin_site() | SorterPlugin(self.s).begin_site() | ||||
GrouperPlugin(self.s).begin_site() | GrouperPlugin(self.s).begin_site() | ||||
text =""" | |||||
text = """ | |||||
{% set sorted = site.grouper['section'].groups|sort(attribute='name') %} | {% set sorted = site.grouper['section'].groups|sort(attribute='name') %} | ||||
{% for group in sorted %} | {% for group in sorted %} | ||||
<ul> | <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 = Generator(self.s) | ||||
gen.load_site_if_needed() | gen.load_site_if_needed() | ||||
gen.load_template_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) | 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): | def setUp(self): | ||||
TEST_SITE.make() | TEST_SITE.make() | ||||
TEST_SITE.parent.child_folder( | 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')) | LESS_SOURCE.copy_contents_to(TEST_SITE.child('content/media/css')) | ||||
File(TEST_SITE.child('content/media/css/site.css')).delete() | File(TEST_SITE.child('content/media/css/site.css')).delete() | ||||
def tearDown(self): | def tearDown(self): | ||||
TEST_SITE.delete() | TEST_SITE.delete() | ||||
@@ -30,7 +29,8 @@ class TestLess(object): | |||||
s = Site(TEST_SITE) | s = Site(TEST_SITE) | ||||
s.config.plugins = ['hyde.ext.plugins.css.LessCSSPlugin'] | s.config.plugins = ['hyde.ext.plugins.css.LessCSSPlugin'] | ||||
source = TEST_SITE.child('content/media/css/site.less') | 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 = Generator(s) | ||||
gen.generate_resource_at_path(source) | gen.generate_resource_at_path(source) | ||||
@@ -12,6 +12,7 @@ from pyquery import PyQuery | |||||
TEST_SITE = File(__file__).parent.parent.child_folder('_test') | TEST_SITE = File(__file__).parent.parent.child_folder('_test') | ||||
def assert_valid_conversion(html): | def assert_valid_conversion(html): | ||||
assert html | assert html | ||||
q = PyQuery(html) | q = PyQuery(html) | ||||
@@ -25,18 +26,17 @@ def assert_valid_conversion(html): | |||||
assert '.' not in q.text() | assert '.' not in q.text() | ||||
assert '/' not in q.text() | assert '/' not in q.text() | ||||
class TestMarkings(object): | class TestMarkings(object): | ||||
def setUp(self): | def setUp(self): | ||||
TEST_SITE.make() | TEST_SITE.make() | ||||
TEST_SITE.parent.child_folder( | TEST_SITE.parent.child_folder( | ||||
'sites/test_jinja').copy_contents_to(TEST_SITE) | |||||
'sites/test_jinja').copy_contents_to(TEST_SITE) | |||||
def tearDown(self): | def tearDown(self): | ||||
TEST_SITE.delete() | TEST_SITE.delete() | ||||
def test_mark(self): | def test_mark(self): | ||||
text = u""" | text = u""" | ||||
=== | === | ||||
@@ -20,7 +20,7 @@ class TestMeta(object): | |||||
def setUp(self): | def setUp(self): | ||||
TEST_SITE.make() | TEST_SITE.make() | ||||
TEST_SITE.parent.child_folder( | TEST_SITE.parent.child_folder( | ||||
'sites/test_jinja').copy_contents_to(TEST_SITE) | |||||
'sites/test_jinja').copy_contents_to(TEST_SITE) | |||||
def tearDown(self): | def tearDown(self): | ||||
TEST_SITE.delete() | TEST_SITE.delete() | ||||
@@ -76,8 +76,8 @@ Heading 2 | |||||
def test_can_load_front_matter(self): | def test_can_load_front_matter(self): | ||||
d = {'title': 'A nice title', | d = {'title': 'A nice title', | ||||
'author': 'Lakshmi Vyas', | |||||
'twitter': 'lakshmivyas'} | |||||
'author': 'Lakshmi Vyas', | |||||
'twitter': 'lakshmivyas'} | |||||
text = """ | text = """ | ||||
--- | --- | ||||
title: %(title)s | title: %(title)s | ||||
@@ -118,8 +118,8 @@ twitter: %(twitter)s | |||||
def test_can_load_from_node_meta(self): | def test_can_load_from_node_meta(self): | ||||
d = {'title': 'A nice title', | d = {'title': 'A nice title', | ||||
'author': 'Lakshmi Vyas', | |||||
'twitter': 'lakshmivyas'} | |||||
'author': 'Lakshmi Vyas', | |||||
'twitter': 'lakshmivyas'} | |||||
text = """ | text = """ | ||||
=== | === | ||||
title: Even nicer title | title: Even nicer title | ||||
@@ -161,7 +161,7 @@ title: Even nicer title | |||||
def test_can_load_from_site_meta(self): | def test_can_load_from_site_meta(self): | ||||
d = {'title': 'A nice title', | d = {'title': 'A nice title', | ||||
'author': 'Lakshmi Vyas'} | |||||
'author': 'Lakshmi Vyas'} | |||||
text = """ | text = """ | ||||
--- | --- | ||||
title: Even nicer title | title: Even nicer title | ||||
@@ -205,15 +205,14 @@ title: Even nicer title | |||||
assert v in q("span." + k).text() | assert v in q("span." + k).text() | ||||
assert q("span.title").text() == "Even nicer title" | assert q("span.title").text() == "Even nicer title" | ||||
def test_multiple_levels(self): | def test_multiple_levels(self): | ||||
page_d = {'title': 'An even nicer title'} | page_d = {'title': 'An even nicer title'} | ||||
blog_d = {'author': 'Laks'} | 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', | site_d = {'author': 'Lakshmi', | ||||
'twitter': 'lakshmivyas', | 'twitter': 'lakshmivyas', | ||||
@@ -256,7 +255,8 @@ title: %(title)s | |||||
for k, v in expected.items(): | for k, v in expected.items(): | ||||
assert hasattr(res.meta, k) | assert hasattr(res.meta, k) | ||||
assert getattr(res.meta, k) == v | 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() | text = target.read_all() | ||||
q = PyQuery(text) | q = PyQuery(text) | ||||
for k, v in expected.items(): | for k, v in expected.items(): | ||||
@@ -19,12 +19,11 @@ class TestOptipng(object): | |||||
def setUp(self): | def setUp(self): | ||||
TEST_SITE.make() | TEST_SITE.make() | ||||
TEST_SITE.parent.child_folder( | 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 = TEST_SITE.child_folder('content/media/images') | ||||
IMAGES.make() | IMAGES.make() | ||||
OPTIPNG_SOURCE.copy_contents_to(IMAGES) | OPTIPNG_SOURCE.copy_contents_to(IMAGES) | ||||
def tearDown(self): | def tearDown(self): | ||||
TEST_SITE.delete() | TEST_SITE.delete() | ||||
@@ -33,8 +32,10 @@ class TestOptipng(object): | |||||
s.config.mode = "production" | s.config.mode = "production" | ||||
s.config.plugins = ['hyde.ext.plugins.images.OptiPNGPlugin'] | s.config.plugins = ['hyde.ext.plugins.images.OptiPNGPlugin'] | ||||
s.config.optipng = Expando(dict(args=dict(quiet=""))) | 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 = Generator(s) | ||||
gen.generate_resource_at_path(source) | gen.generate_resource_at_path(source) | ||||
assert target.exists | assert target.exists | ||||
@@ -13,12 +13,13 @@ from fswrap import File | |||||
TEST_SITE = File(__file__).parent.parent.child_folder('_test') | TEST_SITE = File(__file__).parent.parent.child_folder('_test') | ||||
class TestPaginator(object): | class TestPaginator(object): | ||||
def setUp(self): | def setUp(self): | ||||
TEST_SITE.make() | TEST_SITE.make() | ||||
TEST_SITE.parent.child_folder( | 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.s = Site(TEST_SITE) | ||||
self.deploy = TEST_SITE.child_folder('deploy') | self.deploy = TEST_SITE.child_folder('deploy') | ||||
@@ -27,14 +28,12 @@ class TestPaginator(object): | |||||
self.gen.load_template_if_needed() | self.gen.load_template_if_needed() | ||||
self.gen.generate_all() | self.gen.generate_all() | ||||
def tearDown(self): | def tearDown(self): | ||||
TEST_SITE.delete() | TEST_SITE.delete() | ||||
def test_pages_of_one(self): | def test_pages_of_one(self): | ||||
pages = ['pages_of_one.txt', 'page2/pages_of_one.txt', | 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] | files = [File(self.deploy.child(p)) for p in pages] | ||||
for f in files: | for f in files: | ||||
assert f.exists | assert f.exists | ||||
@@ -42,7 +41,6 @@ class TestPaginator(object): | |||||
page5 = File(self.deploy.child('page5/pages_of_one.txt')) | page5 = File(self.deploy.child('page5/pages_of_one.txt')) | ||||
assert not page5.exists | assert not page5.exists | ||||
def test_pages_of_one_content(self): | def test_pages_of_one_content(self): | ||||
expected_page1_content = dedent('''\ | expected_page1_content = dedent('''\ | ||||
Another Sad Post | Another Sad Post | ||||
@@ -77,7 +75,6 @@ class TestPaginator(object): | |||||
content = File(page4).read_all() | content = File(page4).read_all() | ||||
assert expected_page4_content == content | assert expected_page4_content == content | ||||
def test_pages_of_ten(self): | def test_pages_of_ten(self): | ||||
page1 = self.deploy.child('pages_of_ten.txt') | page1 = self.deploy.child('pages_of_ten.txt') | ||||
page2 = self.deploy.child('page2/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 File(page1).exists | ||||
assert not File(page2).exists | assert not File(page2).exists | ||||
def test_pages_of_ten_depends(self): | def test_pages_of_ten_depends(self): | ||||
depends = self.gen.deps['pages_of_ten.txt'] | depends = self.gen.deps['pages_of_ten.txt'] | ||||
@@ -96,7 +92,6 @@ class TestPaginator(object): | |||||
assert 'blog/angry-post.html' in depends | assert 'blog/angry-post.html' in depends | ||||
assert 'blog/happy-post.html' in depends | assert 'blog/happy-post.html' in depends | ||||
def test_pages_of_ten_content(self): | def test_pages_of_ten_content(self): | ||||
expected_content = dedent('''\ | expected_content = dedent('''\ | ||||
Another Sad Post | Another Sad Post | ||||
@@ -109,7 +104,6 @@ class TestPaginator(object): | |||||
content = File(page).read_all() | content = File(page).read_all() | ||||
assert expected_content == content | assert expected_content == content | ||||
def test_pages_of_one_depends(self): | def test_pages_of_one_depends(self): | ||||
depends = self.gen.deps['pages_of_one.txt'] | depends = self.gen.deps['pages_of_one.txt'] | ||||
@@ -120,7 +114,6 @@ class TestPaginator(object): | |||||
assert 'blog/angry-post.html' in depends | assert 'blog/angry-post.html' in depends | ||||
assert 'blog/happy-post.html' in depends | assert 'blog/happy-post.html' in depends | ||||
def test_custom_file_pattern(self): | def test_custom_file_pattern(self): | ||||
page1 = self.deploy.child('custom_file_pattern.txt') | page1 = self.deploy.child('custom_file_pattern.txt') | ||||
page2 = self.deploy.child('custom_file_pattern-2.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') | RJS_SOURCE = File(__file__).parent.child_folder('requirejs') | ||||
TEST_SITE = File(__file__).parent.parent.child_folder('_test') | TEST_SITE = File(__file__).parent.parent.child_folder('_test') | ||||
class TestRequireJS(object): | class TestRequireJS(object): | ||||
def setUp(self): | def setUp(self): | ||||
TEST_SITE.make() | 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')) | RJS_SOURCE.copy_contents_to(TEST_SITE.child('content/media/js')) | ||||
File(TEST_SITE.child('content/media/js/app.js')).delete() | File(TEST_SITE.child('content/media/js/app.js')).delete() | ||||
@@ -26,7 +29,8 @@ class TestRequireJS(object): | |||||
s = Site(TEST_SITE) | s = Site(TEST_SITE) | ||||
s.config.plugins = ['hyde.ext.plugins.js.RequireJSPlugin'] | s.config.plugins = ['hyde.ext.plugins.js.RequireJSPlugin'] | ||||
source = TEST_SITE.child('content/media/js/rjs.conf') | 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 = Generator(s) | ||||
gen.generate_resource_at_path(source) | gen.generate_resource_at_path(source) | ||||
@@ -19,21 +19,20 @@ class TestSassyCSS(object): | |||||
def setUp(self): | def setUp(self): | ||||
TEST_SITE.make() | TEST_SITE.make() | ||||
TEST_SITE.parent.child_folder( | 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')) | SCSS_SOURCE.copy_contents_to(TEST_SITE.child('content/media/css')) | ||||
File(TEST_SITE.child('content/media/css/site.css')).delete() | File(TEST_SITE.child('content/media/css/site.css')).delete() | ||||
def tearDown(self): | def tearDown(self): | ||||
TEST_SITE.delete() | TEST_SITE.delete() | ||||
def test_scss(self): | def test_scss(self): | ||||
s = Site(TEST_SITE) | s = Site(TEST_SITE) | ||||
s.config.mode = 'prod' | s.config.mode = 'prod' | ||||
s.config.plugins = ['hyde.ext.plugins.css.SassyCSSPlugin'] | s.config.plugins = ['hyde.ext.plugins.css.SassyCSSPlugin'] | ||||
source = TEST_SITE.child('content/media/css/site.scss') | 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 = Generator(s) | ||||
gen.generate_resource_at_path(source) | gen.generate_resource_at_path(source) | ||||
@@ -41,4 +40,3 @@ class TestSassyCSS(object): | |||||
text = target.read_all() | text = target.read_all() | ||||
expected_text = File(SCSS_SOURCE.child('expected-site.css')).read_all() | expected_text = File(SCSS_SOURCE.child('expected-site.css')).read_all() | ||||
assert_no_diff(expected_text, text) | 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') | STYLUS_SOURCE = File(__file__).parent.child_folder('stylus') | ||||
TEST_SITE = File(__file__).parent.parent.child_folder('_test') | TEST_SITE = File(__file__).parent.parent.child_folder('_test') | ||||
class TestStylus(object): | class TestStylus(object): | ||||
def setUp(self): | def setUp(self): | ||||
TEST_SITE.make() | TEST_SITE.make() | ||||
TEST_SITE.parent.child_folder( | 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')) | STYLUS_SOURCE.copy_contents_to(TEST_SITE.child('content/media/css')) | ||||
File(TEST_SITE.child('content/media/css/site.css')).delete() | File(TEST_SITE.child('content/media/css/site.css')).delete() | ||||
def tearDown(self): | def tearDown(self): | ||||
TEST_SITE.delete() | TEST_SITE.delete() | ||||
@@ -34,13 +34,15 @@ class TestStylus(object): | |||||
if File(path).exists: | if File(path).exists: | ||||
s.config.stylus = Expando(dict(app=path)) | s.config.stylus = Expando(dict(app=path)) | ||||
source = TEST_SITE.child('content/media/css/site.styl') | 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 = Generator(s) | ||||
gen.generate_resource_at_path(source) | gen.generate_resource_at_path(source) | ||||
assert target.exists | assert target.exists | ||||
text = target.read_all() | 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() | assert text.strip() == expected_text.strip() | ||||
def test_can_compress_with_stylus(self): | def test_can_compress_with_stylus(self): | ||||
@@ -52,11 +54,13 @@ class TestStylus(object): | |||||
if File(path).exists: | if File(path).exists: | ||||
s.config.stylus = Expando(dict(app=path)) | s.config.stylus = Expando(dict(app=path)) | ||||
source = TEST_SITE.child('content/media/css/site.styl') | 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 = Generator(s) | ||||
gen.generate_resource_at_path(source) | gen.generate_resource_at_path(source) | ||||
assert target.exists | assert target.exists | ||||
text = target.read_all() | 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() | assert text.strip() == expected_text.strip() |
@@ -18,13 +18,11 @@ class TestSyntext(object): | |||||
def setUp(self): | def setUp(self): | ||||
TEST_SITE.make() | TEST_SITE.make() | ||||
TEST_SITE.parent.child_folder( | TEST_SITE.parent.child_folder( | ||||
'sites/test_jinja').copy_contents_to(TEST_SITE) | |||||
'sites/test_jinja').copy_contents_to(TEST_SITE) | |||||
def tearDown(self): | def tearDown(self): | ||||
TEST_SITE.delete() | TEST_SITE.delete() | ||||
def test_syntext(self): | def test_syntext(self): | ||||
text = u""" | text = u""" | ||||
~~~~~~~~css~~~~~~~ | ~~~~~~~~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): | def setUp(self): | ||||
TEST_SITE.make() | TEST_SITE.make() | ||||
TEST_SITE.parent.child_folder( | TEST_SITE.parent.child_folder( | ||||
'sites/test_jinja').copy_contents_to(TEST_SITE) | |||||
'sites/test_jinja').copy_contents_to(TEST_SITE) | |||||
def tearDown(self): | def tearDown(self): | ||||
TEST_SITE.delete() | TEST_SITE.delete() | ||||
def test_textlinks(self): | def test_textlinks(self): | ||||
d = { | d = { | ||||
'objects': 'template/variables', | 'objects': 'template/variables', | ||||
@@ -34,8 +32,8 @@ class TestTextlinks(object): | |||||
{%% markdown %%} | {%% markdown %%} | ||||
[[!!img/hyde-logo.png]] | [[!!img/hyde-logo.png]] | ||||
* [Rich object model][hyde objects] and | * [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. | * Configurable [sorting][], filtering and grouping support. | ||||
[hyde objects]: [[ %(objects)s ]] | [hyde objects]: [[ %(objects)s ]] | ||||
@@ -57,5 +55,5 @@ class TestTextlinks(object): | |||||
assert html | assert html | ||||
for name, path in d.items(): | 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 | assert '/media/img/hyde-logo.png' in html |
@@ -20,12 +20,11 @@ class TestUglify(object): | |||||
def setUp(self): | def setUp(self): | ||||
TEST_SITE.make() | TEST_SITE.make() | ||||
TEST_SITE.parent.child_folder( | 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 = TEST_SITE.child_folder('content/media/js') | ||||
JS.make() | JS.make() | ||||
UGLIFY_SOURCE.copy_contents_to(JS) | UGLIFY_SOURCE.copy_contents_to(JS) | ||||
def tearDown(self): | def tearDown(self): | ||||
TEST_SITE.delete() | TEST_SITE.delete() | ||||
@@ -34,7 +33,8 @@ class TestUglify(object): | |||||
s.config.plugins = ['hyde.ext.plugins.js.UglifyPlugin'] | s.config.plugins = ['hyde.ext.plugins.js.UglifyPlugin'] | ||||
s.config.mode = "production" | s.config.mode = "production" | ||||
source = TEST_SITE.child('content/media/js/jquery.js') | 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 = Generator(s) | ||||
gen.generate_resource_at_path(source) | gen.generate_resource_at_path(source) | ||||
@@ -48,9 +48,11 @@ class TestUglify(object): | |||||
s = Site(TEST_SITE) | s = Site(TEST_SITE) | ||||
s.config.plugins = ['hyde.ext.plugins.js.UglifyPlugin'] | s.config.plugins = ['hyde.ext.plugins.js.UglifyPlugin'] | ||||
s.config.mode = "production" | 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') | 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 = Generator(s) | ||||
gen.generate_resource_at_path(source) | gen.generate_resource_at_path(source) | ||||
@@ -65,7 +67,8 @@ class TestUglify(object): | |||||
s.config.plugins = ['hyde.ext.plugins.js.UglifyPlugin'] | s.config.plugins = ['hyde.ext.plugins.js.UglifyPlugin'] | ||||
s.config.mode = "dev" | s.config.mode = "dev" | ||||
source = TEST_SITE.child('content/media/js/jquery.js') | 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 = Generator(s) | ||||
gen.generate_resource_at_path(source) | gen.generate_resource_at_path(source) | ||||
@@ -74,5 +77,3 @@ class TestUglify(object): | |||||
# TODO: Very fragile. Better comparison needed. | # TODO: Very fragile. Better comparison needed. | ||||
text = target.read_all() | text = target.read_all() | ||||
assert_no_diff(expected, text) | assert_no_diff(expected, text) | ||||
@@ -19,14 +19,14 @@ class TestUrlCleaner(object): | |||||
def setUp(self): | def setUp(self): | ||||
TEST_SITE.make() | TEST_SITE.make() | ||||
TEST_SITE.parent.child_folder( | TEST_SITE.parent.child_folder( | ||||
'sites/test_jinja').copy_contents_to(TEST_SITE) | |||||
'sites/test_jinja').copy_contents_to(TEST_SITE) | |||||
def tearDown(self): | def tearDown(self): | ||||
TEST_SITE.delete() | TEST_SITE.delete() | ||||
def test_url_cleaner(self): | def test_url_cleaner(self): | ||||
s = Site(TEST_SITE) | |||||
cfg = """ | |||||
s = Site(TEST_SITE) | |||||
cfg = """ | |||||
plugins: | plugins: | ||||
- hyde.ext.plugins.urls.UrlCleanerPlugin | - hyde.ext.plugins.urls.UrlCleanerPlugin | ||||
urlcleaner: | urlcleaner: | ||||
@@ -36,24 +36,26 @@ class TestUrlCleaner(object): | |||||
- html | - html | ||||
append_slash: true | 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" %} | {% extends "base.html" %} | ||||
{% block main %} | {% block main %} | ||||
<a id="index" href="{{ content_url('about.html') }}"></a> | <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 %} | {% 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): | 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. | This file was produced with infinite love, care & sweat. | ||||
Please dont copy. If you have to, please drop me a note. | 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 | Use nose | ||||
`$ pip install nose` | `$ pip install nose` | ||||
@@ -15,11 +15,13 @@ from fswrap import File, Folder | |||||
TEST_SITE = File(__file__).parent.child_folder('_test') | TEST_SITE = File(__file__).parent.child_folder('_test') | ||||
class TestGenerator(object): | class TestGenerator(object): | ||||
def setUp(self): | def setUp(self): | ||||
TEST_SITE.make() | 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): | def tearDown(self): | ||||
TEST_SITE.delete() | TEST_SITE.delete() | ||||
@@ -38,7 +40,8 @@ class TestGenerator(object): | |||||
def test_generate_resource_from_path_with_is_processable_false(self): | def test_generate_resource_from_path_with_is_processable_false(self): | ||||
site = Site(TEST_SITE) | site = Site(TEST_SITE) | ||||
site.load() | 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 | resource.is_processable = False | ||||
gen = Generator(site) | gen = Generator(site) | ||||
gen.generate_resource_at_path(TEST_SITE.child('content/about.html')) | 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): | def test_generate_resource_from_path_with_uses_template_false(self): | ||||
site = Site(TEST_SITE) | site = Site(TEST_SITE) | ||||
site.load() | 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 | resource.uses_template = False | ||||
gen = Generator(site) | gen = Generator(site) | ||||
gen.generate_resource_at_path(TEST_SITE.child('content/about.html')) | 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): | def test_generate_resource_from_path_with_deploy_override(self): | ||||
site = Site(TEST_SITE) | site = Site(TEST_SITE) | ||||
site.load() | 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' | resource.relative_deploy_path = 'about/index.html' | ||||
gen = Generator(site) | gen = Generator(site) | ||||
gen.generate_resource_at_path(TEST_SITE.child('content/about.html')) | 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 | assert about.exists | ||||
text = about.read_all() | text = about.read_all() | ||||
q = PyQuery(text) | q = PyQuery(text) | ||||
@@ -74,7 +80,8 @@ class TestGenerator(object): | |||||
def test_has_resource_changed(self): | def test_has_resource_changed(self): | ||||
site = Site(TEST_SITE) | site = Site(TEST_SITE) | ||||
site.load() | 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 = Generator(site) | ||||
gen.generate_all() | gen.generate_all() | ||||
import time | import time | ||||
@@ -110,7 +117,8 @@ class TestGenerator(object): | |||||
{% endblock %} | {% endblock %} | ||||
""" | """ | ||||
site.load() | 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 = Generator(site) | ||||
resource.source_file.write(text) | resource.source_file.write(text) | ||||
gen.generate_all() | gen.generate_all() | ||||
@@ -150,7 +158,8 @@ class TestGenerator(object): | |||||
""" | """ | ||||
File(TEST_SITE.child('nav.yaml')).write(nav) | File(TEST_SITE.child('nav.yaml')).write(nav) | ||||
site.load() | 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 = Generator(site) | ||||
resource.source_file.write(text) | resource.source_file.write(text) | ||||
gen.generate_all() | gen.generate_all() | ||||
@@ -192,7 +201,8 @@ main: | |||||
""" | """ | ||||
File(TEST_SITE.child('nav.yaml')).write(nav) | File(TEST_SITE.child('nav.yaml')).write(nav) | ||||
site.load() | 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 = Generator(site) | ||||
resource.source_file.write(text) | resource.source_file.write(text) | ||||
gen.generate_all() | gen.generate_all() | ||||
@@ -257,4 +267,3 @@ main: | |||||
left = File(site.config.deploy_root_path.child(f1.name)).read_all() | left = File(site.config.deploy_root_path.child(f1.name)).read_all() | ||||
right = File(site.config.deploy_root_path.child(f2.name)).read_all() | right = File(site.config.deploy_root_path.child(f2.name)).read_all() | ||||
assert left == right | 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 = File(__file__).parent.child_folder('_test') | ||||
TEST_SITE_AT_USER = Folder('~/_test') | TEST_SITE_AT_USER = Folder('~/_test') | ||||
@nottest | @nottest | ||||
def create_test_site(): | def create_test_site(): | ||||
TEST_SITE.make() | TEST_SITE.make() | ||||
@nottest | @nottest | ||||
def delete_test_site(): | def delete_test_site(): | ||||
TEST_SITE.delete() | TEST_SITE.delete() | ||||
@nottest | @nottest | ||||
def create_test_site_at_user(): | def create_test_site_at_user(): | ||||
TEST_SITE_AT_USER.make() | TEST_SITE_AT_USER.make() | ||||
@nottest | @nottest | ||||
def delete_test_site_at_user(): | def delete_test_site_at_user(): | ||||
TEST_SITE_AT_USER.delete() | TEST_SITE_AT_USER.delete() | ||||
@raises(HydeException) | @raises(HydeException) | ||||
@with_setup(create_test_site, delete_test_site) | @with_setup(create_test_site, delete_test_site) | ||||
def test_ensure_exception_when_site_yaml_exists(): | 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") | File(TEST_SITE.child('site.yaml')).write("Hey") | ||||
e.run(e.parse(['-s', unicode(TEST_SITE), 'create'])) | e.run(e.parse(['-s', unicode(TEST_SITE), 'create'])) | ||||
@raises(HydeException) | @raises(HydeException) | ||||
@with_setup(create_test_site, delete_test_site) | @with_setup(create_test_site, delete_test_site) | ||||
def test_ensure_exception_when_content_folder_exists(): | 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() | TEST_SITE.child_folder('content').make() | ||||
e.run(e.parse(['-s', unicode(TEST_SITE), 'create'])) | e.run(e.parse(['-s', unicode(TEST_SITE), 'create'])) | ||||
@raises(HydeException) | @raises(HydeException) | ||||
@with_setup(create_test_site, delete_test_site) | @with_setup(create_test_site, delete_test_site) | ||||
def test_ensure_exception_when_layout_folder_exists(): | 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() | TEST_SITE.child_folder('layout').make() | ||||
e.run(e.parse(['-s', unicode(TEST_SITE), 'create'])) | e.run(e.parse(['-s', unicode(TEST_SITE), 'create'])) | ||||
@with_setup(create_test_site, delete_test_site) | @with_setup(create_test_site, delete_test_site) | ||||
def test_ensure_no_exception_when_empty_site_exists(): | def test_ensure_no_exception_when_empty_site_exists(): | ||||
e = Engine(raise_exceptions=True) | e = Engine(raise_exceptions=True) | ||||
e.run(e.parse(['-s', unicode(TEST_SITE), 'create'])) | e.run(e.parse(['-s', unicode(TEST_SITE), 'create'])) | ||||
verify_site_contents(TEST_SITE, Layout.find_layout()) | verify_site_contents(TEST_SITE, Layout.find_layout()) | ||||
@with_setup(create_test_site, delete_test_site) | @with_setup(create_test_site, delete_test_site) | ||||
def test_ensure_no_exception_when_forced(): | def test_ensure_no_exception_when_forced(): | ||||
e = Engine(raise_exceptions=True) | 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'])) | e.run(e.parse(['-s', unicode(TEST_SITE), 'create', '-f'])) | ||||
verify_site_contents(TEST_SITE, Layout.find_layout()) | verify_site_contents(TEST_SITE, Layout.find_layout()) | ||||
@with_setup(create_test_site, delete_test_site) | @with_setup(create_test_site, delete_test_site) | ||||
def test_ensure_no_exception_when_sitepath_does_not_exist(): | def test_ensure_no_exception_when_sitepath_does_not_exist(): | ||||
e = Engine(raise_exceptions=True) | 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'])) | e.run(e.parse(['-s', unicode(TEST_SITE), 'create', '-f'])) | ||||
verify_site_contents(TEST_SITE, Layout.find_layout()) | verify_site_contents(TEST_SITE, Layout.find_layout()) | ||||
@with_setup(create_test_site_at_user, delete_test_site_at_user) | @with_setup(create_test_site_at_user, delete_test_site_at_user) | ||||
def test_ensure_can_create_site_at_user(): | def test_ensure_can_create_site_at_user(): | ||||
e = Engine(raise_exceptions=True) | 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'])) | e.run(e.parse(['-s', unicode(TEST_SITE_AT_USER), 'create', '-f'])) | ||||
verify_site_contents(TEST_SITE_AT_USER, Layout.find_layout()) | verify_site_contents(TEST_SITE_AT_USER, Layout.find_layout()) | ||||
@nottest | @nottest | ||||
def verify_site_contents(site, layout): | def verify_site_contents(site, layout): | ||||
assert site.exists | assert site.exists | ||||
assert site.child_folder('layout').exists | assert site.child_folder('layout').exists | ||||
assert File(site.child('info.yaml')).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()) | actual = map(lambda f: f.get_relative_path(site), site.walker.walk_all()) | ||||
assert actual | assert actual | ||||
assert expected | assert expected | ||||
@@ -104,9 +117,9 @@ def verify_site_contents(site, layout): | |||||
actual.sort() | actual.sort() | ||||
assert actual == expected | assert actual == expected | ||||
@raises(HydeException) | @raises(HydeException) | ||||
@with_setup(create_test_site, delete_test_site) | @with_setup(create_test_site, delete_test_site) | ||||
def test_ensure_exception_when_layout_is_invalid(): | def test_ensure_exception_when_layout_is_invalid(): | ||||
e = Engine(raise_exceptions=True) | e = Engine(raise_exceptions=True) | ||||
e.run(e.parse(['-s', unicode(TEST_SITE), 'create', '-l', 'junk'])) | e.run(e.parse(['-s', unicode(TEST_SITE), 'create', '-l', 'junk'])) | ||||
@@ -24,6 +24,7 @@ import yaml | |||||
ROOT = File(__file__).parent | ROOT = File(__file__).parent | ||||
JINJA2 = ROOT.child_folder('templates/jinja2') | JINJA2 = ROOT.child_folder('templates/jinja2') | ||||
class Article(object): | class Article(object): | ||||
def __init__(self, id): | def __init__(self, id): | ||||
@@ -33,12 +34,14 @@ class Article(object): | |||||
self.user = choice(users) | self.user = choice(users) | ||||
self.body = generate_lorem_ipsum() | self.body = generate_lorem_ipsum() | ||||
self.pub_date = datetime.utcfromtimestamp( | self.pub_date = datetime.utcfromtimestamp( | ||||
randrange(10 ** 9, 2 * 10 ** 9)) | |||||
randrange(10 ** 9, 2 * 10 ** 9)) | |||||
self.published = True | self.published = True | ||||
def dateformat(x): | def dateformat(x): | ||||
return x.strftime('%Y-%m-%d') | return x.strftime('%Y-%m-%d') | ||||
class User(object): | class User(object): | ||||
def __init__(self, username): | def __init__(self, username): | ||||
@@ -59,6 +62,7 @@ navigation = [ | |||||
context = dict(users=users, articles=articles, page_navigation=navigation) | context = dict(users=users, articles=articles, page_navigation=navigation) | ||||
def test_render(): | def test_render(): | ||||
""" | """ | ||||
Uses pyquery to test the html structure for validity | 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 p.meta").length == 20 | ||||
assert actual("div.article div.text").length == 20 | assert actual("div.article div.text").length == 20 | ||||
def test_typogrify(): | def test_typogrify(): | ||||
source = """ | source = """ | ||||
{%filter typogrify%} | {%filter typogrify%} | ||||
@@ -89,6 +94,7 @@ def test_typogrify(): | |||||
html = t.render(source, {}).strip() | html = t.render(source, {}).strip() | ||||
assert html == u'One <span class="amp">&</span> two' | assert html == u'One <span class="amp">&</span> two' | ||||
def test_spaceless(): | def test_spaceless(): | ||||
source = """ | source = """ | ||||
{%spaceless%} | {%spaceless%} | ||||
@@ -124,6 +130,7 @@ def test_spaceless(): | |||||
""" | """ | ||||
assert html.strip() == expected.strip() | assert html.strip() == expected.strip() | ||||
def test_asciidoc(): | def test_asciidoc(): | ||||
source = """ | source = """ | ||||
{%asciidoc%} | {%asciidoc%} | ||||
@@ -146,6 +153,7 @@ def test_asciidoc(): | |||||
assert q("li:nth-child(2)").text().strip() == "test2" | assert q("li:nth-child(2)").text().strip() == "test2" | ||||
assert q("li:nth-child(3)").text().strip() == "test3" | assert q("li:nth-child(3)").text().strip() == "test3" | ||||
def test_markdown(): | def test_markdown(): | ||||
source = """ | source = """ | ||||
{%markdown%} | {%markdown%} | ||||
@@ -157,6 +165,7 @@ def test_markdown(): | |||||
html = t.render(source, {}).strip() | html = t.render(source, {}).strip() | ||||
assert html == u'<h3>Heading 3</h3>' | assert html == u'<h3>Heading 3</h3>' | ||||
def test_restructuredtext(): | def test_restructuredtext(): | ||||
source = """ | source = """ | ||||
{% restructuredtext %} | {% restructuredtext %} | ||||
@@ -171,6 +180,7 @@ Hello | |||||
<h1 class="title">Hello</h1> | <h1 class="title">Hello</h1> | ||||
</div>""", html | </div>""", html | ||||
def test_restructuredtext_with_sourcecode(): | def test_restructuredtext_with_sourcecode(): | ||||
source = """ | source = """ | ||||
{% restructuredtext %} | {% restructuredtext %} | ||||
@@ -188,24 +198,30 @@ See `Example`_ | |||||
{% endrestructuredtext %} | {% endrestructuredtext %} | ||||
""" | """ | ||||
expected = """ | |||||
expected = (""" | |||||
<div class="document" id="code"> | <div class="document" id="code"> | ||||
<h1 class="title">Code</h1> | <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> | </pre></div> | ||||
<p>See <a class="reference external" href="example.html">Example</a></p> | <p>See <a class="reference external" href="example.html">Example</a></p> | ||||
</div> | </div> | ||||
""" | |||||
""") | |||||
t = Jinja2Template(JINJA2.path) | t = Jinja2Template(JINJA2.path) | ||||
s = Site(JINJA2.path) | s = Site(JINJA2.path) | ||||
c = Config(JINJA2.path, config_dict=dict( | c = Config(JINJA2.path, config_dict=dict( | ||||
restructuredtext=dict(highlight_source=True))) | |||||
restructuredtext=dict(highlight_source=True))) | |||||
s.config = c | s.config = c | ||||
t.configure(s) | t.configure(s) | ||||
html = t.render(source, {}).strip() | html = t.render(source, {}).strip() | ||||
assert html.strip() == expected.strip() | assert html.strip() == expected.strip() | ||||
def test_markdown_with_extensions(): | def test_markdown_with_extensions(): | ||||
source = """ | source = """ | ||||
{%markdown%} | {%markdown%} | ||||
@@ -215,13 +231,15 @@ def test_markdown_with_extensions(): | |||||
""" | """ | ||||
t = Jinja2Template(JINJA2.path) | t = Jinja2Template(JINJA2.path) | ||||
s = Site(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 | s.config = c | ||||
t.configure(s) | t.configure(s) | ||||
t.env.filters['dateformat'] = dateformat | t.env.filters['dateformat'] = dateformat | ||||
html = t.render(source, {}).strip() | html = t.render(source, {}).strip() | ||||
assert html == u'<h3 id="heading-3">Heading 3</h3>' | assert html == u'<h3 id="heading-3">Heading 3</h3>' | ||||
def test_markdown_with_sourcecode(): | def test_markdown_with_sourcecode(): | ||||
source = """ | source = """ | ||||
{%markdown%} | {%markdown%} | ||||
@@ -237,19 +255,23 @@ See [Example][] | |||||
{%endmarkdown%} | {%endmarkdown%} | ||||
""" | """ | ||||
expected = """ | |||||
expected = (""" | |||||
<h1>Code</h1> | <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> | </pre></div> | ||||
<p>See <a href="example.html">Example</a></p> | <p>See <a href="example.html">Example</a></p> | ||||
""" | |||||
""") | |||||
t = Jinja2Template(JINJA2.path) | t = Jinja2Template(JINJA2.path) | ||||
s = Site(JINJA2.path) | s = Site(JINJA2.path) | ||||
c = Config(JINJA2.path, config_dict=dict( | c = Config(JINJA2.path, config_dict=dict( | ||||
markdown=dict(extensions=['codehilite']))) | |||||
markdown=dict(extensions=['codehilite']))) | |||||
s.config = c | s.config = c | ||||
t.configure(s) | t.configure(s) | ||||
html = t.render(source, {}).strip() | html = t.render(source, {}).strip() | ||||
@@ -265,13 +287,15 @@ def test_line_statements(): | |||||
""" | """ | ||||
t = Jinja2Template(JINJA2.path) | t = Jinja2Template(JINJA2.path) | ||||
s = Site(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 | s.config = c | ||||
t.configure(s) | t.configure(s) | ||||
t.env.filters['dateformat'] = dateformat | t.env.filters['dateformat'] = dateformat | ||||
html = t.render(source, {}).strip() | html = t.render(source, {}).strip() | ||||
assert html == u'<h3 id="heading-3">Heading 3</h3>' | assert html == u'<h3 id="heading-3">Heading 3</h3>' | ||||
def test_line_statements_with_config(): | def test_line_statements_with_config(): | ||||
source = """ | source = """ | ||||
%% markdown | %% markdown | ||||
@@ -298,6 +322,7 @@ def test_line_statements_with_config(): | |||||
TEST_SITE = File(__file__).parent.child_folder('_test') | TEST_SITE = File(__file__).parent.child_folder('_test') | ||||
@nottest | @nottest | ||||
def assert_markdown_typogrify_processed_well(include_text, includer_text): | def assert_markdown_typogrify_processed_well(include_text, includer_text): | ||||
site = Site(TEST_SITE) | site = Site(TEST_SITE) | ||||
@@ -317,11 +342,13 @@ def assert_markdown_typogrify_processed_well(include_text, includer_text): | |||||
assert q(".amp").length == 1 | assert q(".amp").length == 1 | ||||
return html | return html | ||||
class TestJinjaTemplate(object): | class TestJinjaTemplate(object): | ||||
def setUp(self): | def setUp(self): | ||||
TEST_SITE.make() | 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): | def tearDown(self): | ||||
TEST_SITE.delete() | TEST_SITE.delete() | ||||
@@ -375,7 +402,6 @@ class TestJinjaTemplate(object): | |||||
assert article.length == 1 | assert article.length == 1 | ||||
assert article.text() == "Heya" | assert article.text() == "Heya" | ||||
def test_depends_with_reference_tag(self): | def test_depends_with_reference_tag(self): | ||||
site = Site(TEST_SITE) | site = Site(TEST_SITE) | ||||
JINJA2.copy_contents_to(site.content.source) | JINJA2.copy_contents_to(site.content.source) | ||||
@@ -408,7 +434,6 @@ class TestJinjaTemplate(object): | |||||
assert not deps[0] | assert not deps[0] | ||||
def test_can_include_templates_with_processing(self): | def test_can_include_templates_with_processing(self): | ||||
text = """ | text = """ | ||||
=== | === | ||||
@@ -424,11 +449,9 @@ Hyde & Jinja. | |||||
{% endmarkdown %}{% endfilter %} | {% endmarkdown %}{% endfilter %} | ||||
""" | """ | ||||
text2 = """{% include "inc.md" %}""" | text2 = """{% include "inc.md" %}""" | ||||
assert_markdown_typogrify_processed_well(text, text2) | assert_markdown_typogrify_processed_well(text, text2) | ||||
def test_includetext(self): | def test_includetext(self): | ||||
text = """ | text = """ | ||||
=== | === | ||||
@@ -595,7 +618,6 @@ Hyde & Jinja. | |||||
assert "mark" not in html | assert "mark" not in html | ||||
assert "reference" not in html | assert "reference" not in html | ||||
def test_refer_with_var(self): | def test_refer_with_var(self): | ||||
text = """ | text = """ | ||||
=== | === | ||||
@@ -623,7 +645,6 @@ Hyde & Jinja. | |||||
assert "mark" not in html | assert "mark" not in html | ||||
assert "reference" not in html | assert "reference" not in html | ||||
def test_yaml_tag(self): | def test_yaml_tag(self): | ||||
text = """ | text = """ | ||||
@@ -728,20 +749,22 @@ item_list: | |||||
</ul> | </ul> | ||||
</aside> | </aside> | ||||
""" | """ | ||||
text = "{%% filter markdown|typogrify %%}{%% raw %%}%s{%% endraw %%}{%% endfilter %%}" % expected | |||||
text = """{%% filter markdown|typogrify %%}{%% raw | |||||
%%}%s{%% endraw %%}{%% endfilter %%}""" % expected | |||||
t = Jinja2Template(JINJA2.path) | t = Jinja2Template(JINJA2.path) | ||||
t.configure(None) | t.configure(None) | ||||
html = t.render(text, {}).strip() | html = t.render(text, {}).strip() | ||||
assert html.strip() == expected.strip() | assert html.strip() == expected.strip() | ||||
def test_urlencode_filter(self): | 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""" | 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 = Jinja2Template(JINJA2.path) | ||||
t.configure(None) | t.configure(None) | ||||
@@ -749,13 +772,14 @@ item_list: | |||||
assert html.strip() == expected.strip() | assert html.strip() == expected.strip() | ||||
def test_urldecode_filter(self): | 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 = Jinja2Template(JINJA2.path) | ||||
t.configure(None) | t.configure(None) | ||||
html = t.render(text, {}).strip() | 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') | DATA_ROOT = File(__file__).parent.child_folder('data') | ||||
LAYOUT_ROOT = DATA_ROOT.child_folder(LAYOUTS) | LAYOUT_ROOT = DATA_ROOT.child_folder(LAYOUTS) | ||||
@nottest | @nottest | ||||
def setup_data(): | def setup_data(): | ||||
DATA_ROOT.make() | DATA_ROOT.make() | ||||
@nottest | @nottest | ||||
def cleanup_data(): | def cleanup_data(): | ||||
DATA_ROOT.delete() | DATA_ROOT.delete() | ||||
def test_find_layout_from_package_dir(): | def test_find_layout_from_package_dir(): | ||||
f = Layout.find_layout() | f = Layout.find_layout() | ||||
assert f.name == 'basic' | assert f.name == 'basic' | ||||
assert f.child_folder('layout').exists | assert f.child_folder('layout').exists | ||||
@with_setup(setup_data, cleanup_data) | @with_setup(setup_data, cleanup_data) | ||||
def test_find_layout_from_env_var(): | def test_find_layout_from_env_var(): | ||||
f = Layout.find_layout() | f = Layout.find_layout() | ||||
@@ -8,18 +8,21 @@ from hyde.model import Config, Expando | |||||
from fswrap import File, Folder | from fswrap import File, Folder | ||||
def test_expando_one_level(): | def test_expando_one_level(): | ||||
d = {"a": 123, "b": "abc"} | d = {"a": 123, "b": "abc"} | ||||
x = Expando(d) | x = Expando(d) | ||||
assert x.a == d['a'] | assert x.a == d['a'] | ||||
assert x.b == d['b'] | assert x.b == d['b'] | ||||
def test_expando_two_levels(): | def test_expando_two_levels(): | ||||
d = {"a": 123, "b": {"c": 456}} | d = {"a": 123, "b": {"c": 456}} | ||||
x = Expando(d) | x = Expando(d) | ||||
assert x.a == d['a'] | assert x.a == d['a'] | ||||
assert x.b.c == d['b']['c'] | assert x.b.c == d['b']['c'] | ||||
def test_expando_three_levels(): | def test_expando_three_levels(): | ||||
d = {"a": 123, "b": {"c": 456, "d": {"e": "abc"}}} | d = {"a": 123, "b": {"c": 456, "d": {"e": "abc"}}} | ||||
x = Expando(d) | x = Expando(d) | ||||
@@ -27,6 +30,7 @@ def test_expando_three_levels(): | |||||
assert x.b.c == d['b']['c'] | assert x.b.c == d['b']['c'] | ||||
assert x.b.d.e == d['b']['d']['e'] | assert x.b.d.e == d['b']['d']['e'] | ||||
def test_expando_update(): | def test_expando_update(): | ||||
d1 = {"a": 123, "b": "abc"} | d1 = {"a": 123, "b": "abc"} | ||||
x = Expando(d1) | x = Expando(d1) | ||||
@@ -34,7 +38,7 @@ def test_expando_update(): | |||||
assert x.b == d1['b'] | assert x.b == d1['b'] | ||||
d = {"b": {"c": 456, "d": {"e": "abc"}}, "f": "lmn"} | d = {"b": {"c": 456, "d": {"e": "abc"}}, "f": "lmn"} | ||||
x.update(d) | x.update(d) | ||||
assert x.a == d1['a'] | |||||
assert x.a == d1['a'] | |||||
assert x.b.c == d['b']['c'] | assert x.b.c == d['b']['c'] | ||||
assert x.b.d.e == d['b']['d']['e'] | assert x.b.d.e == d['b']['d']['e'] | ||||
assert x.f == d["f"] | assert x.f == d["f"] | ||||
@@ -44,11 +48,13 @@ def test_expando_update(): | |||||
assert x.a == 789 | assert x.a == 789 | ||||
assert x.f == "opq" | assert x.f == "opq" | ||||
def test_expando_to_dict(): | def test_expando_to_dict(): | ||||
d = {"a": 123, "b": {"c": 456, "d": {"e": "abc"}}} | d = {"a": 123, "b": {"c": 456, "d": {"e": "abc"}}} | ||||
x = Expando(d) | x = Expando(d) | ||||
assert d == x.to_dict() | assert d == x.to_dict() | ||||
def test_expando_to_dict_with_update(): | def test_expando_to_dict_with_update(): | ||||
d1 = {"a": 123, "b": "abc"} | d1 = {"a": 123, "b": "abc"} | ||||
x = Expando(d1) | x = Expando(d1) | ||||
@@ -67,6 +73,8 @@ def test_expando_to_dict_with_update(): | |||||
TEST_SITE = File(__file__).parent.child_folder('_test') | TEST_SITE = File(__file__).parent.child_folder('_test') | ||||
import yaml | import yaml | ||||
class TestConfig(object): | class TestConfig(object): | ||||
@classmethod | @classmethod | ||||
@@ -94,7 +102,8 @@ class TestConfig(object): | |||||
def setUp(self): | def setUp(self): | ||||
TEST_SITE.make() | 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): | def tearDown(self): | ||||
TEST_SITE.delete() | TEST_SITE.delete() | ||||
@@ -16,15 +16,18 @@ from fswrap import File, Folder | |||||
TEST_SITE = File(__file__).parent.child_folder('_test') | TEST_SITE = File(__file__).parent.child_folder('_test') | ||||
class PluginLoaderStub(Plugin): | class PluginLoaderStub(Plugin): | ||||
pass | pass | ||||
class NoReturnPlugin(Plugin): | class NoReturnPlugin(Plugin): | ||||
def begin_text_resource(self, resource, text): | def begin_text_resource(self, resource, text): | ||||
print "NoReturnPlugin" | print "NoReturnPlugin" | ||||
return None | return None | ||||
class ConstantReturnPlugin(Plugin): | class ConstantReturnPlugin(Plugin): | ||||
def begin_text_resource(self, resource, text): | def begin_text_resource(self, resource, text): | ||||
@@ -37,7 +40,8 @@ class TestPlugins(object): | |||||
@classmethod | @classmethod | ||||
def setup_class(cls): | def setup_class(cls): | ||||
TEST_SITE.make() | 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 = [] | folders = [] | ||||
text_files = [] | text_files = [] | ||||
binary_files = [] | binary_files = [] | ||||
@@ -58,14 +62,13 @@ class TestPlugins(object): | |||||
cls.content_text_resources = sorted(text_files) | cls.content_text_resources = sorted(text_files) | ||||
cls.content_binary_resources = sorted(binary_files) | cls.content_binary_resources = sorted(binary_files) | ||||
@classmethod | @classmethod | ||||
def teardown_class(cls): | def teardown_class(cls): | ||||
TEST_SITE.delete() | TEST_SITE.delete() | ||||
def setUp(self): | 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): | def test_can_load_plugin_modules(self): | ||||
assert not len(self.site.plugins) | assert not len(self.site.plugins) | ||||
@@ -74,25 +77,27 @@ class TestPlugins(object): | |||||
assert len(self.site.plugins) == 1 | assert len(self.site.plugins) == 1 | ||||
assert self.site.plugins[0].__class__.__name__ == 'PluginLoaderStub' | assert self.site.plugins[0].__class__.__name__ == 'PluginLoaderStub' | ||||
def test_generator_loads_plugins(self): | def test_generator_loads_plugins(self): | ||||
gen = Generator(self.site) | |||||
Generator(self.site) | |||||
assert len(self.site.plugins) == 1 | assert len(self.site.plugins) == 1 | ||||
def test_generator_template_registered_called(self): | 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 = Generator(self.site) | ||||
gen.generate_all() | gen.generate_all() | ||||
assert template_loaded_stub.call_count == 1 | assert template_loaded_stub.call_count == 1 | ||||
def test_generator_template_begin_generation_called(self): | 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 = Generator(self.site) | ||||
gen.generate_all() | gen.generate_all() | ||||
assert begin_generation_stub.call_count == 1 | 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) | gen = Generator(self.site) | ||||
path = self.site.content.source_folder.child('about.html') | path = self.site.content.source_folder.child('about.html') | ||||
gen.generate_resource_at_path(path) | gen.generate_resource_at_path(path) | ||||
@@ -100,29 +105,32 @@ class TestPlugins(object): | |||||
assert begin_generation_stub.call_count == 1 | assert begin_generation_stub.call_count == 1 | ||||
def test_generator_template_begin_generation_called_for_single_node(self): | 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) | gen = Generator(self.site) | ||||
path = self.site.content.source_folder | path = self.site.content.source_folder | ||||
gen.generate_node_at_path(path) | gen.generate_node_at_path(path) | ||||
assert begin_generation_stub.call_count == 1 | assert begin_generation_stub.call_count == 1 | ||||
def test_generator_template_generation_complete_called(self): | 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 = Generator(self.site) | ||||
gen.generate_all() | gen.generate_all() | ||||
assert generation_complete_stub.call_count == 1 | 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) | gen = Generator(self.site) | ||||
path = self.site.content.source_folder.child('about.html') | path = self.site.content.source_folder.child('about.html') | ||||
gen.generate_resource_at_path(path) | gen.generate_resource_at_path(path) | ||||
assert generation_complete_stub.call_count == 1 | 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) | gen = Generator(self.site) | ||||
path = self.site.content.source_folder | path = self.site.content.source_folder | ||||
gen.generate_node_at_path(path) | gen.generate_node_at_path(path) | ||||
@@ -141,7 +149,7 @@ class TestPlugins(object): | |||||
gen.generate_resource_at_path(path) | gen.generate_resource_at_path(path) | ||||
assert begin_site_stub.call_count == 1 | 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: | with patch.object(PluginLoaderStub, 'begin_site') as begin_site_stub: | ||||
gen = Generator(self.site) | gen = Generator(self.site) | ||||
gen.generate_all() | gen.generate_all() | ||||
@@ -158,7 +166,7 @@ class TestPlugins(object): | |||||
assert begin_site_stub.call_count == 1 | 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: | with patch.object(PluginLoaderStub, 'begin_site') as begin_site_stub: | ||||
gen = Generator(self.site) | gen = Generator(self.site) | ||||
gen.generate_all() | gen.generate_all() | ||||
@@ -169,24 +177,26 @@ class TestPlugins(object): | |||||
assert begin_site_stub.call_count == 1 | assert begin_site_stub.call_count == 1 | ||||
def test_generator_template_site_complete_called(self): | 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 = Generator(self.site) | ||||
gen.generate_all() | gen.generate_all() | ||||
assert site_complete_stub.call_count == 1 | assert site_complete_stub.call_count == 1 | ||||
def test_generator_template_site_complete_called_for_single_resource(self): | 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) | gen = Generator(self.site) | ||||
path = self.site.content.source_folder.child('about.html') | path = self.site.content.source_folder.child('about.html') | ||||
gen.generate_resource_at_path(path) | gen.generate_resource_at_path(path) | ||||
assert site_complete_stub.call_count == 1 | 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 = Generator(self.site) | ||||
gen.generate_all() | gen.generate_all() | ||||
assert site_complete_stub.call_count == 1 | 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): | 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) | gen = Generator(self.site) | ||||
path = self.site.content.source_folder | path = self.site.content.source_folder | ||||
gen.generate_node_at_path(path) | gen.generate_node_at_path(path) | ||||
assert site_complete_stub.call_count == 1 | 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 = Generator(self.site) | ||||
gen.generate_all() | gen.generate_all() | ||||
path = self.site.content.source_folder | path = self.site.content.source_folder | ||||
@@ -221,67 +233,81 @@ class TestPlugins(object): | |||||
gen.generate_all() | gen.generate_all() | ||||
assert begin_node_stub.call_count == len(self.content_nodes) | 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 | assert called_with_nodes == self.content_nodes | ||||
def test_generator_template_begin_node_called_for_single_resource(self): | def test_generator_template_begin_node_called_for_single_resource(self): | ||||
with patch.object(PluginLoaderStub, 'begin_node') as begin_node_stub: | with patch.object(PluginLoaderStub, 'begin_node') as begin_node_stub: | ||||
gen = Generator(self.site) | 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) | 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: | with patch.object(PluginLoaderStub, 'begin_node') as begin_node_stub: | ||||
gen = Generator(self.site) | gen = Generator(self.site) | ||||
gen.generate_all() | gen.generate_all() | ||||
assert begin_node_stub.call_count == len(self.content_nodes) | 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): | 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 = Generator(self.site) | ||||
gen.generate_all() | gen.generate_all() | ||||
assert node_complete_stub.call_count == len(self.content_nodes) | 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 | assert called_with_nodes == self.content_nodes | ||||
def test_generator_template_node_complete_called_for_single_resource(self): | 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 = 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) | 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 = Generator(self.site) | ||||
gen.generate_all() | gen.generate_all() | ||||
assert node_complete_stub.call_count == len(self.content_nodes) | 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): | 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.reset_mock() | ||||
begin_text_resource_stub.return_value = '' | begin_text_resource_stub.return_value = '' | ||||
gen = Generator(self.site) | gen = Generator(self.site) | ||||
gen.generate_all() | 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 = '' | begin_text_resource_stub.return_value = '' | ||||
gen = Generator(self.site) | gen = Generator(self.site) | ||||
gen.generate_all() | gen.generate_all() | ||||
@@ -290,81 +316,105 @@ class TestPlugins(object): | |||||
gen = Generator(self.site) | gen = Generator(self.site) | ||||
gen.generate_resource_at_path(path, incremental=True) | 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 begin_text_resource_stub.call_count == 1 | ||||
assert called_with_resources[0] == path | assert called_with_resources[0] == path | ||||
def test_generator_template_begin_binary_resource_called(self): | 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 = Generator(self.site) | ||||
gen.generate_all() | 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 | 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 = Generator(self.site) | ||||
gen.generate_all() | gen.generate_all() | ||||
begin_binary_resource_stub.reset_mock() | begin_binary_resource_stub.reset_mock() | ||||
path = self.site.content.source_folder.child('favicon.ico') | path = self.site.content.source_folder.child('favicon.ico') | ||||
gen.generate_resource_at_path(path) | 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 begin_binary_resource_stub.call_count == 1 | ||||
assert called_with_resources[0] == path | assert called_with_resources[0] == path | ||||
def test_plugin_chaining(self): | def test_plugin_chaining(self): | ||||
self.site.config.plugins = [ | |||||
self.site.config.plugins = [ | |||||
'hyde.tests.test_plugin.ConstantReturnPlugin', | 'hyde.tests.test_plugin.ConstantReturnPlugin', | ||||
'hyde.tests.test_plugin.NoReturnPlugin' | '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 test_plugin_filters_begin_text_resource(self): | ||||
def empty_return(self, resource, text=''): | def empty_return(self, resource, text=''): | ||||
return 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 = [ | self.site.config.plugins = [ | ||||
'hyde.tests.test_plugin.ConstantReturnPlugin', | 'hyde.tests.test_plugin.ConstantReturnPlugin', | ||||
'hyde.tests.test_plugin.NoReturnPlugin' | '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 = Generator(self.site) | ||||
gen.generate_all() | 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(mock1_args) == 1 | ||||
assert len(mock2_args) == 4 | assert len(mock2_args) == 4 | ||||
assert mock1_args == ["site.css"] | 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 test_plugin_node_filters_begin_text_resource(self): | ||||
def empty_return(*args, **kwargs): | def empty_return(*args, **kwargs): | ||||
return None | 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 = [ | self.site.config.plugins = [ | ||||
'hyde.tests.test_plugin.ConstantReturnPlugin', | 'hyde.tests.test_plugin.ConstantReturnPlugin', | ||||
'hyde.tests.test_plugin.NoReturnPlugin' | '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 = Generator(self.site) | ||||
gen.generate_all() | 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(mock1_args) == 1 | ||||
assert len(mock2_args) == 1 | assert len(mock2_args) == 1 | ||||
assert mock1_args == ["site.css"] | 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') | TEST_SITE_ROOT = File(__file__).parent.child_folder('sites/test_jinja') | ||||
class TestSimpleCopy(object): | class TestSimpleCopy(object): | ||||
@classmethod | @classmethod | ||||
def setup_class(cls): | 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() | cls.SITE_PATH.make() | ||||
TEST_SITE_ROOT.copy_contents_to(cls.SITE_PATH) | 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') | res = s.content.resource_from_relative_path('about.html') | ||||
assert res | assert res | ||||
assert not res.simple_copy | 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 | ||||
assert res.simple_copy | assert res.simple_copy | ||||
@@ -81,7 +85,8 @@ class TestSimpleCopy(object): | |||||
res = s.content.resource_from_relative_path('about.html') | res = s.content.resource_from_relative_path('about.html') | ||||
assert res | assert res | ||||
assert not res.simple_copy | 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 | ||||
assert res.simple_copy | assert res.simple_copy | ||||
res = s.content.resource_from_relative_path('media/css/site.css') | 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) | s = Site(self.SITE_PATH, self.config) | ||||
g = Generator(s) | g = Generator(s) | ||||
g.generate_all() | 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() | left = source.source_file.read_all() | ||||
right = target.read_all() | right = target.read_all() | ||||
assert left == right | assert left == right | ||||
@@ -129,11 +136,13 @@ twitter: @me | |||||
]) | ]) | ||||
conf = {'plugins': ['hyde.ext.plugins.meta.MetaPlugin']} | conf = {'plugins': ['hyde.ext.plugins.meta.MetaPlugin']} | ||||
conf.update(self.config.to_dict()) | 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 = Generator(s) | ||||
g.generate_all() | g.generate_all() | ||||
source = s.content.resource_from_relative_path('blog/index.html') | 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() | left = source.source_file.read_all() | ||||
right = target.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') | TEST_SITE_ROOT = File(__file__).parent.child_folder('sites/test_jinja') | ||||
def test_node_site(): | def test_node_site(): | ||||
s = Site(TEST_SITE_ROOT) | s = Site(TEST_SITE_ROOT) | ||||
r = RootNode(TEST_SITE_ROOT.child_folder('content'), s) | 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) | n = Node(r.source_folder.child_folder('blog'), r) | ||||
assert n.site == s | assert n.site == s | ||||
def test_node_root(): | def test_node_root(): | ||||
s = Site(TEST_SITE_ROOT) | s = Site(TEST_SITE_ROOT) | ||||
r = RootNode(TEST_SITE_ROOT.child_folder('content'), s) | 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) | n = Node(r.source_folder.child_folder('blog'), r) | ||||
assert n.root == r | assert n.root == r | ||||
def test_node_parent(): | def test_node_parent(): | ||||
s = Site(TEST_SITE_ROOT) | s = Site(TEST_SITE_ROOT) | ||||
r = RootNode(TEST_SITE_ROOT.child_folder('content'), s) | r = RootNode(TEST_SITE_ROOT.child_folder('content'), s) | ||||
c = r.add_node(TEST_SITE_ROOT.child_folder('content/blog/2010/december')) | c = r.add_node(TEST_SITE_ROOT.child_folder('content/blog/2010/december')) | ||||
assert c.parent == r.node_from_relative_path('blog/2010') | assert c.parent == r.node_from_relative_path('blog/2010') | ||||
def test_node_module(): | def test_node_module(): | ||||
s = Site(TEST_SITE_ROOT) | s = Site(TEST_SITE_ROOT) | ||||
r = RootNode(TEST_SITE_ROOT.child_folder('content'), s) | 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')) | c = r.add_node(TEST_SITE_ROOT.child_folder('content/blog/2010/december')) | ||||
assert c.module == n | assert c.module == n | ||||
def test_node_url(): | def test_node_url(): | ||||
s = Site(TEST_SITE_ROOT) | s = Site(TEST_SITE_ROOT) | ||||
r = RootNode(TEST_SITE_ROOT.child_folder('content'), s) | 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 == '/' + c.relative_path | ||||
assert c.url == '/blog/2010/december' | assert c.url == '/blog/2010/december' | ||||
def test_node_full_url(): | def test_node_full_url(): | ||||
s = Site(TEST_SITE_ROOT) | s = Site(TEST_SITE_ROOT) | ||||
s.config.base_url = 'http://localhost' | 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')) | c = r.add_node(TEST_SITE_ROOT.child_folder('content/blog/2010/december')) | ||||
assert c.full_url == 'http://localhost/blog/2010/december' | assert c.full_url == 'http://localhost/blog/2010/december' | ||||
def test_node_full_url_quoted(): | def test_node_full_url_quoted(): | ||||
s = Site(TEST_SITE_ROOT) | s = Site(TEST_SITE_ROOT) | ||||
s.config.base_url = 'http://localhost' | 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')) | 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') | assert c.full_url == 'http://localhost/' + quote('blo~g/2010/december') | ||||
def test_node_relative_path(): | def test_node_relative_path(): | ||||
s = Site(TEST_SITE_ROOT) | s = Site(TEST_SITE_ROOT) | ||||
r = RootNode(TEST_SITE_ROOT.child_folder('content'), s) | 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')) | c = r.add_node(TEST_SITE_ROOT.child_folder('content/blog/2010/december')) | ||||
assert c.relative_path == 'blog/2010/december' | assert c.relative_path == 'blog/2010/december' | ||||
def test_load(): | def test_load(): | ||||
s = Site(TEST_SITE_ROOT) | s = Site(TEST_SITE_ROOT) | ||||
s.load() | s.load() | ||||
@@ -96,6 +105,7 @@ def test_load(): | |||||
assert resource.relative_path == path | 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_walk_resources(): | def test_walk_resources(): | ||||
s = Site(TEST_SITE_ROOT) | s = Site(TEST_SITE_ROOT) | ||||
s.load() | s.load() | ||||
@@ -113,6 +123,7 @@ def test_walk_resources(): | |||||
expected.sort() | expected.sort() | ||||
assert pages == expected | assert pages == expected | ||||
def test_contains_resource(): | def test_contains_resource(): | ||||
s = Site(TEST_SITE_ROOT) | s = Site(TEST_SITE_ROOT) | ||||
s.load() | s.load() | ||||
@@ -120,13 +131,16 @@ def test_contains_resource(): | |||||
node = s.content.node_from_relative_path(path) | node = s.content.node_from_relative_path(path) | ||||
assert node.contains_resource('merry-christmas.html') | assert node.contains_resource('merry-christmas.html') | ||||
def test_get_resource(): | def test_get_resource(): | ||||
s = Site(TEST_SITE_ROOT) | s = Site(TEST_SITE_ROOT) | ||||
s.load() | s.load() | ||||
path = 'blog/2010/december' | path = 'blog/2010/december' | ||||
node = s.content.node_from_relative_path(path) | node = s.content.node_from_relative_path(path) | ||||
resource = node.get_resource('merry-christmas.html') | 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(): | def test_resource_slug(): | ||||
s = Site(TEST_SITE_ROOT) | s = Site(TEST_SITE_ROOT) | ||||
@@ -143,9 +157,12 @@ def test_get_resource_from_relative_deploy_path(): | |||||
path = 'blog/2010/december' | path = 'blog/2010/december' | ||||
node = s.content.node_from_relative_path(path) | node = s.content.node_from_relative_path(path) | ||||
resource = node.get_resource('merry-christmas.html') | 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') | 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(): | def test_is_processable_default_true(): | ||||
s = Site(TEST_SITE_ROOT) | s = Site(TEST_SITE_ROOT) | ||||
@@ -153,6 +170,7 @@ def test_is_processable_default_true(): | |||||
for page in s.content.walk_resources(): | for page in s.content.walk_resources(): | ||||
assert page.is_processable | assert page.is_processable | ||||
def test_relative_deploy_path(): | def test_relative_deploy_path(): | ||||
s = Site(TEST_SITE_ROOT) | s = Site(TEST_SITE_ROOT) | ||||
s.load() | s.load() | ||||
@@ -160,28 +178,35 @@ def test_relative_deploy_path(): | |||||
assert page.relative_deploy_path == Folder(page.relative_path) | assert page.relative_deploy_path == Folder(page.relative_path) | ||||
assert page.url == '/' + page.relative_deploy_path | assert page.url == '/' + page.relative_deploy_path | ||||
def test_relative_deploy_path_override(): | def test_relative_deploy_path_override(): | ||||
s = Site(TEST_SITE_ROOT) | s = Site(TEST_SITE_ROOT) | ||||
s.load() | 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' | res.relative_deploy_path = 'blog/2010/december/happy-holidays.html' | ||||
for page in s.content.walk_resources(): | for page in s.content.walk_resources(): | ||||
if res.source_file == page.source_file: | 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: | else: | ||||
assert page.relative_deploy_path == Folder(page.relative_path) | assert page.relative_deploy_path == Folder(page.relative_path) | ||||
class TestSiteWithConfig(object): | class TestSiteWithConfig(object): | ||||
@classmethod | @classmethod | ||||
def setup_class(cls): | 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() | cls.SITE_PATH.make() | ||||
TEST_SITE_ROOT.copy_contents_to(cls.SITE_PATH) | TEST_SITE_ROOT.copy_contents_to(cls.SITE_PATH) | ||||
cls.config_file = File(cls.SITE_PATH.child('alternate.yaml')) | cls.config_file = File(cls.SITE_PATH.child('alternate.yaml')) | ||||
with open(cls.config_file.path) as config: | 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 | @classmethod | ||||
def teardown_class(cls): | def teardown_class(cls): | ||||
@@ -198,7 +223,8 @@ class TestSiteWithConfig(object): | |||||
resource = s.content.resource_from_relative_path(path) | resource = s.content.resource_from_relative_path(path) | ||||
assert resource | assert resource | ||||
assert resource.relative_path == path | 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): | def test_content_url(self): | ||||
s = Site(self.SITE_PATH, config=self.config) | s = Site(self.SITE_PATH, config=self.config) | ||||
@@ -217,7 +243,7 @@ class TestSiteWithConfig(object): | |||||
s.load() | s.load() | ||||
path = '".jpg/abc' | path = '".jpg/abc' | ||||
print s.content_url(path, "") | print s.content_url(path, "") | ||||
print "/" + quote(path, "") | |||||
print "/" + quote(path, "") | |||||
assert s.content_url(path, "") == "/" + quote(path, "") | assert s.content_url(path, "") == "/" + quote(path, "") | ||||
def test_media_url(self): | def test_media_url(self): | ||||
@@ -254,7 +280,7 @@ class TestSiteWithConfig(object): | |||||
s.load() | s.load() | ||||
path = 'css/site.css' | path = 'css/site.css' | ||||
resource = s.content.resource_from_relative_path( | resource = s.content.resource_from_relative_path( | ||||
Folder("media").child(path)) | |||||
Folder("media").child(path)) | |||||
assert resource | assert resource | ||||
assert resource.full_url == "/media/" + path | assert resource.full_url == "/media/" + path | ||||
@@ -265,7 +291,7 @@ class TestSiteWithConfig(object): | |||||
path = 'apple-touch-icon.png' | path = 'apple-touch-icon.png' | ||||
resource = s.content.resource_from_relative_path(path) | resource = s.content.resource_from_relative_path(path) | ||||
assert resource | assert resource | ||||
assert resource.full_url == "/" + path | |||||
assert resource.full_url == "/" + path | |||||
s = Site(self.SITE_PATH, config=c) | s = Site(self.SITE_PATH, config=c) | ||||
s.config.ignore.append('*.png') | s.config.ignore.append('*.png') | ||||
resource = s.content.resource_from_relative_path(path) | resource = s.content.resource_from_relative_path(path) | ||||
@@ -281,10 +307,10 @@ class TestSiteWithConfig(object): | |||||
assert not git_node | assert not git_node | ||||
blog_node = s.content.node_from_relative_path('blog') | blog_node = s.content.node_from_relative_path('blog') | ||||
assert blog_node | assert blog_node | ||||
assert blog_node.full_url == "/blog" | |||||
assert blog_node.full_url == "/blog" | |||||
s = Site(self.SITE_PATH, config=c) | s = Site(self.SITE_PATH, config=c) | ||||
s.config.ignore.append('blog') | s.config.ignore.append('blog') | ||||
blog_node = s.content.node_from_relative_path('blog') | blog_node = s.content.node_from_relative_path('blog') | ||||
assert not blog_node | assert not blog_node | ||||
git_node = s.content.node_from_relative_path('.git') | git_node = s.content.node_from_relative_path('.git') | ||||
assert not git_node | |||||
assert not git_node |
@@ -1,6 +1,7 @@ | |||||
import re | import re | ||||
import difflib | import difflib | ||||
def strip_spaces_between_tags(value): | def strip_spaces_between_tags(value): | ||||
""" | """ | ||||
Stolen from `django.util.html` | Stolen from `django.util.html` | ||||
@@ -8,10 +9,11 @@ def strip_spaces_between_tags(value): | |||||
""" | """ | ||||
return re.sub(r'>\s+<', '><', unicode(value)) | return re.sub(r'>\s+<', '><', unicode(value)) | ||||
def assert_no_diff(expected, out): | def assert_no_diff(expected, out): | ||||
diff = [l for l in difflib.unified_diff(expected.splitlines(True), | 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) | assert not diff, ''.join(diff) | ||||
@@ -23,6 +25,7 @@ def assert_html_equals(expected, actual, sanitize=None): | |||||
actual = sanitize(actual) | actual = sanitize(actual) | ||||
assert expected == actual | assert expected == actual | ||||
def trap_exit_fail(f): | def trap_exit_fail(f): | ||||
def test_wrapper(*args): | def test_wrapper(*args): | ||||
try: | try: | ||||
@@ -32,6 +35,7 @@ def trap_exit_fail(f): | |||||
test_wrapper.__name__ = f.__name__ | test_wrapper.__name__ = f.__name__ | ||||
return test_wrapper | return test_wrapper | ||||
def trap_exit_pass(f): | def trap_exit_pass(f): | ||||
def test_wrapper(*args): | def test_wrapper(*args): | ||||
try: | try: | ||||
@@ -39,4 +43,4 @@ def trap_exit_pass(f): | |||||
except SystemExit: | except SystemExit: | ||||
pass | pass | ||||
test_wrapper.__name__ = f.__name__ | 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) | full_name = os.path.join(path, name) | ||||
if os.path.exists(full_name): | if os.path.exists(full_name): | ||||
return full_name | return full_name | ||||
return None | |||||
return None |