Browse Source

Site building is functional

main
Lakshmi Vyasarajan 14 years ago
parent
commit
eac4f1c0d2
18 changed files with 471 additions and 688 deletions
  1. +1
    -1
      .gitignore
  2. +1
    -0
      hyde/engine.py
  3. +0
    -678
      hyde/filesystem.py
  4. +21
    -3
      hyde/fs.py
  5. +9
    -0
      hyde/layouts/basic/site.yaml
  6. +5
    -1
      hyde/plugin.py
  7. +245
    -0
      hyde/site.py
  8. +7
    -0
      hyde/tests/sites/test_jinja/content/about.html
  9. +9
    -0
      hyde/tests/sites/test_jinja/content/blog/2010/december/merry-christmas.html
  10. +4
    -0
      hyde/tests/sites/test_jinja/info.yaml
  11. +56
    -0
      hyde/tests/sites/test_jinja/layout/base.html
  12. +10
    -0
      hyde/tests/sites/test_jinja/layout/blog/post.html
  13. +1
    -0
      hyde/tests/sites/test_jinja/layout/root.html
  14. +4
    -0
      hyde/tests/sites/test_jinja/media/css/site.css
  15. +9
    -0
      hyde/tests/sites/test_jinja/site.yaml
  16. +21
    -4
      hyde/tests/test_fs.py
  17. +62
    -0
      hyde/tests/test_site.py
  18. +6
    -1
      hyde/tests/util.py

+ 1
- 1
.gitignore View File

@@ -11,4 +11,4 @@ test_site
dist
build
*egg*
.idea

+ 1
- 0
hyde/engine.py View File

@@ -50,6 +50,7 @@ class Engine(Application):
layout.copy_contents_to(args.sitepath)

@subcommand('gen', help='Generate the site')
@store('-c', '--config-path', default='site.yaml', 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):
"""


+ 0
- 678
hyde/filesystem.py View File

@@ -1,678 +0,0 @@
"""

Unified interface for performing file system tasks. Uses os, os.path. shutil
and distutil to perform the tasks. The behavior of some functions is slightly
contaminated with requirements from Hyde: For example, the backup function
deletes the directory that is being backed up.

"""
import os
import shutil
import codecs
import fnmatch
from datetime import datetime
# pylint: disable-msg=E0611
from distutils import dir_util, file_util

@staticmethod
def filter_hidden_inplace(item_list):
"""
Given a list of filenames, removes filenames for invisible files (whose
names begin with dots) or files whose names end in tildes '~'.
Does not remove files with the filname '.htaccess'.
The list is modified in-place; this function has no return value.
"""

if not item_list:
return

wanted = filter(
lambda item:
not ((item.startswith('.') and item != ".htaccess")
or item.endswith('~')),
item_list)

count = len(item_list)
good_item_count = len(wanted)

if count == good_item_count:
return

item_list[:good_item_count] = wanted
for _ in range(good_item_count, count):
item_list.pop()

@staticmethod
def get_path_fragment(root_dir, a_dir):
"""
Gets the path fragment starting at root_dir to a_dir
"""
current_dir = a_dir
current_fragment = ''
while not current_dir == root_dir:
(current_dir, current_fragment_part) = os.path.split(current_dir)
current_fragment = os.path.join(
current_fragment_part, current_fragment)
return current_fragment

@staticmethod
def get_mirror_dir(directory, source_root, mirror_root, ignore_root = False):
"""
Returns the mirror directory from source_root to mirror_root
"""
current_fragment = get_path_fragment(source_root, directory)

if not current_fragment:
return mirror_root

mirror_directory = mirror_root
if not ignore_root:
mirror_directory = os.path.join(
mirror_root,
os.path.basename(source_root))

mirror_directory = os.path.join(
mirror_directory, current_fragment)
return mirror_directory


@staticmethod
def mirror_dir_tree(directory, source_root, mirror_root, ignore_root = False):
"""
Create the mirror directory tree
"""

mirror_directory = get_mirror_dir(
directory, source_root,
mirror_root, ignore_root)
try:
os.makedirs(mirror_directory)
except os.error:
pass
return mirror_directory


class FileSystemEntity(object):
"""
Base class for files and folders.
"""
def __init__(self, path):
super(FileSystemEntity, self).__init__()
if path is FileSystemEntity:
self.path = path.path
else:
self.path = path

def __str__(self):
return self.path

def __repr__(self):
return self.path

def allow(self, include=None, exclude=None):
"""
Given a set of wilcard patterns in the include and exclude arguments,
tests if the patterns allow this item for processing.

