@@ -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() | ||||