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
PyYAML==3.09
Markdown==2.0.3


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

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


+ 1
- 1
hyde/fs.py View File

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


+ 5
- 4
hyde/generator.py View File

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


+ 11
- 15
hyde/server.py View File

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


+ 1
- 0
hyde/site.py View File

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


+ 10
- 4
hyde/template.py View File

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


+ 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 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'<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