The exclude parameter is processed first as a broader filter and then
include is used as a narrower filter to override the results for more
specific files.

Example:
exclude = (".*", "*~")
include = (".htaccess")

"""
if not include:
include = ()
if not exclude:
exclude = ()

if reduce(lambda result,
pattern: result or
fnmatch.fnmatch(self.name, pattern), include, False):
return True

if reduce(lambda result, pattern:
result and not fnmatch.fnmatch(self.name, pattern),
exclude, True):
return True

return False

@property
def humblepath(self):
"""
Expands variables, user, normalizes path and case and coverts
to absolute.

"""
return os.path.abspath(
os.path.normpath(
os.path.normcase(
os.path.expandvars(
os.path.expanduser(self.path)))))

def same_as(self, other):
"""
Checks if the path of this object is same as `other`. `other` must
be a FileSystemEntity.

"""
return (self.humblepath.rstrip(os.sep) ==
other.humblepath.rstrip(os.sep))

@property
def exists(self):
"""
Checks if the entity exists in the file system.

"""
return os.path.exists(self.path)

@property
def isdir(self):
"""
Is this a folder.

"""
return os.path.isdir(self.path)

@property
def stats(self):
"""
Shortcut for os.stat.

"""

return os.stat(self.path)

@property
def name(self):
"""
Name of the entity. Calls os.path.basename.

"""

return os.path.basename(self.path)

@property
def parent(self):
"""
The parent folder. Returns a `Folder` object.

"""

return Folder(os.path.dirname(self.path))

def __get_destination__(self, destination):
"""
Returns a File or Folder object that would represent this entity
if it were copied or moved to `destination`. `destination` must be
an instance of File or Folder.

"""
if os.path.isdir(str(destination)):
target = destination.child(self.name)
if os.path.isdir(self.path):
return Folder(target)
else: return File(target)
else:
return destination

# pylint: disable-msg=R0904,W0142
class File(FileSystemEntity):
"""
Encapsulates commonly used functions related to files.

"""
def __init__(self, path):
super(File, self).__init__(path)

@property
def size(self):
"""
Gets the file size
"""
return os.path.getsize(self.path)
#return 1

def has_extension(self, extension):
"""
Checks if this file has the given extension.

"""
return self.extension == extension

def delete(self):
"""
Deletes if the file exists.

"""
if self.exists:
os.remove(self.path)

@property
def last_modified(self):
"""
Returns a datetime object representing the last modified time.
Calls os.path.getmtime.

"""
return datetime.fromtimestamp(os.path.getmtime(self.path))

def changed_since(self, basetime):
"""
Returns True if the file has been changed since the given time.

"""
return self.last_modified > basetime

def older_than(self, another_file):
"""
Checks if this file is older than the given file. Uses last_modified to
determine age.

"""
return another_file.last_modified > self.last_modified

@property
def path_without_extension(self):
"""
The full path of the file without its extension.

"""
return os.path.splitext(self.path)[0]

@property
def name_without_extension(self):
"""
Name of the file without its extension.

"""
return os.path.splitext(self.name)[0]

@property
def extension(self):
"""
File's extension prefixed with a dot.

"""
return os.path.splitext(self.path)[1]

@property
def kind(self):
"""
File's extension without a dot prefix.

"""
return self.extension.lstrip(".")

def move_to(self, destination):
"""
Moves the file to the given destination. Returns a File
object that represents the target file. `destination` must
be a File or Folder object.

"""
shutil.move(self.path, str(destination))
return self.__get_destination__(destination)

def copy_to(self, destination):
"""
Copies the file to the given destination. Returns a File
object that represents the target file. `destination` must
be a File or Folder object.

"""
shutil.copy(self.path, str(destination))
return self.__get_destination__(destination)

def write(self, text, encoding="utf-8"):
"""
Writes the given text to the file using the given encoding.

