From 23b88333eb334f7d2f7321c5f0f2b2a8437a645a Mon Sep 17 00:00:00 2001 From: Lakshmi Vyasarajan Date: Fri, 21 Jan 2011 00:31:22 +0530 Subject: [PATCH] Fixed dependency issues and optimized server further --- README.markdown | 82 +++++++++++++++++++++++++++ dev-req.txt | 1 - hyde/ext/templates/jinja.py | 49 ++++++++++++---- hyde/fs.py | 2 +- hyde/generator.py | 9 +-- hyde/server.py | 26 ++++----- hyde/site.py | 1 + hyde/template.py | 14 +++-- hyde/tests/test_jinja2template.py | 93 ++++++++++++++++++++++++++++++- req-2.6.txt | 2 + req-2.7.txt | 7 +++ 11 files changed, 248 insertions(+), 38 deletions(-) create mode 100644 req-2.6.txt create mode 100644 req-2.7.txt diff --git a/README.markdown b/README.markdown index e69de29..91f1ad9 100644 --- a/README.markdown +++ b/README.markdown @@ -0,0 +1,82 @@ +# A brand new **hyde** + +This is the new version of hyde under active development. +I haven't managed to document the features yet. [This][hyde1-0] should +give a good understanding of the motivation behind this version. You can +also take a look at the [cloudpanic source][cp] for a reference implementation. + +[hyde1-0]: http://groups.google.com/group/hyde-dev/web/hyde-1-0 +[cp]: github.com/tipiirai/cloudpanic/tree/refactor + +[Here](http://groups.google.com/group/hyde-dev/browse_thread/thread/2a143bd2081b3322) is +the initial announcement of the project. + +# Installation + +Hyde supports both python 2.7 and 2.6. + + pip install -r req-2.6.txt + +or + + pip install -r req-2.7.txt + + +will install all the dependencies of hyde. + +You can choose to install hyde by running + + python setup.py install + +# Creating a new hyde site + +The new version of Hyde uses the `argparse` module and hence support subcommands. + + + hyde -s ~/test_site create -l test + +will create a new hyde site using the test layout. + + +# Generating the hyde site + + cd ~/test_site + hyde gen + +# Serving the website + + cd ~/test_site + hyde serve + open http://localhost:8080 + + +The server also regenerates on demand. As long as the server is running, +you can make changes to your source and refresh the browser to view the changes. + + +# A brief list of features + + +1. Support for multiple templates (although only `Jinja2` is currently implemented) +2. The different processor modules in the previous version are now + replaced by a plugin object. This allows plugins to listen to events that + occur during different times in the lifecycle and respond accordingly. +3. Metadata: Hyde now supports hierarchical metadata. You can specify and override + variables at the site, node or the page level and access them in the templates. +4. Sorting: The sorter plugin provides rich sorting options that extend the + object model. +5. Syntactic Sugar: Because of the richness of the plugin infrastructure, hyde can + now provide additional syntactic sugar to make the content more readable. See + `blockdown` and `autoextend` plugin for examples. + +# Next Steps + +1. Documentation +2. Default Layouts +3. Django Support +4. Plugins: + + * Tags + * Atom / RSS + * Media Compressor + * Image optimizer \ No newline at end of file diff --git a/dev-req.txt b/dev-req.txt index 502c474..1d5553e 100644 --- a/dev-req.txt +++ b/dev-req.txt @@ -1,4 +1,3 @@ -# argparse - needed for 2.6 commando==0.1.1a PyYAML==3.09 Markdown==2.0.3 diff --git a/hyde/ext/templates/jinja.py b/hyde/ext/templates/jinja.py index a701b2f..2822a7f 100644 --- a/hyde/ext/templates/jinja.py +++ b/hyde/ext/templates/jinja.py @@ -63,6 +63,33 @@ class Markdown(Extension): output = caller().strip() return markdown(self.environment, output) +class HydeLoader(FileSystemLoader): + + def __init__(self, sitepath, site, preprocessor=None): + config = site.config if hasattr(site, 'config') else None + if config: + super(HydeLoader, self).__init__([ + str(config.content_root_path), + str(config.layout_root_path), + ]) + else: + super(HydeLoader, self).__init__(str(sitepath)) + + self.site = site + self.preprocessor = preprocessor + + def get_source(self, environment, template): + (contents, + filename, + date) = super(HydeLoader, self).get_source( + environment, template) + if self.preprocessor: + resource = self.site.content.resource_from_relative_path(template) + if resource: + contents = self.preprocessor(resource, contents) or contents + return (contents, filename, date) + + # pylint: disable-msg=W0104,E0602,W0613,R0201 class Jinja2Template(Template): """ @@ -72,18 +99,13 @@ class Jinja2Template(Template): def __init__(self, sitepath): super(Jinja2Template, self).__init__(sitepath) - def configure(self, config): + def configure(self, site, preprocessor=None, postprocessor=None): """ - Uses the config object to initialize the jinja environment. + Uses the site object to initialize the jinja environment. """ - if config: - loader = FileSystemLoader([ - str(config.content_root_path), - str(config.layout_root_path), - ]) - else: - loader = FileSystemLoader(str(self.sitepath)) - self.env = Environment(loader=loader, + self.site = site + self.loader = HydeLoader(self.sitepath, site, preprocessor) + self.env = Environment(loader=self.loader, undefined=SilentUndefined, trim_blocks=True, extensions=[Markdown, @@ -92,9 +114,14 @@ class Jinja2Template(Template): 'jinja2.ext.with_']) self.env.globals['media_url'] = media_url self.env.globals['content_url'] = content_url - self.env.extend(config=config) self.env.filters['markdown'] = markdown + config = {} + if hasattr(site, 'config'): + config = site.config + + self.env.extend(config=config) + try: from typogrify.templatetags import jinja2_filters except ImportError: diff --git a/hyde/fs.py b/hyde/fs.py index d7ca3cd..4eb528e 100644 --- a/hyde/fs.py +++ b/hyde/fs.py @@ -236,7 +236,7 @@ class File(FS): determine age. """ - return File(str(another_file)).last_modified > self.last_modified + return self.last_modified < File(str(another_file)).last_modified @staticmethod def make_temp(text): diff --git a/hyde/generator.py b/hyde/generator.py index 40ea573..1b83dca 100644 --- a/hyde/generator.py +++ b/hyde/generator.py @@ -82,8 +82,9 @@ class Generator(object): self.template.__class__.__name__) logger.info("Configuring the template environment") - self.template.configure(self.site.config) - + self.template.configure(self.site, + preprocessor=self.events.begin_text_resource, + postprocessor=self.events.text_resource_complete) self.events.template_loaded(self.template) def initialize(self): @@ -125,7 +126,7 @@ class Generator(object): return False deps = self.template.get_dependencies(resource.source_file.read_all()) if not deps or None in deps: - return True + return False content = self.site.content.source_folder layout = Folder(self.site.sitepath).child_folder('layout') for dep in deps: @@ -222,8 +223,8 @@ class Generator(object): def __generate_node__(self, node): - logger.info("Generating [%s]", node) for node in node.walk(): + logger.info("Generating Node [%s]", node) self.events.begin_node(node) for resource in node.resources: self.__generate_resource__(resource) diff --git a/hyde/server.py b/hyde/server.py index d3c4db7..ba06ba5 100644 --- a/hyde/server.py +++ b/hyde/server.py @@ -10,6 +10,7 @@ from BaseHTTPServer import HTTPServer from hyde.fs import File, Folder from hyde.site import Site from hyde.generator import Generator +from hyde.exceptions import HydeException from hyde.util import getLoggerWithNullHandler logger = getLoggerWithNullHandler('hyde.server') @@ -39,21 +40,25 @@ class HydeRequestHandler(SimpleHTTPRequestHandler): logger.info('Redirecting...[%s]' % new_url) self.redirect(new_url) else: - f = File(self.translate_path(self.path)) - if not f.exists: - self.do_404() - else: + try: SimpleHTTPRequestHandler.do_GET(self) + except HydeException: + self.do_404() + def translate_path(self, path): """ Finds the absolute path of the requested file by referring to the `site` variable in the server. """ + path = SimpleHTTPRequestHandler.translate_path(self, path) site = self.server.site result = urlparse.urlparse(self.path) logger.info("Trying to load file based on request:[%s]" % result.path) path = result.path.lstrip('/') + if path.strip() == "" or File(path).kind.strip() == "": + return site.config.deploy_root_path.child(path) + res = site.content.resource_from_relative_deploy_path(path) if not res: @@ -69,6 +74,7 @@ class HydeRequestHandler(SimpleHTTPRequestHandler): if not res: # Nothing much we can do. logger.error("Cannot load file:[%s]" % path) + raise HydeException("Cannot load file: [%s]" % path) return site.config.deploy_root_path.child(path) else: @@ -117,31 +123,22 @@ class HydeWebServer(HTTPServer): def __init__(self, site, address, port): self.site = site self.site.load() - self.exception_count = 0 self.generator = Generator(self.site) HTTPServer.__init__(self, (address, port), HydeRequestHandler) - def __reinit__(self): - self.site.load() - self.generator = Generator(self.site) - self.regenerate() - def regenerate(self): """ Regenerates the entire site. """ try: logger.info('Regenerating the entire site') + self.site.load() self.generator.generate_all() - self.exception_count = 0 except Exception, exception: - self.exception_count += 1 logger.error('Error occured when regenerating the site [%s]' % exception.message) - if self.exception_count <= 1: - self.__reinit__() def generate_resource(self, resource): @@ -154,7 +151,6 @@ class HydeWebServer(HTTPServer): try: logger.info('Generating resource [%s]' % resource) self.generator.generate_resource(resource) - self.exception_count = 0 except Exception, exception: logger.error( 'Error [%s] occured when generating the resource [%s]' diff --git a/hyde/site.py b/hyde/site.py index 2919f6f..507258d 100644 --- a/hyde/site.py +++ b/hyde/site.py @@ -291,6 +291,7 @@ class RootNode(Node): resource = self.resource_from_path(afile) if resource: logger.info("Resource exists at [%s]" % resource.relative_path) + return resource if not afile.is_descendant_of(self.source_folder): raise HydeException("The given file [%s] does not reside" diff --git a/hyde/template.py b/hyde/template.py index 3ca5321..38e927c 100644 --- a/hyde/template.py +++ b/hyde/template.py @@ -4,7 +4,6 @@ Abstract classes and utilities for template engines """ from hyde.exceptions import HydeException - from hyde.util import getLoggerWithNullHandler class Template(object): @@ -17,15 +16,22 @@ class Template(object): self.sitepath = sitepath self.logger = getLoggerWithNullHandler(self.__class__.__name__) - def configure(self, config): + def configure(self, config, preprocessor=None, postprocessor=None): """ The config object is a simple YAML object with required settings. The template implementations are responsible for transforming this object to match the `settings` required for the template engines. - """ + The preprocessor and postprocessor contain the fucntions that + trigger the hyde plugins to preprocess the template after load + and postprocess it after it is processed and code is generated. + + Note that the processor must only be used when referencing templates, + for example, using the include tag. The regular preprocessing and + post processing logic is handled by hyde. + """ abstract - + def get_dependencies(self, text): """ Finds the dependencies based on the included diff --git a/hyde/tests/test_jinja2template.py b/hyde/tests/test_jinja2template.py index cbc58cf..34fb7a5 100644 --- a/hyde/tests/test_jinja2template.py +++ b/hyde/tests/test_jinja2template.py @@ -9,6 +9,8 @@ Code borrowed from rwbench.py from the jinja2 examples from datetime import datetime from hyde.ext.templates.jinja import Jinja2Template from hyde.fs import File, Folder +from hyde.site import Site +from hyde.generator import Generator from hyde.model import Config import jinja2 @@ -17,6 +19,9 @@ from random import choice, randrange from util import assert_html_equals import yaml +from pyquery import PyQuery +from nose.tools import raises, nottest, with_setup + ROOT = File(__file__).parent JINJA2 = ROOT.child_folder('templates/jinja2') @@ -64,7 +69,6 @@ def test_render(): source = File(JINJA2.child('index.html')).read_all() html = t.render(source, context) - from pyquery import PyQuery actual = PyQuery(html) assert actual(".navigation li").length == 30 assert actual("div.article").length == 20 @@ -128,7 +132,92 @@ def test_markdown_with_extensions(): {%endmarkdown%} """ t = Jinja2Template(JINJA2.path) + s = Site(JINJA2.path) c = Config(JINJA2.path, dict(markdown=dict(extensions=['headerid']))) - t.configure(c) + s.config = c + t.configure(s) html = t.render(source, {}).strip() assert html == u'

