diff --git a/hyde/aggregator.py b/hyde/aggregator.py
deleted file mode 100644
index 7c68785..0000000
--- a/hyde/aggregator.py
+++ /dev/null
@@ -1 +0,0 @@
-# -*- coding: utf-8 -*-
\ No newline at end of file
diff --git a/hyde/engine.py b/hyde/engine.py
index fe0b0b5..5c7c777 100644
--- a/hyde/engine.py
+++ b/hyde/engine.py
@@ -6,12 +6,22 @@ from commando import *
from hyde.exceptions import HydeException
from hyde.fs import File, Folder
from hyde.layout import Layout, HYDE_DATA
+from hyde.model import Config
+from hyde.site import Site
from hyde.version import __version__
+import logging
import os
+import yaml
HYDE_LAYOUTS = "HYDE_LAYOUTS"
+logger = logging.getLogger('hyde.engine')
+logger.setLevel(logging.DEBUG)
+
+import sys
+logger.addHandler(logging.StreamHandler(sys.stdout))
+
class Engine(Application):
"""
The Hyde Application
@@ -29,36 +39,67 @@ class Engine(Application):
"""
pass
- @subcommand('init', help='Create a new hyde site')
+ @subcommand('create', help='Create a new hyde site')
@store('-l', '--layout', default='basic', help='Layout for the new site')
@true('-f', '--force', default=False, dest='overwrite',
help='Overwrite the current site if it exists')
- def init(self, args):
+ def create(self, args):
"""
- The initialize command. Creates a new site from the template at the given
+ The create command. Creates a new site from the template at the given
sitepath.
"""
- sitepath = File(args.sitepath)
+ sitepath = Folder(args.sitepath)
if sitepath.exists and not args.overwrite:
raise HydeException("The given site path[%s] is not empty" % sitepath)
layout = Layout.find_layout(args.layout)
+ logger.info("Creating site at [%s] with layout [%s]" % (sitepath, layout))
if not layout or not layout.exists:
raise HydeException(
"The given layout is invalid. Please check if you have the `layout` "
"in the right place and the environment variable(%s) has been setup "
"properly if you are using custom path for layouts" % HYDE_DATA)
layout.copy_contents_to(args.sitepath)
+ logger.info("Site creation complete")
@subcommand('gen', help='Generate the site')
- @store('-c', '--config-path', default='site.yaml', help='The configuration used to generate the site')
+ @store('-c', '--config-path', default='site.yaml', dest='config',
+ help='The configuration used to generate the site')
@store('-d', '--deploy-path', default='deploy', help='Where should the site be generated?')
def gen(self, args):
"""
The generate command. Generates the site at the given deployment directory.
"""
- sitepath = File(args.sitepath)
+ sitepath = Folder(args.sitepath)
+ logger.info("Generating site at [%s]" % sitepath)
# Read the configuration
- # Find the appropriate template environment
+ config_file = sitepath.child(args.config)
+ logger.info("Reading site configuration from [%s]", config_file)
+ conf = {}
+ with open(config_file) as stream:
+ conf = yaml.load(stream)
+ site = Site(sitepath, Config(sitepath, conf))
+ # TODO: Find the appropriate template environment
+ from hyde.ext.templates.jinja import Jinja2Template
+ template = Jinja2Template(sitepath)
+ logger.info("Using [%s] as the template", template)
# Configure the environment
+ logger.info("Configuring Template environment")
+ template.configure(site.config)
# Prepare site info
- # Generate site one file at a time
\ No newline at end of file
+ logger.info("Analyzing site contents")
+ site.build()
+ context = dict(site=site)
+ # Generate site one file at a time
+ logger.info("Generating site to [%s]" % site.config.deploy_root_path)
+ for page in site.content.walk_resources():
+ logger.info("Processing [%s]", page)
+ target = File(page.source_file.get_mirror(site.config.deploy_root_path, site.content.source_folder))
+ target.parent.make()
+ if page.source_file.is_text:
+ logger.info("Rendering [%s]", page)
+ context.update(page=page)
+ text = template.render(page, context)
+ target.write(text)
+ else:
+ logger.info("Copying binary file [%s]", page)
+ page.source_file.copy_to(target)
\ No newline at end of file
diff --git a/hyde/ext/templates/jinja.py b/hyde/ext/templates/jinja.py
index 10f6ed2..43f553c 100644
--- a/hyde/ext/templates/jinja.py
+++ b/hyde/ext/templates/jinja.py
@@ -1,28 +1,57 @@
"""
Jinja template utilties
"""
+
+from hyde.fs import File, Folder
from hyde.template import Template
-from jinja2 import Environment, FileSystemLoader
+from jinja2 import contextfunction, Environment, FileSystemLoader, Undefined
+
+
+class LoyalUndefined(Undefined):
+ def __getattr__(self, name):
+ return self
+
+ __getitem__ = __getattr__
+
+ def __call__(self, *args, **kwargs):
+ return self
+
+@contextfunction
+def media_url(context, path):
+ site = context['site']
+ return Folder(site.config.media_url).child(path)
+
+@contextfunction
+def content_url(context, path):
+ site = context['site']
+ return Folder(site.config.base_url).child(path)
# pylint: disable-msg=W0104,E0602,W0613,R0201
class Jinja2Template(Template):
"""
The Jinja2 Template implementation
"""
-
+
def __init__(self, sitepath):
super(Jinja2Template, self).__init__(sitepath)
- self.env = Environment(loader=FileSystemLoader(sitepath))
def configure(self, config):
"""
Uses the config object to initialize the jinja environment.
"""
- pass
+ if config:
+ loader = FileSystemLoader([
+ str(config.content_root_path),
+ str(config.media_root_path),
+ str(config.layout_root_path),
+ ])
+ self.env = Environment(loader=loader, undefined=LoyalUndefined)
+ self.env.globals['media_url'] = media_url
+ self.env.globals['content_url'] = content_url
- def render(self, template_name, context):
+ def render(self, resource, context):
"""
- Renders the given template using the context
+ Renders the given resource using the context
"""
- template = self.env.get_template(template_name)
+ template = self.env.get_template(resource.relative_path)
return template.render(context)
diff --git a/hyde/fs.py b/hyde/fs.py
index ce3aa77..9f5041f 100644
--- a/hyde/fs.py
+++ b/hyde/fs.py
@@ -7,14 +7,13 @@ for common operations to provide a single interface.
"""
import codecs
-import contextlib
import logging
from logging import NullHandler
+import mimetypes
import os
import shutil
from distutils import dir_util
import functools
-import itertools
# pylint: disable-msg=E0611
logger = logging.getLogger('fs')
@@ -29,7 +28,7 @@ class FS(object):
"""
def __init__(self, path):
super(FS, self).__init__()
- self.path = str(path).strip().rstrip(os.sep)
+ self.path = os.path.expanduser(str(path).strip().rstrip(os.sep))
def __str__(self):
return self.path
@@ -85,18 +84,23 @@ class FS(object):
f = f.parent
def is_descendant_of(self, ancestor):
- stop = Folder(ancestor)
- for folder in self.ancestors():
- if folder == stop:
- return True
- if stop.depth > folder.depth:
- return False
- return False
+ """
+ Checks if this folder is inside the given ancestor.
+ """
+ stop = Folder(ancestor)
+ for folder in self.ancestors():
+ if folder == stop:
+ return True
+ if stop.depth > folder.depth:
+ return False
+ return False
def get_relative_path(self, root):
"""
Gets the fragment of the current path starting at root.
"""
+ if self == root:
+ return ''
return functools.reduce(lambda f, p: Folder(p.name).child(f), self.ancestors(stop=root), self.name)
def get_mirror(self, target_root, source_root=None):
@@ -123,7 +127,7 @@ class FS(object):
Returns a File or Folder object that would represent this entity
if it were copied or moved to `destination`.
"""
- if (isinstance(destination, File) or os.path.isfile(str(destination))):
+ if isinstance(destination, File) or os.path.isfile(str(destination)):
return destination
else:
return FS.file_or_folder(Folder(destination).child(self.name))
@@ -156,6 +160,19 @@ class File(FS):
"""
return self.extension.lstrip(".")
+ @property
+ def mimetype(self):
+ (mime, encoding) = mimetypes.guess_type(self.path)
+ return mime
+
+ @property
+ def is_text(self):
+ return self.mimetype.split("/")[0] == "text"
+
+ @property
+ def is_image(self):
+ return self.mimetype.split("/")[0] == "image"
+
def read_all(self, encoding='utf-8'):
"""
Reads from the file and returns the content as a string.
@@ -224,20 +241,56 @@ class FSVisitor(object):
class FolderWalker(FSVisitor):
"""
Walks the entire hirearchy of this directory starting with itself.
- Calls self.visit_folder first and then calls self.visit_file for
- any files found. After all files and folders have been exhausted
- self.visit_complete is called.
If a pattern is provided, only the files that match the pattern are
processed.
-
- If visitor.visit_folder returns False, the files in the folder are not
- processed.
"""
+ def walk(self, walk_folders=False, walk_files=False):
+ """
+ A simple generator that yields a File or Folder object based on
+ the arguments.
+ """
+
+ if walk_files or walk_folders:
+ for root, dirs, a_files in os.walk(self.folder.path):
+ folder = Folder(root)
+ if walk_folders:
+ yield folder
+ if walk_files:
+ for a_file in a_files:
+ if not self.pattern or fnmatch.fnmatch(a_file, self.pattern):
+ yield File(folder.child(a_file))
+
+ def walk_all(self):
+ """
+ Yield both Files and Folders as the tree is walked.
+ """
+
+ return self.walk(walk_folders=True, walk_files=True)
+
+ def walk_files(self):
+ """
+ Yield only Files.
+ """
+ return self.walk(walk_folders=False, walk_files=True)
+
+ def walk_folders(self):
+ """
+ Yield only Folders.
+ """
+ return self.walk(walk_folders=True, walk_files=False)
+
def __exit__(self, exc_type, exc_val, exc_tb):
"""
Automatically walk the folder when the context manager is exited.
+
+ Calls self.visit_folder first and then calls self.visit_file for
+ any files found. After all files and folders have been exhausted
+ self.visit_complete is called.
+
+ If visitor.visit_folder returns False, the files in the folder are not
+ processed.
"""
def __visit_folder__(folder):
@@ -271,18 +324,54 @@ class FolderWalker(FSVisitor):
class FolderLister(FSVisitor):
"""
- Lists the contents of this directory starting with itself.
- Calls self.visit_folder first and then calls self.visit_file for
- any files found. After all files and folders have been exhausted
- self.visit_complete is called.
+ Lists the contents of this directory.
If a pattern is provided, only the files that match the pattern are
processed.
"""
+ def list(self, list_folders=False, list_files=False):
+ """
+ A simple generator that yields a File or Folder object based on
+ the arguments.
+ """
+
+ a_files = os.listdir(self.folder.path)
+ for a_file in a_files:
+ path = self.folder.child(a_file)
+ if os.path.isdir(path):
+ if list_folders:
+ yield Folder(path)
+ elif list_files:
+ if not self.pattern or fnmatch.fnmatch(a_file, self.pattern):
+ yield File(path)
+
+ def list_all(self):
+ """
+ Yield both Files and Folders as the folder is listed.
+ """
+
+ return self.list(list_folders=True, list_files=True)
+
+ def list_files(self):
+ """
+ Yield only Files.
+ """
+ return self.list(list_folders=False, list_files=True)
+
+ def list_folders(self):
+ """
+ Yield only Folders.
+ """
+ return self.list(list_folders=True, list_files=False)
+
def __exit__(self, exc_type, exc_val, exc_tb):
"""
Automatically list the folder contents when the context manager is exited.
+
+ Calls self.visit_folder first and then calls self.visit_file for
+ any files found. After all files and folders have been exhausted
+ self.visit_complete is called.
"""
a_files = os.listdir(self.folder.path)
@@ -307,13 +396,13 @@ class Folder(FS):
"""
Returns a folder object by combining the fragment to this folder's path
"""
- return Folder(os.path.join(self.path, fragment))
+ return Folder(os.path.join(self.path, Folder(fragment).path))
- def child(self, name):
+ def child(self, fragment):
"""
- Returns a path of a child item represented by `name`.
+ Returns a path of a child item represented by `fragment`.
"""
- return os.path.join(self.path, name)
+ return os.path.join(self.path, FS(fragment).path)
def make(self):
"""
@@ -372,13 +461,15 @@ class Folder(FS):
the tree has been deleted before and readded now. To workaround the
bug, we first walk the tree and create directories that are needed.
"""
- with self.walk() as walker:
+ source = self
+ with source.walker as walker:
@walker.folder_visitor
def visit_folder(folder):
"""
Create the mirror directory
"""
- Folder(folder.get_mirror(target)).make()
+ if folder != source:
+ Folder(folder.get_mirror(target, source)).make()
def copy_contents_to(self, destination):
"""
@@ -386,20 +477,24 @@ class Folder(FS):
Returns a Folder object that represents the moved directory.
"""
logger.info("Copying contents of %s to %s" % (self, destination))
- self._create_target_tree(Folder(destination))
- dir_util.copy_tree(self.path, str(destination))
- return Folder(destination)
+ target = Folder(destination)
+ target.make()
+ self._create_target_tree(target)
+ dir_util.copy_tree(self.path, str(target))
+ return target
- def walk(self, pattern=None):
+ @property
+ def walker(self, pattern=None):
"""
- Walks this folder using `FolderWalker`
+ Return a `FolderWalker` object
"""
return FolderWalker(self, pattern)
- def list(self, pattern=None):
+ @property
+ def lister(self, pattern=None):
"""
- Lists this folder using `FolderLister`
+ Return a `FolderLister` object
"""
return FolderLister(self, pattern)
\ No newline at end of file
diff --git a/hyde/layouts/basic/layout/base.html b/hyde/layouts/basic/layout/base.html
index 5702364..c568763 100644
--- a/hyde/layouts/basic/layout/base.html
+++ b/hyde/layouts/basic/layout/base.html
@@ -29,20 +29,20 @@
{% block favicons %}
-
-
+
+
{% endblock favicons %}
{% block css %}
-
+
{% endblock css %}
{% block headjs %}
-
+
{% endblock headjs %}
{% block endhead %}{% endblock endhead %}
@@ -68,13 +68,13 @@
{% block jquery %}
-
+
{% endblock jquery %}
{% block scripts %}
-
-
+
+
{% endblock scripts %}
@@ -83,7 +83,7 @@
{% endblock pngfix %}
diff --git a/hyde/layouts/test/content/404.html b/hyde/layouts/test/content/404.html
new file mode 100644
index 0000000..b5ea1ac
--- /dev/null
+++ b/hyde/layouts/test/content/404.html
@@ -0,0 +1,22 @@
+
+
not found
+
+
+
+
+
+
+
+
Not found
+
:(
+
\ No newline at end of file
diff --git a/hyde/layouts/test/content/about.html b/hyde/layouts/test/content/about.html
new file mode 100644
index 0000000..fce92b4
--- /dev/null
+++ b/hyde/layouts/test/content/about.html
@@ -0,0 +1,7 @@
+{% extends "base.html" %}
+
+{% block main %}
+ Hi!
+
+ I am a test template to make sure jinja2 generation works well with hyde.
+{% endblock %}
\ No newline at end of file
diff --git a/hyde/layouts/test/content/apple-touch-icon.png b/hyde/layouts/test/content/apple-touch-icon.png
new file mode 100644
index 0000000..1f1972b
Binary files /dev/null and b/hyde/layouts/test/content/apple-touch-icon.png differ
diff --git a/hyde/layouts/test/content/blog/2010/december/merry-christmas.html b/hyde/layouts/test/content/blog/2010/december/merry-christmas.html
new file mode 100644
index 0000000..fd78860
--- /dev/null
+++ b/hyde/layouts/test/content/blog/2010/december/merry-christmas.html
@@ -0,0 +1,9 @@
+{% extends "blog/post.html" %}
+
+{% block article %}
+ {{ lipsum() }}
+{% endblock %}
+
+{% block aside %}
+ {{ lipsum() }}
+{% endblock %}
\ No newline at end of file
diff --git a/hyde/layouts/test/content/crossdomain.xml b/hyde/layouts/test/content/crossdomain.xml
new file mode 100644
index 0000000..0d42929
--- /dev/null
+++ b/hyde/layouts/test/content/crossdomain.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/hyde/layouts/test/content/favicon.ico b/hyde/layouts/test/content/favicon.ico
new file mode 100644
index 0000000..4ec0d29
Binary files /dev/null and b/hyde/layouts/test/content/favicon.ico differ
diff --git a/hyde/layouts/test/content/robots.txt b/hyde/layouts/test/content/robots.txt
new file mode 100644
index 0000000..d310d07
--- /dev/null
+++ b/hyde/layouts/test/content/robots.txt
@@ -0,0 +1,5 @@
+# www.robotstxt.org/
+# www.google.com/support/webmasters/bin/answer.py?hl=en&answer=156449
+
+User-agent: *
+
diff --git a/hyde/layouts/test/info.yaml b/hyde/layouts/test/info.yaml
new file mode 100644
index 0000000..8b79d85
--- /dev/null
+++ b/hyde/layouts/test/info.yaml
@@ -0,0 +1,4 @@
+author: Lakshmi Vyasarajan
+description: A test layout for hyde.
+template: jinja2 (2.6)
+version: 0.1
\ No newline at end of file
diff --git a/hyde/layouts/test/layout/base.html b/hyde/layouts/test/layout/base.html
new file mode 100644
index 0000000..62bafb8
--- /dev/null
+++ b/hyde/layouts/test/layout/base.html
@@ -0,0 +1,57 @@
+{% extends "root.html" %}
+{% block all %}
+
+
+
+ {% block starthead %}{% endblock starthead %}
+
+
+
+ {% block title %}{{page.meta.title}}{% endblock %}
+
+
+
+
+
+
+ {% block favicons %}
+
+
+
+ {% endblock favicons %}
+
+ {% block css %}
+
+ {% endblock css %}
+ {% block endhead %}{% endblock endhead %}
+
+
+ {% block content %}
+
+ {% endblock content%}
+
+ {% block js %}
+
+ {% block jquery %}
+
+
+ {% endblock jquery %}
+
+ {% block scripts %}{% endblock scripts %}
+ {%endblock js %}
+
+
+
+{% endblock all %}
\ No newline at end of file
diff --git a/hyde/layouts/test/layout/blog/post.html b/hyde/layouts/test/layout/blog/post.html
new file mode 100644
index 0000000..8c52553
--- /dev/null
+++ b/hyde/layouts/test/layout/blog/post.html
@@ -0,0 +1,10 @@
+{% extends "base.html" %}
+
+{% block main %}
+
+ {% block article %}{% endblock %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/hyde/layouts/test/layout/root.html b/hyde/layouts/test/layout/root.html
new file mode 100644
index 0000000..00f6151
--- /dev/null
+++ b/hyde/layouts/test/layout/root.html
@@ -0,0 +1 @@
+{% block all %}{% endblock all %}
\ No newline at end of file
diff --git a/hyde/layouts/test/media/css/site.css b/hyde/layouts/test/media/css/site.css
new file mode 100644
index 0000000..aa72a24
--- /dev/null
+++ b/hyde/layouts/test/media/css/site.css
@@ -0,0 +1,4 @@
+body{
+ margin: 0 auto;
+ width: 960px;
+}
\ No newline at end of file
diff --git a/hyde/layouts/test/site.yaml b/hyde/layouts/test/site.yaml
new file mode 100644
index 0000000..4fc9fce
--- /dev/null
+++ b/hyde/layouts/test/site.yaml
@@ -0,0 +1,7 @@
+mode: development
+media_root:: media # Relative path from site root (the directory where this file exists)
+media_url: /media
+template: hyde.ext.jinja2
+widgets:
+plugins:
+aggregators:
\ No newline at end of file
diff --git a/hyde/model.py b/hyde/model.py
index 4acb7bb..da3a35c 100644
--- a/hyde/model.py
+++ b/hyde/model.py
@@ -35,6 +35,7 @@ class Config(Expando):
def __init__(self, site_path, config_dict=None):
default_config = dict(
content_root = 'content',
+ deploy_root = 'deploy',
media_root = 'media',
layout_root = 'layout',
media_url = '/media',
@@ -46,6 +47,14 @@ class Config(Expando):
super(Config, self).__init__(conf)
self.site_path = Folder(site_path)
+
+ @property
+ def deploy_root_path(self):
+ """
+ Derives the deploy root path from the site path
+ """
+ return self.site_path.child_folder(self.deploy_root)
+
@property
def content_root_path(self):
"""
diff --git a/hyde/site.py b/hyde/site.py
index 65adea0..010d1bc 100644
--- a/hyde/site.py
+++ b/hyde/site.py
@@ -3,32 +3,30 @@
Parses & holds information about the site to be generated.
"""
-
-
from hyde.exceptions import HydeException
-from hyde.fs import File, Folder
-from hyde.model import Config, Expando
+from hyde.fs import FS, File, Folder
+from hyde.model import Config
import logging
-import os
from logging import NullHandler
logger = logging.getLogger('hyde.site')
logger.addHandler(NullHandler())
-
-class Resource(object):
+class Processable(object):
"""
- Represents any file that is processed by hyde
+ A node or resource.
"""
- def __init__(self, source_file, node):
- super(Resource, self).__init__()
- self.source_file = source_file
- if not node:
- raise HydeException("Resource cannot exist without a node")
- if not source_file:
- raise HydeException("Source file is required to instantiate a resource")
- self.node = node
+ def __init__(self, source):
+ super(Processable, self).__init__()
+ self.source = FS.file_or_folder(source)
+
+ @property
+ def name(self):
+ """
+ The resource name
+ """
+ return self.source.name
def __repr__(self):
return self.path
@@ -38,7 +36,22 @@ class Resource(object):
"""
Gets the source path of this node.
"""
- return self.source_file.path
+ return self.source.path
+
+class Resource(Processable):
+ """
+ Represents any file that is processed by hyde
+ """
+
+ def __init__(self, source_file, node):
+ super(Resource, self).__init__(source_file)
+ self.source_file = source_file
+ if not node:
+ raise HydeException("Resource cannot exist without a node")
+ if not source_file:
+ raise HydeException("Source file is required"
+ " to instantiate a resource")
+ self.node = node
@property
def relative_path(self):
@@ -47,15 +60,17 @@ class Resource(object):
"""
return self.source_file.get_relative_path(self.node.root.source_folder)
-class Node(object):
+
+class Node(Processable):
"""
Represents any folder that is processed by hyde
"""
def __init__(self, source_folder, parent=None):
- super(Node, self).__init__()
+ super(Node, self).__init__(source_folder)
if not source_folder:
- raise HydeException("Source folder is required to instantiate a node.")
+ raise HydeException("Source folder is required"
+ " to instantiate a node.")
self.root = self
self.module = None
self.site = None
@@ -68,16 +83,14 @@ class Node(object):
self.child_nodes = []
self.resources = []
- def __repr__(self):
- return self.path
-
def add_child_node(self, folder):
"""
Creates a new child node and adds it to the list of child nodes.
"""
if folder.parent != self.source_folder:
- raise HydeException("The given folder [%s] is not a direct descendant of [%s]" %
+ raise HydeException("The given folder [%s] is not a"
+ " direct descendant of [%s]" %
(folder, self.source_folder))
node = Node(folder, self)
self.child_nodes.append(node)
@@ -89,18 +102,26 @@ class Node(object):
"""
if afile.parent != self.source_folder:
- raise HydeException("The given file [%s] is not a direct descendant of [%s]" %
+ raise HydeException("The given file [%s] is not"
+ " a direct descendant of [%s]" %
(afile, self.source_folder))
resource = Resource(afile, self)
self.resources.append(resource)
return resource
- @property
- def path(self):
+ def walk(self):
+ yield self
+ for child in self.child_nodes:
+ for node in child.walk():
+ yield node
+
+ def walk_resources(self):
"""
- Gets the source path of this node.
+ Walks the resources in this hierarchy.
"""
- return self.source_folder.path
+ for node in self.walk():
+ for resource in node.resources:
+ yield resource
@property
def relative_path(self):
@@ -109,20 +130,22 @@ class Node(object):
"""
return self.source_folder.get_relative_path(self.root.source_folder)
+
class RootNode(Node):
"""
Represents one of the roots of site: Content, Media or Layout
"""
def __init__(self, source_folder, site):
- super(RootNode, self).__init__(source_folder)
- self.site = site
- self.node_map = {}
- self.resource_map = {}
+ super(RootNode, self).__init__(source_folder)
+ self.site = site
+ self.node_map = {}
+ self.resource_map = {}
def node_from_path(self, path):
"""
- Gets the node that maps to the given path. If no match is found it returns None.
+ Gets the node that maps to the given path.
+ If no match is found it returns None.
"""
if Folder(path) == self.source_folder:
return self
@@ -130,26 +153,32 @@ class RootNode(Node):
def node_from_relative_path(self, relative_path):
"""
- Gets the content node that maps to the given relative path. If no match is found it returns None.
+ Gets the content node that maps to the given relative path.
+ If no match is found it returns None.
"""
- return self.node_from_path(self.source_folder.child(str(relative_path)))
+ return self.node_from_path(
+ self.source_folder.child(str(relative_path)))
def resource_from_path(self, path):
"""
- Gets the resource that maps to the given path. If no match is found it returns None.
+ Gets the resource that maps to the given path.
+ If no match is found it returns None.
"""
return self.resource_map.get(str(File(path)), None)
def resource_from_relative_path(self, relative_path):
"""
- Gets the content resource that maps to the given relative path. If no match is found it returns None.
+ Gets the content resource that maps to the given relative path.
+ If no match is found it returns None.
"""
- return self.resource_from_path(self.source_folder.child(str(relative_path)))
+ return self.resource_from_path(
+ self.source_folder.child(str(relative_path)))
def add_node(self, a_folder):
"""
- Adds a new node to this folder's hierarchy. Also adds to to the hashtable of path to
- node associations for quick lookup.
+ Adds a new node to this folder's hierarchy.
+ Also adds to to the hashtable of path to node associations
+ for quick lookup.
"""
folder = Folder(a_folder)
node = self.node_from_path(folder)
@@ -158,7 +187,8 @@ class RootNode(Node):
return node
if not folder.is_descendant_of(self.source_folder):
- raise HydeException("The given folder [%s] does not belong to this hierarchy [%s]" %
+ raise HydeException("The given folder [%s] does not"
+ " belong to this hierarchy [%s]" %
(folder, self.source_folder))
p_folder = folder
@@ -174,14 +204,15 @@ class RootNode(Node):
for h_folder in hierarchy:
node = node.add_child_node(h_folder)
self.node_map[str(h_folder)] = node
- logger.info("Added node [%s] to [%s]" % (node.relative_path, self.source_folder))
+ logger.info("Added node [%s] to [%s]" % (
+ node.relative_path, self.source_folder))
return node
def add_resource(self, a_file):
"""
- Adds a file to the parent node. Also adds to to the hashtable of path to
- resource associations for quick lookup.
+ Adds a file to the parent node. Also adds to to the
+ hashtable of path to resource associations for quick lookup.
"""
afile = File(a_file)
@@ -191,7 +222,8 @@ class RootNode(Node):
logger.info("Resource exists at [%s]" % resource.relative_path)
if not afile.is_descendant_of(self.source_folder):
- raise HydeException("The given file [%s] does not reside in this hierarchy [%s]" %
+ raise HydeException("The given file [%s] does not reside"
+ " in this hierarchy [%s]" %
(afile, self.content_folder))
node = self.node_from_path(afile.parent)
@@ -201,19 +233,22 @@ class RootNode(Node):
resource = node.add_child_resource(afile)
self.resource_map[str(afile)] = resource
- logger.info("Added resource [%s] to [%s]" % (resource.relative_path, self.source_folder))
+ logger.info("Added resource [%s] to [%s]" %
+ (resource.relative_path, self.source_folder))
return resource
def build(self):
"""
- Walks the `source_folder` and builds the sitemap. Creates nodes and resources,
- reads metadata and injects attributes. This is the model for hyde.
+ Walks the `source_folder` and builds the sitemap.
+ Creates nodes and resources, reads metadata and injects attributes.
+ This is the model for hyde.
"""
if not self.source_folder.exists:
- raise HydeException("The given source folder[%s] does not exist" % self.source_folder)
+ raise HydeException("The given source folder[%s]"
+ " does not exist" % self.source_folder)
- with self.source_folder.walk() as walker:
+ with self.source_folder.walker as walker:
@walker.folder_visitor
def visit_folder(folder):
@@ -223,6 +258,7 @@ class RootNode(Node):
def visit_file(afile):
self.add_resource(afile)
+
class Site(object):
"""
Represents the site to be generated.
@@ -232,9 +268,7 @@ class Site(object):
super(Site, self).__init__()
self.site_path = Folder(str(site_path))
self.config = config if config else Config(self.site_path)
- self.content = RootNode(self.config.content_root_path, self )
- self.node_map = {}
- self.resource_map = {}
+ self.content = RootNode(self.config.content_root_path, self)
def build(self):
"""
diff --git a/hyde/template.py b/hyde/template.py
index 7b13124..0629ea1 100644
--- a/hyde/template.py
+++ b/hyde/template.py
@@ -19,9 +19,9 @@ class Template(object):
"""
abstract
- def render(self, template_name, context):
+ def render(self, resource, context):
"""
- Given the name of a template (partial path usually), and the context, this function
+ Given the resource, and the context, this function
must return the rendered string.
"""
abstract
\ No newline at end of file
diff --git a/hyde/tests/sites/test_jinja/content/404.html b/hyde/tests/sites/test_jinja/content/404.html
new file mode 100644
index 0000000..b5ea1ac
--- /dev/null
+++ b/hyde/tests/sites/test_jinja/content/404.html
@@ -0,0 +1,22 @@
+
+not found
+
+
+
+
+
+
+
+
Not found
+
:(
+
\ No newline at end of file
diff --git a/hyde/tests/sites/test_jinja/content/apple-touch-icon.png b/hyde/tests/sites/test_jinja/content/apple-touch-icon.png
new file mode 100644
index 0000000..1f1972b
Binary files /dev/null and b/hyde/tests/sites/test_jinja/content/apple-touch-icon.png differ
diff --git a/hyde/tests/sites/test_jinja/content/blog/2010/december/merry-christmas.html b/hyde/tests/sites/test_jinja/content/blog/2010/december/merry-christmas.html
index d3bfe68..fd78860 100644
--- a/hyde/tests/sites/test_jinja/content/blog/2010/december/merry-christmas.html
+++ b/hyde/tests/sites/test_jinja/content/blog/2010/december/merry-christmas.html
@@ -1,9 +1,9 @@
{% extends "blog/post.html" %}
{% block article %}
- {% lipsum n=10 %}
+ {{ lipsum() }}
{% endblock %}
{% block aside %}
- {% lipsum n=2 %}
+ {{ lipsum() }}
{% endblock %}
\ No newline at end of file
diff --git a/hyde/tests/sites/test_jinja/content/crossdomain.xml b/hyde/tests/sites/test_jinja/content/crossdomain.xml
new file mode 100644
index 0000000..0d42929
--- /dev/null
+++ b/hyde/tests/sites/test_jinja/content/crossdomain.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/hyde/tests/sites/test_jinja/content/favicon.ico b/hyde/tests/sites/test_jinja/content/favicon.ico
new file mode 100644
index 0000000..4ec0d29
Binary files /dev/null and b/hyde/tests/sites/test_jinja/content/favicon.ico differ
diff --git a/hyde/tests/sites/test_jinja/content/robots.txt b/hyde/tests/sites/test_jinja/content/robots.txt
new file mode 100644
index 0000000..d310d07
--- /dev/null
+++ b/hyde/tests/sites/test_jinja/content/robots.txt
@@ -0,0 +1,5 @@
+# www.robotstxt.org/
+# www.google.com/support/webmasters/bin/answer.py?hl=en&answer=156449
+
+User-agent: *
+
diff --git a/hyde/tests/sites/test_jinja/layout/base.html b/hyde/tests/sites/test_jinja/layout/base.html
index 127f5fb..afce0f3 100644
--- a/hyde/tests/sites/test_jinja/layout/base.html
+++ b/hyde/tests/sites/test_jinja/layout/base.html
@@ -16,12 +16,12 @@
{% block favicons %}
-
-
+
+
{% endblock favicons %}
{% block css %}
-
+
{% endblock css %}
{% block endhead %}{% endblock endhead %}
diff --git a/hyde/tests/sites/test_jinja/site.yaml b/hyde/tests/sites/test_jinja/site.yaml
index 95aac84..4fc9fce 100644
--- a/hyde/tests/sites/test_jinja/site.yaml
+++ b/hyde/tests/sites/test_jinja/site.yaml
@@ -1,6 +1,7 @@
mode: development
media_root:: media # Relative path from site root (the directory where this file exists)
media_url: /media
+template: hyde.ext.jinja2
widgets:
plugins:
aggregators:
\ No newline at end of file
diff --git a/hyde/tests/test_fs.py b/hyde/tests/test_fs.py
index 4e29106..c1acad5 100644
--- a/hyde/tests/test_fs.py
+++ b/hyde/tests/test_fs.py
@@ -44,6 +44,10 @@ def test_kind():
f = File(__file__)
assert f.kind == os.path.splitext(__file__)[1].lstrip('.')
+def test_path_expands_user():
+ f = File("~/abc/def")
+ assert f.path == os.path.expanduser("~/abc/def")
+
def test_parent():
f = File(__file__)
p = f.parent
@@ -104,6 +108,7 @@ JINJA2 = TEMPLATE_ROOT.child_folder('jinja2')
HELPERS = File(JINJA2.child('helpers.html'))
INDEX = File(JINJA2.child('index.html'))
LAYOUT = File(JINJA2.child('layout.html'))
+LOGO = File(TEMPLATE_ROOT.child('../../../resources/hyde-logo.png'))
def test_ancestors():
depth = 0
@@ -132,10 +137,11 @@ def test_is_descendant_of():
print "*"
assert not INDEX.is_descendant_of(DATA_ROOT)
-def test_fragment():
+def test_get_relative_path():
assert INDEX.get_relative_path(TEMPLATE_ROOT) == Folder(JINJA2.name).child(INDEX.name)
assert INDEX.get_relative_path(TEMPLATE_ROOT.parent) == Folder(
TEMPLATE_ROOT.name).child_folder(JINJA2.name).child(INDEX.name)
+ assert JINJA2.get_relative_path(JINJA2) == ""
def test_get_mirror():
mirror = JINJA2.get_mirror(DATA_ROOT, source_root=TEMPLATE_ROOT)
@@ -143,6 +149,18 @@ def test_get_mirror():
mirror = JINJA2.get_mirror(DATA_ROOT, source_root=TEMPLATE_ROOT.parent)
assert mirror == DATA_ROOT.child_folder(TEMPLATE_ROOT.name).child_folder(JINJA2.name)
+def test_mimetype():
+ assert HELPERS.mimetype == 'text/html'
+ assert LOGO.mimetype == 'image/png'
+
+def test_is_text():
+ assert HELPERS.is_text
+ assert not LOGO.is_text
+
+def test_is_image():
+ assert not HELPERS.is_image
+ assert LOGO.is_image
+
@nottest
def setup_data():
DATA_ROOT.make()
@@ -242,7 +260,7 @@ def test_walker():
files = []
complete = []
- with TEMPLATE_ROOT.walk() as walker:
+ with TEMPLATE_ROOT.walker as walker:
@walker.folder_visitor
def visit_folder(f):
@@ -265,12 +283,34 @@ def test_walker():
assert len(folders) == 2
assert len(complete) == 1
+def test_walker_walk_all():
+ items = list(TEMPLATE_ROOT.walker.walk_all())
+ assert len(items) == 6
+ assert TEMPLATE_ROOT in items
+ assert JINJA2 in items
+ assert INDEX in items
+ assert HELPERS in items
+ assert LAYOUT in items
+
+def test_walker_walk_files():
+ items = list(TEMPLATE_ROOT.walker.walk_files())
+ assert len(items) == 4
+ assert INDEX in items
+ assert HELPERS in items
+ assert LAYOUT in items
+
+def test_walker_walk_folders():
+ items = list(TEMPLATE_ROOT.walker.walk_folders())
+ assert len(items) == 2
+ assert TEMPLATE_ROOT in items
+ assert JINJA2 in items
+
def test_walker_templates_just_root():
folders = []
files = []
complete = []
- with TEMPLATE_ROOT.walk() as walker:
+ with TEMPLATE_ROOT.walker as walker:
@walker.folder_visitor
def visit_folder(f):
@@ -295,7 +335,7 @@ def test_lister_templates():
files = []
complete = []
- with TEMPLATE_ROOT.list() as lister:
+ with TEMPLATE_ROOT.lister as lister:
@lister.folder_visitor
def visit_folder(f):
@@ -315,12 +355,39 @@ def test_lister_templates():
assert len(complete) == 1
+def test_lister_list_all():
+ items = list(TEMPLATE_ROOT.lister.list_all())
+ assert len(items) == 1
+ assert JINJA2 in items
+ items = list(JINJA2.lister.list_all())
+ assert len(items) == 4
+ assert INDEX in items
+ assert HELPERS in items
+ assert LAYOUT in items
+
+
+def test_lister_list_files():
+ items = list(TEMPLATE_ROOT.lister.list_files())
+ assert len(items) == 0
+ items = list(JINJA2.lister.list_files())
+ assert len(items) == 4
+ assert INDEX in items
+ assert HELPERS in items
+ assert LAYOUT in items
+
+def test_lister_list_folders():
+ items = list(TEMPLATE_ROOT.lister.list_folders())
+ assert len(items) == 1
+ assert JINJA2 in items
+ items = list(JINJA2.lister.list_folders())
+ assert len(items) == 0
+
def test_lister_jinja2():
folders = []
files = []
complete = []
- with JINJA2.list() as lister:
+ with JINJA2.lister as lister:
@lister.folder_visitor
def visit_folder(f):
diff --git a/hyde/tests/test_initialize.py b/hyde/tests/test_initialize.py
index 7e5613e..38ba8d4 100644
--- a/hyde/tests/test_initialize.py
+++ b/hyde/tests/test_initialize.py
@@ -9,10 +9,11 @@ Use nose
from hyde.engine import Engine
from hyde.exceptions import HydeException
from hyde.fs import FS, File, Folder
-
+from hyde.layout import Layout
from nose.tools import raises, with_setup, nottest
TEST_SITE = File(__file__).parent.child_folder('_test')
+TEST_SITE_AT_USER = Folder('~/_test')
@nottest
def create_test_site():
@@ -22,6 +23,14 @@ def create_test_site():
def delete_test_site():
TEST_SITE.delete()
+@nottest
+def create_test_site_at_user():
+ TEST_SITE_AT_USER.make()
+
+@nottest
+def delete_test_site_at_user():
+ TEST_SITE_AT_USER.delete()
+
@raises(HydeException)
@with_setup(create_test_site, delete_test_site)
def test_ensure_exception_when_sitepath_exists():
@@ -39,9 +48,29 @@ def test_ensure_no_exception_when_sitepath_does_not_exist():
e = Engine()
TEST_SITE.delete()
e.run(e.parse(['-s', str(TEST_SITE), 'init', '-f']))
- assert TEST_SITE.exists
- assert TEST_SITE.child_folder('layout').exists
- assert File(TEST_SITE.child('info.yaml')).exists
+ verify_site_contents(TEST_SITE, Layout.find_layout())
+
+@with_setup(create_test_site_at_user, delete_test_site_at_user)
+def test_ensure_can_create_site_at_user():
+ e = Engine()
+ TEST_SITE_AT_USER.delete()
+ e.run(e.parse(['-s', str(TEST_SITE_AT_USER), 'init', '-f']))
+ verify_site_contents(TEST_SITE_AT_USER, Layout.find_layout())
+
+@nottest
+def verify_site_contents(site, layout):
+ assert site.exists
+ assert site.child_folder('layout').exists
+ assert File(site.child('info.yaml')).exists
+
+ expected = map(lambda f: f.get_relative_path(layout), layout.walker.walk_all())
+ actual = map(lambda f: f.get_relative_path(site), site.walker.walk_all())
+ assert actual
+ assert expected
+
+ expected.sort()
+ actual.sort()
+ assert actual == expected
@raises(HydeException)
@with_setup(create_test_site, delete_test_site)
diff --git a/hyde/tests/test_model.py b/hyde/tests/test_model.py
index 1a4eb18..13d0fb6 100644
--- a/hyde/tests/test_model.py
+++ b/hyde/tests/test_model.py
@@ -44,6 +44,7 @@ class TestConfig(object):
cls.conf2 = """
mode: development
+ deploy_root: ~/deploy_site
content_root: site/stuff # Relative path from site root
media_root: mmm # Relative path from site root
media_url: /media
@@ -62,6 +63,9 @@ class TestConfig(object):
assert hasattr(c, path)
assert getattr(c, path) == TEST_SITE_ROOT.child_folder(root)
+ assert c.deploy_root_path == TEST_SITE_ROOT.child_folder('deploy')
+
+
def test_conf1(self):
c = Config(site_path=TEST_SITE_ROOT, config_dict=yaml.load(self.conf1))
assert c.content_root_path == TEST_SITE_ROOT.child_folder('stuff')
@@ -71,3 +75,5 @@ class TestConfig(object):
assert c.content_root_path == TEST_SITE_ROOT.child_folder('site/stuff')
assert c.media_root_path == TEST_SITE_ROOT.child_folder('mmm')
assert c.media_url == TEST_SITE_ROOT.child_folder('/media')
+ print c.deploy_root_path
+ assert c.deploy_root_path == Folder('~/deploy_site')
\ No newline at end of file
diff --git a/hyde/tests/test_site.py b/hyde/tests/test_site.py
index 07e24c0..e045985 100644
--- a/hyde/tests/test_site.py
+++ b/hyde/tests/test_site.py
@@ -65,6 +65,23 @@ def test_build():
assert resource.relative_path == path
assert not s.content.resource_from_relative_path('/happy-festivus.html')
+def test_walk_resources():
+ s = Site(TEST_SITE_ROOT)
+ s.build()
+ pages = [page.name for page in s.content.walk_resources()]
+ expected = ["404.html",
+ "about.html",
+ "apple-touch-icon.png",
+ "merry-christmas.html",
+ "crossdomain.xml",
+ "favicon.ico",
+ "robots.txt"
+ ]
+ pages.sort()
+ expected.sort()
+ assert pages == expected
+
+
class TestSiteWithConfig(object):
@classmethod
diff --git a/resources/hyde-logo.png b/resources/hyde-logo.png
new file mode 100644
index 0000000..2d3d77c
Binary files /dev/null and b/resources/hyde-logo.png differ
diff --git a/resources/hyde-lt-b.png b/resources/hyde-lt-b.png
new file mode 100644
index 0000000..09c383a
Binary files /dev/null and b/resources/hyde-lt-b.png differ
diff --git a/resources/hyde-lt.png b/resources/hyde-lt.png
new file mode 100644
index 0000000..d2a2dd6
Binary files /dev/null and b/resources/hyde-lt.png differ