"""

fout = codecs.open(self.path, 'w', encoding)
fout.write(text)
fout.close()

def read_all(self):
"""
Reads from the file and returns the content as a string.

"""
fin = codecs.open(self.path, 'r')
read_text = fin.read()
fin.close()
return read_text

# pylint: disable-msg=R0904,W0142
class Folder(FileSystemEntity):
"""
Encapsulates commonly used directory functions.

"""

def __init__(self, path):
super(Folder, self).__init__(path)

def __str__(self):
return self.path

def __repr__(self):
return self.path

def delete(self):
"""
Deletes the directory if it exists.

"""
if self.exists:
shutil.rmtree(self.path)

def depth(self):
"""
Returns the number of ancestors of this directory.

"""
return len(self.path.split(os.sep))

def make(self):
"""
Creates this directory and any of the missing directories in the path.
Any errors that may occur are eaten.

"""
try:
if not self.exists:
os.makedirs(self.path)
except os.error:
pass
return self

def is_parent_of(self, other_entity):
"""
Returns True if this directory is a direct parent of the the given
directory.

"""
return self.same_as(other_entity.parent)

def is_ancestor_of(self, other_entity):
"""
Returns True if this directory is in the path of the given directory.
Note that this will return True if the given directory is same as this.

"""
folder = other_entity
while not folder.parent.same_as(folder):
folder = folder.parent
if self.same_as(folder):
return True
return False

def child(self, name):
"""
Returns a path of a child item represented by `name`.

"""
return os.path.join(self.path, name)

def child_folder(self, *args):
"""
Returns a Folder object by joining the path component in args
to this directory's path.

"""
return Folder(os.path.join(self.path, *args))

def child_folder_with_fragment(self, fragment):
"""
Returns a Folder object by joining the fragment to
this directory's path.

"""
return Folder(os.path.join(self.path, fragment.lstrip(os.sep)))

def get_fragment(self, root):
"""
Returns the path fragment of this directory starting with the given
directory.

"""
return get_path_fragment(str(root), self.path)

def get_mirror_folder(self, root, mirror_root, ignore_root=False):
"""
Returns a Folder object that reperesents if the entire fragment of this
directory starting with `root` were copied to `mirror_root`. If ignore_root
is True, the mirror does not include `root` directory itself.

Example:
Current Directory: /usr/local/hyde/stuff
root: /usr/local/hyde
mirror_root: /usr/tmp

Result:

if ignore_root == False:
Folder(/usr/tmp/hyde/stuff)
if ignore_root == True:
Folder(/usr/tmp/stuff)

"""
path = get_mirror_dir(self.path,
str(root), str(mirror_root), ignore_root)
return Folder(path)

def create_mirror_folder(self, root, mirror_root, ignore_root=False):
"""
Creates the mirror directory returned by `get_mirror_folder`

"""
mirror_folder = self.get_mirror_folder(
root, mirror_root, ignore_root)
mirror_folder.make()
return mirror_folder

def backup(self, destination):
"""
Creates a backup of this directory in the given destination. The backup is
suffixed with a number for uniqueness. Deletes this directory after backup
is complete.

"""
new_name = self.name
count = 0
dest = Folder(destination.child(new_name))
while(True):
dest = Folder(destination.child(new_name))
if not dest.exists:
break
else:
count = count + 1
new_name = self.name + str(count)
dest.make()
dest.move_contents_of(self)
self.delete()
return dest

def move_to(self, destination):
"""
Moves this directory to the given destination. Returns a Folder object
that represents the moved directory.

"""
shutil.copytree(self.path, str(destination))
shutil.rmtree(self.path)
return self.__get_destination__(destination)

def copy_to(self, destination):
"""
Copies this directory to the given destination. Returns a Folder object
that represents the moved directory.

"""
shutil.copytree(self.path, str(destination))
return self.__get_destination__(destination)

def move_folder_from(self, source, incremental=False):
"""
Moves the given source directory to this directory. If incremental is True
only newer objects are overwritten.

"""
self.copy_folder_from(source, incremental)
shutil.rmtree(str(source))

def copy_folder_from(self, source, incremental=False):
"""
Copies the given source directory to this directory. If incremental is True
only newer objects are overwritten.