Heading 3

' + + +TEST_SITE = File(__file__).parent.child_folder('_test') + +@nottest +def create_test_site(): + TEST_SITE.make() + TEST_SITE.parent.child_folder('sites/test_jinja').copy_contents_to(TEST_SITE) + +@nottest +def delete_test_site(): + TEST_SITE.delete() + +@with_setup(create_test_site, delete_test_site) +def test_can_include_templates_with_processing(): + text = """ +=== +is_processable: False +=== + +{% filter typogrify %}{% markdown %} +This is a heading +================= + +Hyde & Jinja. + +{% endmarkdown %}{% endfilter %} +""" + + + text2 = """ +{% include "inc.md" %} + +""" + site = Site(TEST_SITE) + site.config.plugins = ['hyde.ext.plugins.meta.MetaPlugin'] + inc = File(TEST_SITE.child('content/inc.md')) + inc.write(text) + site.load() + gen = Generator(site) + gen.load_template_if_needed() + template = gen.template + html = template.render(text2, {}).strip() + assert html + q = PyQuery(html) + assert "is_processable" not in html + assert "This is a" in q("h1").text() + assert "heading" in q("h1").text() + assert q(".amp").length == 1 + + +#@with_setup(create_test_site, delete_test_site) +@nottest +def test_includetext(): + text = """ +=== +is_processable: False +=== + +This is a heading +================= + +An "&". + +""" + + text2 = """ +{% includetext inc.md %} + +""" + site = Site(TEST_SITE) + inc = File(TEST_SITE.child('content/inc.md')) + inc.write(text) + + site.load() + gen = Generator(site) + gen.load_template_if_needed() + template = gen.template + html = template.render(text2, {}).strip() + assert html + q = PyQuery(html) + assert q("h1").length == 1 + assert q(".amp").length == 1 diff --git a/req-2.6.txt b/req-2.6.txt new file mode 100644 index 0000000..b51f34a --- /dev/null +++ b/req-2.6.txt @@ -0,0 +1,2 @@ +argparse +-r req-2.7.txt \ No newline at end of file diff --git a/req-2.7.txt b/req-2.7.txt new file mode 100644 index 0000000..f895145 --- /dev/null +++ b/req-2.7.txt @@ -0,0 +1,7 @@ +commando==0.1.1a +PyYAML==3.09 +Markdown==2.0.3 +MarkupSafe==0.11 +smartypants==1.6.0.3 +-e git://github.com/hydepy/typogrify.git#egg=typogrify +Jinja2==2.5.5 \ No newline at end of file