Browse Source

Fixed dependency issues and optimized server further

main
Lakshmi Vyasarajan 14 years ago
parent
commit
23b88333eb
11 changed files with 248 additions and 38 deletions
  1. +82
    -0
      README.markdown
  2. +0
    -1
      dev-req.txt
  3. +38
    -11
      hyde/ext/templates/jinja.py
  4. +1
    -1
      hyde/fs.py
  5. +5
    -4
      hyde/generator.py
  6. +11
    -15
      hyde/server.py
  7. +1
    -0
      hyde/site.py
  8. +10
    -4
      hyde/template.py
  9. +91
    -2
      hyde/tests/test_jinja2template.py
  10. +2
    -0
      req-2.6.txt
  11. +7
    -0
      req-2.7.txt

+ 82
- 0
README.markdown View File

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

+ 0
- 1
dev-req.txt View File

@@ -1,4 +1,3 @@
# argparse - needed for 2.6
commando==0.1.1a commando==0.1.1a
PyYAML==3.09 PyYAML==3.09
Markdown==2.0.3 Markdown==2.0.3


+ 38
- 11
hyde/ext/templates/jinja.py View File

@@ -63,6 +63,33 @@ class Markdown(Extension):
output = caller().strip() output = caller().strip()
return markdown(self.environment, output) 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 # pylint: disable-msg=W0104,E0602,W0613,R0201
class Jinja2Template(Template): class Jinja2Template(Template):
""" """
@@ -72,18 +99,13 @@ class Jinja2Template(Template):
def __init__(self, sitepath): def __init__(self, sitepath):
super(Jinja2Template, self).__init__(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, undefined=SilentUndefined,
trim_blocks=True, trim_blocks=True,
extensions=[Markdown, extensions=[Markdown,
@@ -92,9 +114,14 @@ class Jinja2Template(Template):
'jinja2.ext.with_']) 'jinja2.ext.with_'])
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.extend(config=config)
self.env.filters['markdown'] = markdown self.env.filters['markdown'] = markdown


config = {}
if hasattr(site, 'config'):
config = site.config

self.env.extend(config=config)

try: try:
from typogrify.templatetags import jinja2_filters from typogrify.templatetags import jinja2_filters
except ImportError: except ImportError:


+ 1
- 1
hyde/fs.py View File

@@ -236,7 +236,7 @@ class File(FS):
determine age. determine age.


""" """
return File(str(another_file)).last_modified > self.last_modified
return self.last_modified < File(str(another_file)).last_modified


@staticmethod @staticmethod
def make_temp(text): def make_temp(text):


+ 5
- 4
hyde/generator.py View File

@@ -82,8 +82,9 @@ class Generator(object):
self.template.__class__.__name__) self.template.__class__.__name__)


logger.info("Configuring the template environment") 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) self.events.template_loaded(self.template)


def initialize(self): def initialize(self):
@@ -125,7 +126,7 @@ class Generator(object):
return False return False
deps = self.template.get_dependencies(resource.source_file.read_all()) deps = self.template.get_dependencies(resource.source_file.read_all())
if not deps or None in deps: if not deps or None in deps:
return True
return False
content = self.site.content.source_folder content = self.site.content.source_folder
layout = Folder(self.site.sitepath).child_folder('layout') layout = Folder(self.site.sitepath).child_folder('layout')
for dep in deps: for dep in deps:
@@ -222,8 +223,8 @@ class Generator(object):




def __generate_node__(self, node): def __generate_node__(self, node):
logger.info("Generating [%s]", node)
for node in node.walk(): for node in node.walk():
logger.info("Generating Node [%s]", node)
self.events.begin_node(node) self.events.begin_node(node)
for resource in node.resources: for resource in node.resources:
self.__generate_resource__(resource) self.__generate_resource__(resource)


+ 11
- 15
hyde/server.py View File

@@ -10,6 +10,7 @@ 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.exceptions import HydeException


from hyde.util import getLoggerWithNullHandler from hyde.util import getLoggerWithNullHandler
logger = getLoggerWithNullHandler('hyde.server') logger = getLoggerWithNullHandler('hyde.server')
@@ -39,21 +40,25 @@ class HydeRequestHandler(SimpleHTTPRequestHandler):
logger.info('Redirecting...[%s]' % new_url) logger.info('Redirecting...[%s]' % new_url)
self.redirect(new_url) self.redirect(new_url)
else: else:
f = File(self.translate_path(self.path))
if not f.exists:
self.do_404()
else:
try:
SimpleHTTPRequestHandler.do_GET(self) SimpleHTTPRequestHandler.do_GET(self)
except HydeException:
self.do_404()



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
referring to the `site` variable in the server. referring to the `site` variable in the server.
""" """
path = SimpleHTTPRequestHandler.translate_path(self, path)
site = self.server.site site = self.server.site
result = urlparse.urlparse(self.path) result = urlparse.urlparse(self.path)
logger.info("Trying to load file based on request:[%s]" % result.path) logger.info("Trying to load file based on request:[%s]" % result.path)
path = result.path.lstrip('/') 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) res = site.content.resource_from_relative_deploy_path(path)


if not res: if not res:
@@ -69,6 +74,7 @@ class HydeRequestHandler(SimpleHTTPRequestHandler):
if not res: if not res:
# Nothing much we can do. # Nothing much we can do.
logger.error("Cannot load file:[%s]" % path) logger.error("Cannot load file:[%s]" % path)
raise HydeException("Cannot load file: [%s]" % path)