"""
# There is a bug in dir_util that makes copy_tree crash if a folder in
# the tree has been deleted before and readded now. To workaround the
# bug, we first walk the tree and create directories that are needed.
#
# pylint: disable-msg=C0111,W0232
target_root = self
# pylint: disable-msg=R0903
class _DirCreator:
@staticmethod
def visit_folder(folder):
target = folder.get_mirror_folder(
source.parent, target_root, ignore_root=True)
target.make()

source.walk(_DirCreator)

dir_util.copy_tree(str(source),
self.child(source.name),
update=incremental)

def move_contents_of(self, source, move_empty_folders=True,
incremental=False):
"""
Moves the contents of the given source directory to this directory. If
incremental is True only newer objects are overwritten.

"""
# pylint: disable-msg=C0111,W0232
class _Mover:
@staticmethod
def visit_folder(folder):
self.move_folder_from(folder, incremental)
@staticmethod
def visit_file(a_file):
self.move_file_from(a_file, incremental)
source.list(_Mover, move_empty_folders)

def copy_contents_of(self, source, copy_empty_folders=True,
incremental=False):
"""
Copies the contents of the given source directory to this directory. If
incremental is True only newer objects are overwritten.

"""
# pylint: disable-msg=C0111,W0232
class _Copier:
@staticmethod
def visit_folder(folder):
self.copy_folder_from(folder, incremental)
@staticmethod
def visit_file(a_file):
self.copy_file_from(a_file, incremental)
source.list(_Copier, copy_empty_folders)

def move_file_from(self, source, incremental=False):
"""
Moves the given source file to this directory. If incremental is True the
move is performed only if the source file is newer.

"""
self.copy_file_from(source, incremental)
source.delete()

def copy_file_from(self, source, incremental=False):
"""
Copies the given source file to this directory. If incremental is True the
move is performed only if the source file is newer.

"""
file_util.copy_file(str(source), self.path, update=incremental)

def list(self, visitor, list_empty_folders=True):
"""
Calls the visitor.visit_file or visitor.visit_folder for each file or folder
in this directory. If list_empty_folders is False folders that are empty are
skipped.
"""
a_files = os.listdir(self.path)
for a_file in a_files:
path = os.path.join(self.path, str(a_file))
if os.path.isdir(path):
if not list_empty_folders:
if Folder(path).empty():
continue
visitor.visit_folder(Folder(path))
else:
visitor.visit_file(File(path))

def empty(self):
"""
Checks if this directory or any of its subdirectories contain files.

"""
paths = os.listdir(self.path)
for path in paths:
if os.path.isdir(path):
if not Folder(path).empty():
return False
else:
return False
return True

def walk(self, visitor = None, pattern = None):
"""
Walks the entire hirearchy of this directory starting with itself.
Calls visitor.visit_folder first and then calls visitor.visit_file for
any files found. After all files and folders have been exhausted
visitor.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 __visit_folder__(visitor, folder):
process_folder = True
if visitor and hasattr(visitor,'visit_folder'):
process_folder = visitor.visit_folder(folder)
# If there is no return value assume true
#
if process_folder is None:
process_folder = True
return process_folder

def __visit_file__(visitor, a_file):
if visitor and hasattr(visitor,'visit_file'):
visitor.visit_file(a_file)

def __visit_complete__(visitor):
if visitor and hasattr(visitor,'visit_complete'):
visitor.visit_complete()

for root, dirs, a_files in os.walk(self.path):
folder = Folder(root)
if not __visit_folder__(visitor, folder):
dirs[:] = []
continue
for a_file in a_files:
if not pattern or fnmatch.fnmatch(a_file, pattern):
__visit_file__(visitor, File(folder.child(a_file)))
__visit_complete__(visitor)

+ 21
- 3
hyde/fs.py View File

@@ -14,6 +14,7 @@ import os
import shutil
from distutils import dir_util
import functools
import itertools
# pylint: disable-msg=E0611

logger = logging.getLogger('fs')
@@ -28,7 +29,7 @@ class FS(object):
"""
def __init__(self, path):
super(FS, self).__init__()
self.path = str(path).strip()
self.path = str(path).strip().rstrip(os.sep)

def __str__(self):
return self.path
@@ -63,6 +64,14 @@ class FS(object):
"""
return Folder(os.path.dirname(self.path))

