| @@ -6,8 +6,7 @@ from hyde.fs import File, Folder | |||||
| from hyde.template import Template | from hyde.template import Template | ||||
| from jinja2 import contextfunction, Environment, FileSystemLoader, Undefined | from jinja2 import contextfunction, Environment, FileSystemLoader, Undefined | ||||
| class LoyalUndefined(Undefined): | |||||
| class SilentUndefined(Undefined): | |||||
| def __getattr__(self, name): | def __getattr__(self, name): | ||||
| return self | return self | ||||
| @@ -45,12 +44,11 @@ class Jinja2Template(Template): | |||||
| if config: | if config: | ||||
| loader = FileSystemLoader([ | loader = FileSystemLoader([ | ||||
| str(config.content_root_path), | str(config.content_root_path), | ||||
| str(config.media_root_path), | |||||
| str(config.layout_root_path), | str(config.layout_root_path), | ||||
| ]) | ]) | ||||
| else: | else: | ||||
| loader = FileSystemLoader(str(self.sitepath)) | loader = FileSystemLoader(str(self.sitepath)) | ||||
| self.env = Environment(loader=loader, undefined=LoyalUndefined) | |||||
| self.env = Environment(loader=loader, undefined=SilentUndefined) | |||||
| 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 | ||||
| @@ -171,12 +171,27 @@ class File(FS): | |||||
| (mime, encoding) = mimetypes.guess_type(self.path) | (mime, encoding) = mimetypes.guess_type(self.path) | ||||
| return mime | return mime | ||||
| @property | |||||
| def is_binary(self): | |||||
| """Return true if this is a binary file.""" | |||||
| with open(self.path, 'rb') as fin: | |||||
| CHUNKSIZE = 1024 | |||||
| while 1: | |||||
| chunk = fin.read(CHUNKSIZE) | |||||
| if '\0' in chunk: | |||||
| return True | |||||
| if len(chunk) < CHUNKSIZE: | |||||
| break | |||||
| return False | |||||
| @property | @property | ||||
| def is_text(self): | def is_text(self): | ||||
| return self.mimetype.split("/")[0] == "text" | |||||
| """Return true if this is a text file.""" | |||||
| return (not self.is_binary) | |||||
| @property | @property | ||||
| def is_image(self): | def is_image(self): | ||||
| """Return true if this is an image file.""" | |||||
| return self.mimetype.split("/")[0] == "image" | return self.mimetype.split("/")[0] == "image" | ||||
| def read_all(self, encoding='utf-8'): | def read_all(self, encoding='utf-8'): | ||||
| @@ -3,6 +3,7 @@ The generator class and related utility functions. | |||||
| """ | """ | ||||
| from hyde.exceptions import HydeException | from hyde.exceptions import HydeException | ||||
| from hyde.fs import File | from hyde.fs import File | ||||
| from hyde.plugin import Plugin | |||||
| from hyde.template import Template | from hyde.template import Template | ||||
| from contextlib import contextmanager | from contextlib import contextmanager | ||||
| @@ -24,6 +25,30 @@ class Generator(object): | |||||
| self.site = site | self.site = site | ||||
| self.__context__ = dict(site=site) | self.__context__ = dict(site=site) | ||||
| self.template = None | self.template = None | ||||
| Plugin.load_all(site) | |||||
| class PluginProxy(object): | |||||
| """ | |||||
| A proxy class to raise events in registered plugins | |||||
| """ | |||||
| def __init__(self, site): | |||||
| super(PluginProxy, self).__init__() | |||||
| self.site = site | |||||
| def __getattr__(self, method_name): | |||||
| if hasattr(Plugin, method_name): | |||||
| def __call_plugins__(*args, **kwargs): | |||||
| if self.site.plugins: | |||||
| for plugin in self.site.plugins: | |||||
| if hasattr(plugin, method_name): | |||||
| function = getattr(plugin, method_name) | |||||
| function(*args, **kwargs) | |||||
| return __call_plugins__ | |||||
| raise HydeException( | |||||
| "Unknown plugin method [%s] called." % method_name) | |||||
| self.events = PluginProxy(self.site) | |||||
| @contextmanager | @contextmanager | ||||
| def context_for_resource(self, resource): | def context_for_resource(self, resource): | ||||
| @@ -37,7 +62,7 @@ class Generator(object): | |||||
| yield self.__context__ | yield self.__context__ | ||||
| self.__context__.update(resource=None) | self.__context__.update(resource=None) | ||||
| def initialize_template_if_needed(self): | |||||
| def load_template_if_needed(self): | |||||
| """ | """ | ||||
| Loads and configures the template environement from the site | Loads and configures the template environement from the site | ||||
| configuration if its not done already. | configuration if its not done already. | ||||
| @@ -50,7 +75,16 @@ class Generator(object): | |||||
| logger.info("Configuring the template environment") | logger.info("Configuring the template environment") | ||||
| self.template.configure(self.site.config) | self.template.configure(self.site.config) | ||||
| def reload_if_needed(self): | |||||
| self.events.template_loaded(self.template) | |||||
| def initialize(self): | |||||
| """ | |||||
| Start Generation. Perform setup tasks and inform plugins. | |||||
| """ | |||||
| logger.info("Begin Generation") | |||||
| self.events.begin_generation() | |||||
| def load_site_if_needed(self): | |||||
| """ | """ | ||||
| Checks if the site requries a reload and loads if | Checks if the site requries a reload and loads if | ||||
| necessary. | necessary. | ||||
| @@ -60,25 +94,35 @@ class Generator(object): | |||||
| logger.info("Reading site contents") | logger.info("Reading site contents") | ||||
| self.site.load() | self.site.load() | ||||
| def finalize(self): | |||||
| """ | |||||
| Generation complete. Inform plugins and cleanup. | |||||
| """ | |||||
| logger.info("Generation Complete") | |||||
| self.events.generation_complete() | |||||
| def generate_all(self): | def generate_all(self): | ||||
| """ | """ | ||||
| Generates the entire website | Generates the entire website | ||||
| """ | """ | ||||
| logger.info("Reading site contents") | logger.info("Reading site contents") | ||||
| self.initialize_template_if_needed() | |||||
| self.reload_if_needed() | |||||
| self.load_template_if_needed() | |||||
| self.initialize() | |||||
| self.load_site_if_needed() | |||||
| 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) | self.__generate_node__(self.site.content) | ||||
| self.events.site_complete() | |||||
| self.finalize() | |||||
| def generate_node_at_path(self, node_path=None): | def generate_node_at_path(self, node_path=None): | ||||
| """ | """ | ||||
| Generates a single node. If node_path is non-existent or empty, | Generates a single node. If node_path is non-existent or empty, | ||||
| generates the entire site. | generates the entire site. | ||||
| """ | """ | ||||
| self.initialize_template_if_needed() | |||||
| self.reload_if_needed() | |||||
| self.load_template_if_needed() | |||||
| self.load_site_if_needed() | |||||
| node = None | node = None | ||||
| if node_path: | if node_path: | ||||
| node = self.site.content.node_from_path(node_path) | node = self.site.content.node_from_path(node_path) | ||||
| @@ -89,12 +133,16 @@ class Generator(object): | |||||
| Generates the given node. If node is invalid, empty or | Generates the given node. If node is invalid, empty or | ||||
| non-existent, generates the entire website. | non-existent, generates the entire website. | ||||
| """ | """ | ||||
| self.initialize_template_if_needed() | |||||
| self.reload_if_needed() | |||||
| if not node: | if not node: | ||||
| return self.generate_all() | return self.generate_all() | ||||
| self.load_template_if_needed() | |||||
| self.initialize() | |||||
| self.load_site_if_needed() | |||||
| try: | try: | ||||
| self.__generate_node__(node) | self.__generate_node__(node) | ||||
| self.finalize() | |||||
| except HydeException: | except HydeException: | ||||
| self.generate_all() | self.generate_all() | ||||
| @@ -103,31 +151,39 @@ class Generator(object): | |||||
| Generates a single resource. If resource_path is non-existent or empty, | Generates a single resource. If resource_path is non-existent or empty, | ||||
| generats the entire website. | generats the entire website. | ||||
| """ | """ | ||||
| self.initialize_template_if_needed() | |||||
| self.reload_if_needed() | |||||
| self.load_template_if_needed() | |||||
| self.load_site_if_needed() | |||||
| resource = None | resource = None | ||||
| if resource_path: | if resource_path: | ||||
| resource = self.site.content.resource_from_path(resource_path) | resource = self.site.content.resource_from_path(resource_path) | ||||
| return self.generate_resource(resource) | |||||
| self.generate_resource(resource) | |||||
| def generate_resource(self, resource=None): | def generate_resource(self, resource=None): | ||||
| """ | """ | ||||
| Generates the given resource. If resource is invalid, empty or | Generates the given resource. If resource is invalid, empty or | ||||
| non-existent, generates the entire website. | non-existent, generates the entire website. | ||||
| """ | """ | ||||
| self.initialize_template_if_needed() | |||||
| self.reload_if_needed() | |||||
| if not resource: | if not resource: | ||||
| return self.generate_all() | return self.generate_all() | ||||
| self.load_template_if_needed() | |||||
| self.initialize() | |||||
| self.load_site_if_needed() | |||||
| try: | try: | ||||
| self.__generate_resource__(resource) | self.__generate_resource__(resource) | ||||
| self.finalize() | |||||
| except HydeException: | except HydeException: | ||||
| self.generate_all() | self.generate_all() | ||||
| def __generate_node__(self, node): | def __generate_node__(self, node): | ||||
| logger.info("Generating [%s]", node) | logger.info("Generating [%s]", node) | ||||
| for resource in node.walk_resources(): | |||||
| self.__generate_resource__(resource) | |||||
| for node in node.walk(): | |||||
| self.events.begin_node(node) | |||||
| for resource in node.resources: | |||||
| self.__generate_resource__(resource) | |||||
| self.events.node_complete(node) | |||||
| def __generate_resource__(self, resource): | def __generate_resource__(self, resource): | ||||
| logger.info("Processing [%s]", resource) | logger.info("Processing [%s]", resource) | ||||
| @@ -137,10 +193,15 @@ class Generator(object): | |||||
| self.site.content.source_folder)) | self.site.content.source_folder)) | ||||
| target.parent.make() | target.parent.make() | ||||
| if resource.source_file.is_text: | if resource.source_file.is_text: | ||||
| text = resource.source_file.read_all() | |||||
| text = self.events.begin_text_resource(resource, text) or text | |||||
| logger.info("Rendering [%s]", resource) | logger.info("Rendering [%s]", resource) | ||||
| text = self.template.render(resource.source_file.read_all(), | |||||
| context) | |||||
| text = self.template.render(text, context) | |||||
| text = self.events.text_resource_complete( | |||||
| resource, text) or text | |||||
| target.write(text) | target.write(text) | ||||
| else: | else: | ||||
| logger.info("Copying binary file [%s]", resource) | logger.info("Copying binary file [%s]", resource) | ||||
| self.events.begin_binary_resource(resource) | |||||
| resource.source_file.copy_to(target) | resource.source_file.copy_to(target) | ||||
| self.events.binary_resource_complete(resource) | |||||
| @@ -5,6 +5,11 @@ import sys | |||||
| from hyde.exceptions import HydeException | from hyde.exceptions import HydeException | ||||
| import logging | |||||
| from logging import NullHandler | |||||
| logger = logging.getLogger('hyde.engine') | |||||
| logger.addHandler(NullHandler()) | |||||
| plugins = {} | plugins = {} | ||||
| templates = {} | templates = {} | ||||
| @@ -17,6 +22,7 @@ def load_python_object(name): | |||||
| if module_name == '': | if module_name == '': | ||||
| (module_name, object_name) = (object_name, module_name) | (module_name, object_name) = (object_name, module_name) | ||||
| try: | try: | ||||
| logger.info('Loading module [%s]' % module_name) | |||||
| module = __import__(module_name) | module = __import__(module_name) | ||||
| except ImportError: | except ImportError: | ||||
| raise HydeException("The given module name [%s] is invalid." % | raise HydeException("The given module name [%s] is invalid." % | ||||
| @@ -32,11 +38,13 @@ def load_python_object(name): | |||||
| module_name) | module_name) | ||||
| try: | try: | ||||
| logger.info('Getting object [%s] from module [%s]' % | |||||
| (object_name, module_name)) | |||||
| return getattr(module, object_name) | return getattr(module, object_name) | ||||
| except AttributeError: | except AttributeError: | ||||
| raise HydeException("Cannot load the specified plugin [%s]. " | raise HydeException("Cannot load the specified plugin [%s]. " | ||||
| "The given module [%s] does not contain the " | "The given module [%s] does not contain the " | ||||
| "desired object [%s]. Please fix the" | |||||
| "desired object [%s]. Please fix the " | |||||
| "configuration or ensure that the module is " | "configuration or ensure that the module is " | ||||
| "installed properly" % | "installed properly" % | ||||
| (name, module_name, object_name)) | (name, module_name, object_name)) | ||||
| @@ -44,7 +44,8 @@ class Config(Expando): | |||||
| media_root='media', | media_root='media', | ||||
| layout_root='layout', | layout_root='layout', | ||||
| media_url='/media', | media_url='/media', | ||||
| site_url='/' | |||||
| site_url='/', | |||||
| plugins = [] | |||||
| ) | ) | ||||
| conf = dict(**default_config) | conf = dict(**default_config) | ||||
| if config_dict: | if config_dict: | ||||
| @@ -2,12 +2,14 @@ | |||||
| """ | """ | ||||
| Contains definition for a plugin protocol and other utiltities. | Contains definition for a plugin protocol and other utiltities. | ||||
| """ | """ | ||||
| import abc | |||||
| from hyde import loader | |||||
| class Plugin(object): | class Plugin(object): | ||||
| """ | """ | ||||
| The plugin protocol | The plugin protocol | ||||
| """ | """ | ||||
| __metaclass__ = abc.ABCMeta | |||||
| def __init__(self, site): | def __init__(self, site): | ||||
| super(Plugin, self).__init__() | super(Plugin, self).__init__() | ||||
| @@ -19,60 +21,105 @@ class Plugin(object): | |||||
| """ | """ | ||||
| pass | pass | ||||
| def prepare_site(self): | |||||
| def begin_generation(self): | |||||
| """ | """ | ||||
| Called when generation is about to take place. | Called when generation is about to take place. | ||||
| """ | """ | ||||
| pass | pass | ||||
| def site_load_complete(self): | |||||
| def begin_site(self): | |||||
| """ | """ | ||||
| Called when the site is built complete. This implies that all the | |||||
| Called when the site is loaded completely. This implies that all the | |||||
| nodes and resources have been identified and are accessible in the | nodes and resources have been identified and are accessible in the | ||||
| site variable. | site variable. | ||||
| """ | """ | ||||
| pass | pass | ||||
| def prepare_node(self, node): | |||||
| def begin_node(self, node): | |||||
| """ | """ | ||||
| Called when a node is about to be processed for generation. | Called when a node is about to be processed for generation. | ||||
| This method is called only when the entire node is generated. | |||||
| """ | """ | ||||
| pass | pass | ||||
| def prepare_resource(self, resource, text): | |||||
| def begin_text_resource(self, resource, text): | |||||
| """ | """ | ||||
| Called when a resource is about to be processed for generation. | |||||
| The `text` parameter contains the, resource text at this point | |||||
| Called when a text resource is about to be processed for generation. | |||||
| The `text` parameter contains the resource text at this point | |||||
| in its lifecycle. It is the text that has been loaded and any | in its lifecycle. It is the text that has been loaded and any | ||||
| plugins that are higher in the order may have tampered with it. | plugins that are higher in the order may have tampered with it. | ||||
| But the text has not been processed by the template yet. | |||||
| But the text has not been processed by the template yet. Note that | |||||
| the source file associated with the text resource may not be modifed | |||||
| by any plugins. | |||||
| If this function returns a value, it is used as the text for further | If this function returns a value, it is used as the text for further | ||||
| processing. | processing. | ||||
| """ | """ | ||||
| return text | return text | ||||
| def process_resource(self, resource, text): | |||||
| def begin_binary_resource(self, resource): | |||||
| """ | |||||
| Called when a binary resource is about to be processed for generation. | |||||
| Plugins are free to modify the contents of the file. | |||||
| """ | |||||
| pass | |||||
| def text_resource_complete(self, resource, text): | |||||
| """ | """ | ||||
| Called when a resource has been processed by the template. | Called when a resource has been processed by the template. | ||||
| The `text` parameter contains the, resource text at this point | |||||
| The `text` parameter contains the resource text at this point | |||||
| in its lifecycle. It is the text that has been processed by the | in its lifecycle. It is the text that has been processed by the | ||||
| template and any plugins that are higher in the order may have | template and any plugins that are higher in the order may have | ||||
| tampered with it. | |||||
| tampered with it. Note that the source file associated with the | |||||
| text resource may not be modifed by any plugins. | |||||
| If this function returns a value, it is used as the text for further | If this function returns a value, it is used as the text for further | ||||
| processing. | processing. | ||||
| """ | """ | ||||
| return text | return text | ||||
| def binary_resource_complete(self, resource): | |||||
| """ | |||||
| Called when a binary resource has already been processed. | |||||
| Plugins are free to modify the contents of the file. | |||||
| """ | |||||
| pass | |||||
| def node_complete(self, node): | def node_complete(self, node): | ||||
| """ | """ | ||||
| Called when all the resources in the node have been processed. | Called when all the resources in the node have been processed. | ||||
| This method is called only when the entire node is generated. | |||||
| """ | |||||
| pass | |||||
| def site_complete(self): | |||||
| """ | |||||
| Called when the entire site has been processed. This method is called | |||||
| only when the entire site is generated. | |||||
| """ | """ | ||||
| pass | pass | ||||
| def site_complete(self): | def site_complete(self): | ||||
| """ | """ | ||||
| Called when the entire site has been processed. | |||||
| Called when the generation process is complete. This method is called | |||||
| only when the entire site is generated. | |||||
| """ | |||||
| pass | |||||
| def generation_complete(self): | |||||
| """ | |||||
| Called when generation is completed. | |||||
| """ | """ | ||||
| pass | pass | ||||
| @staticmethod | |||||
| def load_all(site): | |||||
| """ | |||||
| Loads plugins based on the configuration. Assigns the plugins to | |||||
| 'site.plugins' | |||||
| """ | |||||
| site.plugins = [loader.load_python_object(name)(site) | |||||
| for name in site.config.plugins] | |||||
| @@ -275,6 +275,7 @@ class Site(object): | |||||
| self.sitepath = Folder(str(sitepath)) | self.sitepath = Folder(str(sitepath)) | ||||
| self.config = config if config else Config(self.sitepath) | self.config = config if config else Config(self.sitepath) | ||||
| self.content = RootNode(self.config.content_root_path, self) | self.content = RootNode(self.config.content_root_path, self) | ||||
| self.plugins = [] | |||||
| def load(self): | def load(self): | ||||
| """ | """ | ||||
| @@ -1,24 +1,24 @@ | |||||
| <?xml version="1.0"?> | <?xml version="1.0"?> | ||||
| <!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd"> | <!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd"> | ||||
| <cross-domain-policy> | <cross-domain-policy> | ||||
| <!-- Read this: www.adobe.com/devnet/articles/crossdomain_policy_file_spec.html --> | <!-- Read this: www.adobe.com/devnet/articles/crossdomain_policy_file_spec.html --> | ||||
| <!-- Most restrictive policy: --> | <!-- Most restrictive policy: --> | ||||
| <site-control permitted-cross-domain-policies="none"/> | |||||
| <site-control permitted-cross-domain-policies="none"/> | |||||
| <!-- Least restrictive policy: --> | <!-- Least restrictive policy: --> | ||||
| <!-- | <!-- | ||||
| <site-control permitted-cross-domain-policies="all"/> | |||||
| <allow-access-from domain="*" to-ports="*" secure="false"/> | |||||
| <allow-http-request-headers-from domain="*" headers="*" secure="false"/> | |||||
| <site-control permitted-cross-domain-policies="all"/> | |||||
| <allow-access-from domain="*" to-ports="*" secure="false"/> | |||||
| <allow-http-request-headers-from domain="*" headers="*" secure="false"/> | |||||
| --> | --> | ||||
| <!-- | <!-- | ||||
| If you host a crossdomain.xml file with allow-access-from domain=“*” | |||||
| and don’t understand all of the points described here, you probably | |||||
| If you host a crossdomain.xml file with allow-access-from domain=“*” | |||||
| and don’t understand all of the points described here, you probably | |||||
| have a nasty security vulnerability. ~ simon willison | have a nasty security vulnerability. ~ simon willison | ||||
| --> | --> | ||||
| @@ -109,6 +109,7 @@ HELPERS = File(JINJA2.child('helpers.html')) | |||||
| INDEX = File(JINJA2.child('index.html')) | INDEX = File(JINJA2.child('index.html')) | ||||
| LAYOUT = File(JINJA2.child('layout.html')) | LAYOUT = File(JINJA2.child('layout.html')) | ||||
| LOGO = File(TEMPLATE_ROOT.child('../../../resources/hyde-logo.png')) | LOGO = File(TEMPLATE_ROOT.child('../../../resources/hyde-logo.png')) | ||||
| XML = File(TEMPLATE_ROOT.child('../sites/test_jinja/content/crossdomain.xml')) | |||||
| def test_ancestors(): | def test_ancestors(): | ||||
| depth = 0 | depth = 0 | ||||
| @@ -156,6 +157,7 @@ def test_mimetype(): | |||||
| def test_is_text(): | def test_is_text(): | ||||
| assert HELPERS.is_text | assert HELPERS.is_text | ||||
| assert not LOGO.is_text | assert not LOGO.is_text | ||||
| assert XML.is_text | |||||
| def test_is_image(): | def test_is_image(): | ||||
| assert not HELPERS.is_image | assert not HELPERS.is_image | ||||
| @@ -62,7 +62,8 @@ class TestConfig(object): | |||||
| assert getattr(c, name) == root | assert getattr(c, name) == root | ||||
| assert hasattr(c, path) | assert hasattr(c, path) | ||||
| assert getattr(c, path) == TEST_SITE_ROOT.child_folder(root) | assert getattr(c, path) == TEST_SITE_ROOT.child_folder(root) | ||||
| assert hasattr(c, 'plugins') | |||||
| assert len(c.plugins) == 0 | |||||
| assert c.deploy_root_path == TEST_SITE_ROOT.child_folder('deploy') | assert c.deploy_root_path == TEST_SITE_ROOT.child_folder('deploy') | ||||
| def test_conf1(self): | def test_conf1(self): | ||||
| @@ -0,0 +1,246 @@ | |||||
| # -*- coding: utf-8 -*- | |||||
| """ | |||||
| Use nose | |||||
| `$ pip install nose` | |||||
| `$ nosetests` | |||||
| """ | |||||
| from hyde.exceptions import HydeException | |||||
| from hyde.fs import File, Folder | |||||
| from hyde.generator import Generator | |||||
| from hyde.plugin import Plugin | |||||
| from hyde.site import Site | |||||
| from mock import patch | |||||
| from nose.tools import raises, nottest, with_setup | |||||
| TEST_SITE = File(__file__).parent.child_folder('_test') | |||||
| class PluginLoaderStub(Plugin): | |||||
| pass | |||||
| class TestPlugins(object): | |||||
| @classmethod | |||||
| def setup_class(cls): | |||||
| TEST_SITE.make() | |||||
| TEST_SITE.parent.child_folder('sites/test_jinja').copy_contents_to(TEST_SITE) | |||||
| folders = [] | |||||
| text_files = [] | |||||
| binary_files = [] | |||||
| with TEST_SITE.child_folder('content').walker as walker: | |||||
| @walker.folder_visitor | |||||
| def visit_folder(folder): | |||||
| folders.append(folder.path) | |||||
| @walker.file_visitor | |||||
| def visit_file(afile): | |||||
| if not afile.is_text: | |||||
| binary_files.append(afile.path) | |||||
| else: | |||||
| text_files.append(afile.path) | |||||
| cls.content_nodes = sorted(folders) | |||||
| cls.content_text_resources = sorted(text_files) | |||||
| cls.content_binary_resources = sorted(binary_files) | |||||
| @classmethod | |||||
| def teardown_class(cls): | |||||
| TEST_SITE.delete() | |||||
| def setUp(self): | |||||
| self.site = Site(TEST_SITE) | |||||
| self.site.config.plugins = ['hyde.tests.test_plugin.PluginLoaderStub'] | |||||
| def test_can_load_plugin_modules(self): | |||||
| assert not len(self.site.plugins) | |||||
| Plugin.load_all(self.site) | |||||
| assert len(self.site.plugins) == 1 | |||||
| assert self.site.plugins[0].__class__.__name__ == 'PluginLoaderStub' | |||||
| def test_generator_loads_plugins(self): | |||||
| gen = Generator(self.site) | |||||
| assert len(self.site.plugins) == 1 | |||||
| def test_generator_template_registered_called(self): | |||||
| with patch.object(PluginLoaderStub, 'template_loaded') as template_loaded_stub: | |||||
| gen = Generator(self.site) | |||||
| gen.generate_all() | |||||
| assert template_loaded_stub.call_count == 1 | |||||
| def test_generator_template_begin_generation_called(self): | |||||
| with patch.object(PluginLoaderStub, 'begin_generation') as begin_generation_stub: | |||||
| gen = Generator(self.site) | |||||
| gen.generate_all() | |||||
| assert begin_generation_stub.call_count == 1 | |||||
| def test_generator_template_begin_generation_called_for_single_resource(self): | |||||
| with patch.object(PluginLoaderStub, 'begin_generation') as begin_generation_stub: | |||||
| gen = Generator(self.site) | |||||
| path = self.site.content.source_folder.child('about.html') | |||||
| gen.generate_resource_at_path(path) | |||||
| assert begin_generation_stub.call_count == 1 | |||||
| def test_generator_template_begin_generation_called_for_single_node(self): | |||||
| with patch.object(PluginLoaderStub, 'begin_generation') as begin_generation_stub: | |||||
| gen = Generator(self.site) | |||||
| path = self.site.content.source_folder | |||||
| gen.generate_node_at_path(path) | |||||
| assert begin_generation_stub.call_count == 1 | |||||
| def test_generator_template_generation_complete_called(self): | |||||
| with patch.object(PluginLoaderStub, 'generation_complete') as generation_complete_stub: | |||||
| gen = Generator(self.site) | |||||
| gen.generate_all() | |||||
| assert generation_complete_stub.call_count == 1 | |||||
| def test_generator_template_generation_complete_called_for_single_resource(self): | |||||
| with patch.object(PluginLoaderStub, 'generation_complete') as generation_complete_stub: | |||||
| gen = Generator(self.site) | |||||
| path = self.site.content.source_folder.child('about.html') | |||||
| gen.generate_resource_at_path(path) | |||||
| assert generation_complete_stub.call_count == 1 | |||||
| def test_generator_template_generation_complete_called_for_single_node(self): | |||||
| with patch.object(PluginLoaderStub, 'generation_complete') as generation_complete_stub: | |||||
| gen = Generator(self.site) | |||||
| path = self.site.content.source_folder | |||||
| gen.generate_node_at_path(path) | |||||
| assert generation_complete_stub.call_count == 1 | |||||
| def test_generator_template_begin_site_called(self): | |||||
| with patch.object(PluginLoaderStub, 'begin_site') as begin_site_stub: | |||||
| gen = Generator(self.site) | |||||
| gen.generate_all() | |||||
| assert begin_site_stub.call_count == 1 | |||||
| def test_generator_template_begin_site_not_called_for_single_resource(self): | |||||
| with patch.object(PluginLoaderStub, 'begin_site') as begin_site_stub: | |||||
| gen = Generator(self.site) | |||||
| path = self.site.content.source_folder.child('about.html') | |||||
| gen.generate_resource_at_path(path) | |||||
| assert begin_site_stub.call_count == 0 | |||||
| def test_generator_template_begin_site_not_called_for_single_node(self): | |||||
| with patch.object(PluginLoaderStub, 'begin_site') as begin_site_stub: | |||||
| gen = Generator(self.site) | |||||
| path = self.site.content.source_folder | |||||
| gen.generate_node_at_path(path) | |||||
| assert begin_site_stub.call_count == 0 | |||||
| def test_generator_template_site_complete_called(self): | |||||
| with patch.object(PluginLoaderStub, 'site_complete') as site_complete_stub: | |||||
| gen = Generator(self.site) | |||||
| gen.generate_all() | |||||
| assert site_complete_stub.call_count == 1 | |||||
| def test_generator_template_site_complete_not_called_for_single_resource(self): | |||||
| with patch.object(PluginLoaderStub, 'site_complete') as site_complete_stub: | |||||
| gen = Generator(self.site) | |||||
| path = self.site.content.source_folder.child('about.html') | |||||
| gen.generate_resource_at_path(path) | |||||
| assert site_complete_stub.call_count == 0 | |||||
| def test_generator_template_site_complete_not_called_for_single_node(self): | |||||
| with patch.object(PluginLoaderStub, 'site_complete') as site_complete_stub: | |||||
| gen = Generator(self.site) | |||||
| path = self.site.content.source_folder | |||||
| gen.generate_node_at_path(path) | |||||
| assert site_complete_stub.call_count == 0 | |||||
| def test_generator_template_begin_node_called(self): | |||||
| with patch.object(PluginLoaderStub, 'begin_node') as begin_node_stub: | |||||
| gen = Generator(self.site) | |||||
| gen.generate_all() | |||||
| assert begin_node_stub.call_count == len(self.content_nodes) | |||||
| called_with_nodes = sorted([arg[0][0].path for arg in begin_node_stub.call_args_list]) | |||||
| assert called_with_nodes == self.content_nodes | |||||
| def test_generator_template_begin_node_not_called_for_single_resource(self): | |||||
| with patch.object(PluginLoaderStub, 'begin_node') as begin_node_stub: | |||||
| gen = Generator(self.site) | |||||
| gen.generate_resource_at_path(self.site.content.source_folder.child('about.html')) | |||||
| assert begin_node_stub.call_count == 0 | |||||
| def test_generator_template_node_complete_called(self): | |||||
| with patch.object(PluginLoaderStub, 'node_complete') as node_complete_stub: | |||||
| gen = Generator(self.site) | |||||
| gen.generate_all() | |||||
| assert node_complete_stub.call_count == len(self.content_nodes) | |||||
| called_with_nodes = sorted([arg[0][0].path for arg in node_complete_stub.call_args_list]) | |||||
| assert called_with_nodes == self.content_nodes | |||||
| def test_generator_template_node_complete_not_called_for_single_resource(self): | |||||
| with patch.object(PluginLoaderStub, 'node_complete') as node_complete_stub: | |||||
| gen = Generator(self.site) | |||||
| gen.generate_resource_at_path(self.site.content.source_folder.child('about.html')) | |||||
| assert node_complete_stub.call_count == 0 | |||||
| def test_generator_template_begin_text_resource_called(self): | |||||
| with patch.object(PluginLoaderStub, 'begin_text_resource') as begin_text_resource_stub: | |||||
| gen = Generator(self.site) | |||||
| gen.generate_all() | |||||
| called_with_resources = sorted([arg[0][0].path for arg in begin_text_resource_stub.call_args_list]) | |||||
| assert begin_text_resource_stub.call_count == len(self.content_text_resources) | |||||
| assert called_with_resources == self.content_text_resources | |||||
| def test_generator_template_begin_text_resource_called_for_single_resource(self): | |||||
| with patch.object(PluginLoaderStub, 'begin_text_resource') as begin_text_resource_stub: | |||||
| gen = Generator(self.site) | |||||
| path = self.site.content.source_folder.child('about.html') | |||||
| gen.generate_resource_at_path(path) | |||||
| called_with_resources = sorted([arg[0][0].path for arg in begin_text_resource_stub.call_args_list]) | |||||
| assert begin_text_resource_stub.call_count == 1 | |||||
| assert called_with_resources[0] == path | |||||
| def test_generator_template_begin_binary_resource_called(self): | |||||
| with patch.object(PluginLoaderStub, 'begin_binary_resource') as begin_binary_resource_stub: | |||||
| gen = Generator(self.site) | |||||
| gen.generate_all() | |||||
| called_with_resources = sorted([arg[0][0].path for arg in begin_binary_resource_stub.call_args_list]) | |||||
| assert begin_binary_resource_stub.call_count == len(self.content_binary_resources) | |||||
| assert called_with_resources == self.content_binary_resources | |||||
| def test_generator_template_begin_binary_resource_called_for_single_resource(self): | |||||
| with patch.object(PluginLoaderStub, 'begin_binary_resource') as begin_binary_resource_stub: | |||||
| gen = Generator(self.site) | |||||
| path = self.site.content.source_folder.child('favicon.ico') | |||||
| gen.generate_resource_at_path(path) | |||||
| called_with_resources = sorted([arg[0][0].path for arg in begin_binary_resource_stub.call_args_list]) | |||||
| assert begin_binary_resource_stub.call_count == 1 | |||||
| assert called_with_resources[0] == path | |||||
| @@ -75,7 +75,8 @@ def test_walk_resources(): | |||||
| "merry-christmas.html", | "merry-christmas.html", | ||||
| "crossdomain.xml", | "crossdomain.xml", | ||||
| "favicon.ico", | "favicon.ico", | ||||
| "robots.txt" | |||||
| "robots.txt", | |||||
| "site.css" | |||||
| ] | ] | ||||
| pages.sort() | pages.sort() | ||||
| expected.sort() | expected.sort() | ||||