return site.config.deploy_root_path.child(path) return site.config.deploy_root_path.child(path)
else: else:
@@ -117,31 +123,22 @@ class HydeWebServer(HTTPServer):
def __init__(self, site, address, port): def __init__(self, site, address, port):
self.site = site self.site = site
self.site.load() self.site.load()
self.exception_count = 0
self.generator = Generator(self.site) self.generator = Generator(self.site)


HTTPServer.__init__(self, (address, port), HTTPServer.__init__(self, (address, port),
HydeRequestHandler) HydeRequestHandler)


def __reinit__(self):
self.site.load()
self.generator = Generator(self.site)
self.regenerate()

def regenerate(self): def regenerate(self):
""" """
Regenerates the entire site. Regenerates the entire site.
""" """
try: try:
logger.info('Regenerating the entire site') logger.info('Regenerating the entire site')
self.site.load()
self.generator.generate_all() self.generator.generate_all()
self.exception_count = 0
except Exception, exception: except Exception, exception:
self.exception_count += 1
logger.error('Error occured when regenerating the site [%s]' logger.error('Error occured when regenerating the site [%s]'
% exception.message) % exception.message)
if self.exception_count <= 1:
self.__reinit__()




def generate_resource(self, resource): def generate_resource(self, resource):
@@ -154,7 +151,6 @@ class HydeWebServer(HTTPServer):
try: try:
logger.info('Generating resource [%s]' % resource) logger.info('Generating resource [%s]' % resource)
self.generator.generate_resource(resource) self.generator.generate_resource(resource)
self.exception_count = 0
except Exception, exception: except Exception, exception:
logger.error( logger.error(
'Error [%s] occured when generating the resource [%s]' 'Error [%s] occured when generating the resource [%s]'


+ 1
- 0
hyde/site.py View File

@@ -291,6 +291,7 @@ class RootNode(Node):
resource = self.resource_from_path(afile) resource = self.resource_from_path(afile)
if resource: if resource:
logger.info("Resource exists at [%s]" % resource.relative_path) logger.info("Resource exists at [%s]" % resource.relative_path)
return resource


if not afile.is_descendant_of(self.source_folder): if not afile.is_descendant_of(self.source_folder):
raise HydeException("The given file [%s] does not reside" raise HydeException("The given file [%s] does not reside"


+ 10
- 4
hyde/template.py View File

@@ -4,7 +4,6 @@
Abstract classes and utilities for template engines Abstract classes and utilities for template engines
""" """
from hyde.exceptions import HydeException from hyde.exceptions import HydeException

from hyde.util import getLoggerWithNullHandler from hyde.util import getLoggerWithNullHandler


class Template(object): class Template(object):
@@ -17,15 +16,22 @@ class Template(object):
self.sitepath = sitepath self.sitepath = sitepath
self.logger = getLoggerWithNullHandler(self.__class__.__name__) 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 The config object is a simple YAML object with required settings. The
template implementations are responsible for transforming this object template implementations are responsible for transforming this object
to match the `settings` required for the template engines. 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 abstract
def get_dependencies(self, text): def get_dependencies(self, text):
""" """
Finds the dependencies based on the included Finds the dependencies based on the included


+ 91
- 2
hyde/tests/test_jinja2template.py View File

@@ -9,6 +9,8 @@ Code borrowed from rwbench.py from the jinja2 examples
from datetime import datetime from datetime import datetime
from hyde.ext.templates.jinja import Jinja2Template from hyde.ext.templates.jinja import Jinja2Template
from hyde.fs import File, Folder from hyde.fs import File, Folder
from hyde.site import Site
from hyde.generator import Generator
from hyde.model import Config from hyde.model import Config


import jinja2 import jinja2
@@ -17,6 +19,9 @@ from random import choice, randrange
from util import assert_html_equals from util import assert_html_equals
import yaml import yaml


from pyquery import PyQuery
from nose.tools import raises, nottest, with_setup

ROOT = File(__file__).parent ROOT = File(__file__).parent
JINJA2 = ROOT.child_folder('templates/jinja2') JINJA2 = ROOT.child_folder('templates/jinja2')


@@ -64,7 +69,6 @@ def test_render():
source = File(JINJA2.child('index.html')).read_all() source = File(JINJA2.child('index.html')).read_all()


html = t.render(source, context) html = t.render(source, context)
from pyquery import PyQuery
actual = PyQuery(html) actual = PyQuery(html)
assert actual(".navigation li").length == 30 assert actual(".navigation li").length == 30
assert actual("div.article").length == 20 assert actual("div.article").length == 20
@@ -128,7 +132,92 @@ def test_markdown_with_extensions():
{%endmarkdown%} {%endmarkdown%}
""" """
t = Jinja2Template(JINJA2.path) t = Jinja2Template(JINJA2.path)
s = Site(JINJA2.path)
c = Config(JINJA2.path, dict(markdown=dict(extensions=['headerid']))) c = Config(JINJA2.path, dict(markdown=dict(extensions=['headerid'])))
t.configure(c)
s.config = c
t.configure(s)
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>'


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

+ 2
- 0
req-2.6.txt View File

@@ -0,0 +1,2 @@
argparse
-r req-2.7.txt

+ 7
- 0
req-2.7.txt View File

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

Loading…
Cancel
Save