@property
def depth(self):
"""
Returns the number of ancestors of this directory.
"""
return len(self.path.rstrip(os.sep).split(os.sep))


def ancestors(self, stop=None):
"""
Generates the parents until stop or the absolute
@@ -75,7 +84,16 @@ class FS(object):
yield f.parent
f = f.parent

def get_fragment(self, root):
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

def get_relative_path(self, root):
"""
Gets the fragment of the current path starting at root.
"""
@@ -89,7 +107,7 @@ class FS(object):
>>> Folder('/usr/local/hyde/stuff').get_mirror('/usr/tmp', source_root='/usr/local/hyde')
Folder('/usr/tmp/stuff')
"""
fragment = self.get_fragment(source_root if source_root else self.parent)
fragment = self.get_relative_path(source_root if source_root else self.parent)
return Folder(target_root).child(fragment)

@staticmethod


+ 9
- 0
hyde/layouts/basic/site.yaml View File

@@ -0,0 +1,9 @@
site:
mode: development
media:
root:
path: media # Relative path from site root (the directory where this file exists)
url: /media
widgets:
plugins:
aggregators:

+ 5
- 1
hyde/plugin.py View File

@@ -1 +1,5 @@
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
# 1. start_node
# 2. start_resource
# 3. end_resource
# 4. end_node

+ 245
- 0
hyde/site.py View File

@@ -0,0 +1,245 @@
# -*- coding: utf-8 -*-
"""
Parses & holds information about the site to be generated.
"""


from hyde.fs import File, Folder
from hyde.exceptions import HydeException

import logging
import os
from logging import NullHandler
logger = logging.getLogger('hyde.site')
logger.addHandler(NullHandler())


class Resource(object):
"""
Represents any file that is processed by hyde
"""

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 __repr__(self):
return self.path

@property
def path(self):
"""
Gets the source path of this node.
"""
return self.source_file.path

@property
def relative_path(self):
"""
Gets the path relative to the root folder (Content, Media, Layout)
"""
return self.source_file.get_relative_path(self.node.root.source_folder)

class Node(object):
"""
Represents any folder that is processed by hyde
"""

def __init__(self, source_folder, parent=None):
super(Node, self).__init__()
if not source_folder:
raise HydeException("Source folder is required to instantiate a node.")
self.root = self
self.module = None
self.site = None
self.source_folder = Folder(str(source_folder))
self.parent = parent
if parent:
self.root = self.parent.root
self.module = self.parent.module if self.parent.module else self
self.site = parent.site
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]" %
(folder, self.source_folder))
node = Node(folder, self)
self.child_nodes.append(node)
return node

def add_child_resource(self, afile):
"""
Creates a new resource and adds it to the list of child resources.
"""

if afile.parent != self.source_folder:
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):
"""
Gets the source path of this node.
"""
return self.source_folder.path

@property
def relative_path(self):
"""
Gets the path relative to the root folder (Content, Media, Layout)
"""
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 = {}

def node_from_path(self, path):
"""
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
return self.node_map.get(str(Folder(path)), None)

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.
"""
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.
"""
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.
"""
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.
"""
folder = Folder(a_folder)
node = self.node_from_path(folder)
if node:
logger.info("Node exists at [%s]" % node.relative_path)
return node

if not folder.is_descendant_of(self.source_folder):
raise HydeException("The given folder [%s] does not belong to this hierarchy [%s]" %
(folder, self.source_folder))

p_folder = folder
parent = None
hierarchy = []
while not parent:
hierarchy.append(p_folder)
p_folder = p_folder.parent
parent = self.node_from_path(p_folder)

hierarchy.reverse()
node = parent if parent else self
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))

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

afile = File(a_file)

resource = self.resource_from_path(afile)
if resource:
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]" %
(afile, self.content_folder))

node = self.node_from_path(afile.parent)

if not node:
node = self.add_node(afile.parent)

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))
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.
"""

if not self.source_folder.exists:
raise HydeException("The given source folder[%s] does not exist" % self.source_folder)

with self.source_folder.walk() as walker:

@walker.folder_visitor
def visit_folder(folder):
self.add_node(folder)

