| @@ -16,7 +16,7 @@ import yaml | |||||
| HYDE_LAYOUTS = "HYDE_LAYOUTS" | HYDE_LAYOUTS = "HYDE_LAYOUTS" | ||||
| logger = getLoggerWithConsoleHandler('hyde.engine') | |||||
| logger = getLoggerWithConsoleHandler('hyde') | |||||
| class Engine(Application): | class Engine(Application): | ||||
| """ | """ | ||||
| @@ -104,6 +104,21 @@ class Jinja2Template(Template): | |||||
| jinja2_filters.register(self.env) | jinja2_filters.register(self.env) | ||||
| def get_dependencies(self, text): | |||||
| """ | |||||
| Finds dependencies hierarchically based on the included | |||||
| files. | |||||
| """ | |||||
| from jinja2.meta import find_referenced_templates | |||||
| ast = self.env.parse(text) | |||||
| tpls = find_referenced_templates(ast) | |||||
| deps = [] | |||||
| for dep in tpls: | |||||
| deps.append(dep) | |||||
| source = self.env.loader.get_source(self.env, dep)[0] | |||||
| deps.extend(self.get_dependencies(source)) | |||||
| return list(set(deps)) | |||||
| @property | @property | ||||
| def exception_class(self): | def exception_class(self): | ||||
| return TemplateError | return TemplateError | ||||
| @@ -2,7 +2,7 @@ | |||||
| The generator class and related utility functions. | 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, Folder | |||||
| from hyde.plugin import Plugin | from hyde.plugin import Plugin | ||||
| from hyde.template import Template | from hyde.template import Template | ||||
| @@ -110,6 +110,29 @@ class Generator(object): | |||||
| logger.info("Generation Complete") | logger.info("Generation Complete") | ||||
| self.events.generation_complete() | self.events.generation_complete() | ||||
| def has_resource_changed(self, resource): | |||||
| target = File(self.site.config.deploy_root_path.child( | |||||
| resource.relative_deploy_path)) | |||||
| if not target.exists or target.older_than(resource.source_file): | |||||
| return True | |||||
| if resource.source_file.is_binary: | |||||
| return False | |||||
| deps = self.template.get_dependencies(resource.source_file.read_all()) | |||||
| if not deps or None in deps: | |||||
| return True | |||||
| content = self.site.content.source_folder | |||||
| layout = Folder(self.site.sitepath).child_folder('layout') | |||||
| for dep in deps: | |||||
| source = File(content.child(dep)) | |||||
| if not source.exists: | |||||
| source = File(layout.child(dep)) | |||||
| if not source.exists: | |||||
| return True | |||||
| if target.older_than(source): | |||||
| return True | |||||
| return False | |||||
| def generate_all(self): | def generate_all(self): | ||||
| """ | """ | ||||
| Generates the entire website | Generates the entire website | ||||
| @@ -10,9 +10,9 @@ from BaseHTTPServer import HTTPServer | |||||
| from hyde.fs import File, Folder | from hyde.fs import File, Folder | ||||
| from hyde.site import Site | from hyde.site import Site | ||||
| from hyde.generator import Generator | from hyde.generator import Generator | ||||
| from hyde.util import getLoggerWithConsoleHandler | |||||
| logger = getLoggerWithConsoleHandler('hyde.engine') | |||||
| from hyde.util import getLoggerWithNullHandler | |||||
| logger = getLoggerWithNullHandler('hyde.server') | |||||
| class HydeRequestHandler(SimpleHTTPRequestHandler): | class HydeRequestHandler(SimpleHTTPRequestHandler): | ||||
| """ | """ | ||||
| @@ -131,7 +131,7 @@ class HydeWebServer(HTTPServer): | |||||
| """ | """ | ||||
| target = self.site.config.deploy_root_path.child( | target = self.site.config.deploy_root_path.child( | ||||
| resource.relative_deploy_path) | resource.relative_deploy_path) | ||||
| if File(target).older_than(resource.source_file): | |||||
| if self.generator.has_resource_changed(resource): | |||||
| try: | try: | ||||
| logger.info('Generating resource [%s]' % resource) | logger.info('Generating resource [%s]' % resource) | ||||
| self.generator.generate_resource(resource) | self.generator.generate_resource(resource) | ||||
| @@ -25,6 +25,13 @@ class Template(object): | |||||
| """ | """ | ||||
| abstract | abstract | ||||
| def get_dependencies(self, text): | |||||
| """ | |||||
| Finds the dependencies based on the included | |||||
| files. | |||||
| """ | |||||
| return None | |||||
| def render(self, text, context): | def render(self, text, context): | ||||
| """ | """ | ||||
| @@ -73,4 +73,24 @@ def test_generate_resource_from_path_with_deploy_override(): | |||||
| assert about.exists | assert about.exists | ||||
| text = about.read_all() | text = about.read_all() | ||||
| q = PyQuery(text) | q = PyQuery(text) | ||||
| assert resource.name in q("div#main").text() | |||||
| assert resource.name in q("div#main").text() | |||||
| @with_setup(create_test_site, delete_test_site) | |||||
| def test_has_resource_changed(): | |||||
| site = Site(TEST_SITE) | |||||
| site.load() | |||||
| resource = site.content.resource_from_path(TEST_SITE.child('content/about.html')) | |||||
| gen = Generator(site) | |||||
| gen.generate_all() | |||||
| assert not gen.has_resource_changed(resource) | |||||
| import time | |||||
| time.sleep(1) | |||||
| text = resource.source_file.read_all() | |||||
| resource.source_file.write(text) | |||||
| assert gen.has_resource_changed(resource) | |||||
| gen.generate_all() | |||||
| assert not gen.has_resource_changed(resource) | |||||
| time.sleep(1) | |||||
| l = File(TEST_SITE.child('layout/root.html')) | |||||
| l.write(l.read_all()) | |||||
| assert gen.has_resource_changed(resource) | |||||
| @@ -73,6 +73,31 @@ 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_depends(): | |||||
| t = Jinja2Template(JINJA2.path) | |||||
| t.configure(None) | |||||
| source = File(JINJA2.child('index.html')).read_all() | |||||
| deps = list(t.get_dependencies(source)) | |||||
| assert len(deps) == 2 | |||||
| assert 'helpers.html' in deps | |||||
| assert 'layout.html' in deps | |||||
| def test_depends_multi_level(): | |||||
| t = Jinja2Template(JINJA2.path) | |||||
| t.configure(None) | |||||
| source = "{% extends 'index.html' %}" | |||||
| deps = list(t.get_dependencies(source)) | |||||
| assert len(deps) == 3 | |||||
| assert 'helpers.html' in deps | |||||
| assert 'layout.html' in deps | |||||
| assert 'index.html' in deps | |||||
| def test_typogrify(): | def test_typogrify(): | ||||
| source = """ | source = """ | ||||
| {%filter typogrify%} | {%filter typogrify%} | ||||
| @@ -94,12 +119,12 @@ def test_markdown(): | |||||
| t.configure(None) | t.configure(None) | ||||
| 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_markdown_with_extensions(): | def test_markdown_with_extensions(): | ||||
| source = """ | source = """ | ||||
| {%markdown%} | {%markdown%} | ||||
| ### Heading 3 | ### Heading 3 | ||||
| {%endmarkdown%} | {%endmarkdown%} | ||||
| """ | """ | ||||
| t = Jinja2Template(JINJA2.path) | t = Jinja2Template(JINJA2.path) | ||||
| @@ -18,7 +18,7 @@ except: | |||||
| pass | pass | ||||
| def getLoggerWithConsoleHandler(logger_name): | def getLoggerWithConsoleHandler(logger_name): | ||||
| logger = logging.getLogger('hyde.server') | |||||
| logger = logging.getLogger(logger_name) | |||||
| logger.setLevel(logging.DEBUG) | logger.setLevel(logging.DEBUG) | ||||
| handler = logging.StreamHandler(sys.stdout) | handler = logging.StreamHandler(sys.stdout) | ||||
| formatter = ColorFormatter(fmt="$COLOR%(levelname)s " | formatter = ColorFormatter(fmt="$COLOR%(levelname)s " | ||||