@walker.file_visitor
def visit_file(afile):
self.add_resource(afile)

class Site(object):
"""
Represents the site to be generated
"""

def __init__(self, site_path):
super(Site, self).__init__()
self.site_path = Folder(str(site_path))

# TODO: Get the value from config
content_folder = self.site_path.child_folder('content')
self.content = RootNode(content_folder, self)
self.node_map = {}
self.resource_map = {}

def build(self):
"""
Walks the content and media folders to build up the sitemap.
"""

self.content.build()


+ 7
- 0
hyde/tests/sites/test_jinja/content/about.html View File

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

+ 9
- 0
hyde/tests/sites/test_jinja/content/blog/2010/december/merry-christmas.html View File

@@ -0,0 +1,9 @@
{% extends "blog/post.html" %}

{% block article %}
{% lipsum n=10 %}
{% endblock %}

{% block aside %}
{% lipsum n=2 %}
{% endblock %}

+ 4
- 0
hyde/tests/sites/test_jinja/info.yaml View File

@@ -0,0 +1,4 @@
author: Lakshmi Vyasarajan
description: A test layout for hyde.
template: jinja2 (2.6)
version: 0.1

+ 56
- 0
hyde/tests/sites/test_jinja/layout/base.html View File

@@ -0,0 +1,56 @@
{% extends "root.html" %}
{% block all %}
<!doctype html>
<html lang="en">
<head>
{% block starthead %}{% endblock starthead %}
<meta charset="{{page.meta.charset|default('utf-8')}}">
<meta http-equiv="X-UA-Compatible" content="{{page.meta.compatibility|default('IE=edge,chrome=1')">

<title>{% block title %}{{page.meta.title}}{% endblock %}</title>
<meta name="description" content="{{page.meta.description}}">
<meta name="author" content="{{page.meta.author}}">

<!-- Mobile viewport optimized: j.mp/bplateviewport -->
<meta name="viewport" content="{{page.meta.viewport|default('width=device-width, initial-scale=1.0')">

{% block favicons %}
<!-- Place favicon.ico & apple-touch-icon.png in the root of your domain and delete these references -->
<link rel="shortcut icon" href="{% media '/favicon.ico' %}">
<link rel="apple-touch-icon" href="{% media '/apple-touch-icon.png' %}">
{% endblock favicons %}

{% block css %}
<link rel="stylesheet" href="{% media 'css/site.css' %}">
{% endblock css %}
{% block endhead %}{% endblock endhead %}
</head>
<body id="{{page.id if page.id else page.name_without_extension}}">
{% block content %}
<div id="container">
{% block container %}
<header>
{% block header %}{% endblock header %}
</header>
<div id="main" role="main">
{% block main %}{% endblock main %}
</div>
<footer>
{% block footer %}{% endblock %}
</footer>
{% endblock container%}
</div> <!--! end of #container -->
{% endblock content%}

{% block js %}
<!-- Javascript at the bottom for fast page loading -->
{% block jquery %}
<!-- Grab Google CDN's jQuery. fall back to local if necessary -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.js"></script>
{% endblock jquery %}

{% block scripts %}

</body>
</html>
{% endblock all %}

+ 10
- 0
hyde/tests/sites/test_jinja/layout/blog/post.html View File

@@ -0,0 +1,10 @@
{% extends "base.html" %}

{% block main %}
<article>
{% block article %}{% endblock %}
</article>
<aside>
{% block aside %}{% endblock %}
</aside>
{% endblock %}

+ 1
- 0
hyde/tests/sites/test_jinja/layout/root.html View File

@@ -0,0 +1 @@
{% block all %}{% endblock %}

+ 4
- 0
hyde/tests/sites/test_jinja/media/css/site.css View File

@@ -0,0 +1,4 @@
body{
margin: 0 auto;
width: 960px;
}

+ 9
- 0
hyde/tests/sites/test_jinja/site.yaml View File

@@ -0,0 +1,9 @@
site:
mode: development
media:
root:
path: media # Relative path from site root (the directory where this file exists)
url: /media
widgets:
plugins:
aggregators:

+ 21
- 4
hyde/tests/test_fs.py View File

@@ -22,6 +22,16 @@ def test_name():
f = FS(__file__)
assert f.name == os.path.basename(__file__)

def test_equals():
f = FS('/blog/2010/december')
g = FS('/blog/2010/december')
h = FS('/blog/2010/december/')
i = FS('/blog/2010/november')
assert f == f.path
assert f == g
assert f == h
assert f != i

def test_name_without_extension():
f = File(__file__)
assert f.name_without_extension == "test_fs"
@@ -108,16 +118,23 @@ def test_ancestors_stop():
depth = 0
next = JINJA2
for folder in INDEX.ancestors(stop=TEMPLATE_ROOT.parent):
print folder
assert folder == next
depth += 1
next = folder.parent
assert depth == 2

def test_is_descendant_of():
assert INDEX.is_descendant_of(JINJA2)
print "*"
assert JINJA2.is_descendant_of(TEMPLATE_ROOT)
print "*"
assert INDEX.is_descendant_of(TEMPLATE_ROOT)
print "*"
assert not INDEX.is_descendant_of(DATA_ROOT)

def test_fragment():
print INDEX.get_fragment(TEMPLATE_ROOT)
assert INDEX.get_fragment(TEMPLATE_ROOT) == Folder(JINJA2.name).child(INDEX.name)
assert INDEX.get_fragment(TEMPLATE_ROOT.parent) == Folder(
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)

def test_get_mirror():


+ 62
- 0
hyde/tests/test_site.py View File

@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
"""
Use nose
`$ pip install nose`
`$ nosetests`
"""
from hyde.fs import File, Folder
from hyde.site import Node, RootNode, Site


TEST_SITE_ROOT = File(__file__).parent.child_folder('sites/test_jinja')

def test_node_site():
s = Site(TEST_SITE_ROOT)
r = RootNode(TEST_SITE_ROOT.child_folder('content'), s)
assert r.site == s
n = Node(r.source_folder.child_folder('blog'), r)
assert n.site == s

def test_node_root():
s = Site(TEST_SITE_ROOT)
r = RootNode(TEST_SITE_ROOT.child_folder('content'), s)
assert r.root == r
n = Node(r.source_folder.child_folder('blog'), r)
assert n.root == r

def test_node_parent():
s = Site(TEST_SITE_ROOT)
r = RootNode(TEST_SITE_ROOT.child_folder('content'), s)
c = r.add_node(TEST_SITE_ROOT.child_folder('content/blog/2010/december'))
assert c.parent == r.node_from_relative_path('blog/2010')

def test_node_module():
s = Site(TEST_SITE_ROOT)
r = RootNode(TEST_SITE_ROOT.child_folder('content'), s)
assert not r.module
n = r.add_node(TEST_SITE_ROOT.child_folder('content/blog'))
assert n.module == n
c = r.add_node(TEST_SITE_ROOT.child_folder('content/blog/2010/december'))
assert c.module == n

def test_node_relative_path():
s = Site(TEST_SITE_ROOT)
r = RootNode(TEST_SITE_ROOT.child_folder('content'), s)
assert not r.module
n = r.add_node(TEST_SITE_ROOT.child_folder('content/blog'))
assert n.relative_path == 'blog'
c = r.add_node(TEST_SITE_ROOT.child_folder('content/blog/2010/december'))
assert c.relative_path == 'blog/2010/december'

def test_build():
s = Site(TEST_SITE_ROOT)
s.build()
path = 'blog/2010/december'
node = s.content.node_from_relative_path(path)
assert node
assert Folder(node.relative_path) == Folder(path)
path += '/merry-christmas.html'
resource = s.content.resource_from_relative_path(path)
assert resource
assert resource.relative_path == path
assert not s.content.resource_from_relative_path('/happy-festivus.html')

+ 6
- 1
hyde/tests/util.py View File

@@ -1,4 +1,9 @@
from django.utils.html import strip_spaces_between_tags
def strip_spaces_between_tags(value):
"""
Stolen from `django.util.html`
Returns the given HTML with spaces between tags removed.
"""
return re.sub(r'>\s+<', '><', force_unicode(value))

def assert_html_equals(expected, actual, sanitize=None):
expected = strip_spaces_between_tags(expected.strip())


Loading…
Cancel
Save