@@ -22,27 +22,28 @@ logger.setLevel(logging.DEBUG) | |||
import sys | |||
logger.addHandler(logging.StreamHandler(sys.stdout)) | |||
class Engine(Application): | |||
""" | |||
The Hyde Application | |||
""" | |||
@command(description='hyde - a python static website generator', | |||
epilog='Use %(prog)s {command} -h to get help on individual commands') | |||
epilog='Use %(prog)s {command} -h to get help on individual commands') | |||
@version('-v', '--version', version='%(prog)s ' + __version__) | |||
@store('-s', '--sitepath', default='.', help="Location of the hyde site") | |||
def main(self, args): | |||
""" | |||
Will not be executed. A sub command is required. This function exists to provide | |||
common parameters for the subcommands and some generic stuff like version and | |||
metadata | |||
Will not be executed. A sub command is required. This function exists | |||
to provide common parameters for the subcommands and some generic stuff | |||
like version and metadata | |||
""" | |||
pass | |||
@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') | |||
help='Overwrite the current site if it exists') | |||
def create(self, args): | |||
""" | |||
The create command. Creates a new site from the template at the given | |||
@@ -50,24 +51,29 @@ class Engine(Application): | |||
""" | |||
sitepath = Folder(args.sitepath) | |||
if sitepath.exists and not args.overwrite: | |||
raise HydeException("The given site path[%s] is not empty" % sitepath) | |||
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)) | |||
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) | |||
"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', dest='config', | |||
help='The configuration used to generate the site') | |||
@store('-d', '--deploy-path', default='deploy', help='Where should the site be generated?') | |||
@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. | |||
The generate command. Generates the site at the given | |||
deployment directory. | |||
""" | |||
sitepath = Folder(args.sitepath) | |||
config_file = sitepath.child(args.config) | |||
@@ -79,4 +85,4 @@ class Engine(Application): | |||
from hyde.generator import Generator | |||
gen = Generator(site) | |||
gen.generate_all() | |||
gen.generate_all() |
@@ -16,16 +16,19 @@ class LoyalUndefined(Undefined): | |||
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): | |||
""" | |||
@@ -22,6 +22,7 @@ logger.addHandler(NullHandler()) | |||
__all__ = ['File', 'Folder'] | |||
class FS(object): | |||
""" | |||
The base file system object | |||
@@ -70,7 +71,6 @@ class FS(object): | |||
""" | |||
return len(self.path.rstrip(os.sep).split(os.sep)) | |||
def ancestors(self, stop=None): | |||
""" | |||
Generates the parents until stop or the absolute | |||
@@ -101,17 +101,22 @@ class FS(object): | |||
""" | |||
if self == root: | |||
return '' | |||
return functools.reduce(lambda f, p: Folder(p.name).child(f), self.ancestors(stop=root), self.name) | |||
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): | |||
""" | |||
Returns a File or Folder object that reperesents if the entire fragment of this | |||
directory starting with `source_root` were copied to `target_root`. | |||
Returns a File or Folder object that reperesents if the entire | |||
fragment of this directory starting with `source_root` were copied | |||
to `target_root`. | |||
>>> Folder('/usr/local/hyde/stuff').get_mirror('/usr/tmp', source_root='/usr/local/hyde') | |||
>>> Folder('/usr/local/hyde/stuff').get_mirror('/usr/tmp', | |||
source_root='/usr/local/hyde') | |||
Folder('/usr/tmp/stuff') | |||
""" | |||
fragment = self.get_relative_path(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 | |||
@@ -132,6 +137,7 @@ class FS(object): | |||
else: | |||
return FS.file_or_folder(Folder(destination).child(self.name)) | |||
class File(FS): | |||
""" | |||
The File object. | |||
@@ -201,6 +207,7 @@ class File(FS): | |||
shutil.copy(self.path, str(destination)) | |||
return target | |||
class FSVisitor(object): | |||
""" | |||
Implements syntactic sugar for walking and listing folders | |||
@@ -235,7 +242,8 @@ class FSVisitor(object): | |||
def __enter__(self): | |||
return self | |||
def __exit__(self, exc_type, exc_val, exc_tb): pass | |||
def __exit__(self, exc_type, exc_val, exc_tb): | |||
pass | |||
class FolderWalker(FSVisitor): | |||
@@ -252,15 +260,18 @@ class FolderWalker(FSVisitor): | |||
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)) | |||
if not walk_files and not walk_folders: | |||
return | |||
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): | |||
""" | |||
@@ -295,7 +306,7 @@ class FolderWalker(FSVisitor): | |||
def __visit_folder__(folder): | |||
process_folder = True | |||
if hasattr(self,'visit_folder'): | |||
if hasattr(self, 'visit_folder'): | |||
process_folder = self.visit_folder(folder) | |||
# If there is no return value assume true | |||
# | |||
@@ -304,11 +315,11 @@ class FolderWalker(FSVisitor): | |||
return process_folder | |||
def __visit_file__(a_file): | |||
if hasattr(self,'visit_file'): | |||
if hasattr(self, 'visit_file'): | |||
self.visit_file(a_file) | |||
def __visit_complete__(): | |||
if hasattr(self,'visit_complete'): | |||
if hasattr(self, 'visit_complete'): | |||
self.visit_complete() | |||
for root, dirs, a_files in os.walk(self.folder.path): | |||
@@ -331,43 +342,44 @@ class FolderLister(FSVisitor): | |||
""" | |||
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) | |||
""" | |||
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. | |||
""" | |||
""" | |||
Yield both Files and Folders as the folder is listed. | |||
""" | |||
return self.list(list_folders=True, list_files=True) | |||
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) | |||
""" | |||
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) | |||
""" | |||
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. | |||
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 | |||
@@ -382,9 +394,10 @@ class FolderLister(FSVisitor): | |||
elif hasattr(self, 'visit_file'): | |||
if not self.pattern or fnmatch.fnmatch(a_file, self.pattern): | |||
self.visit_file(File(path)) | |||
if hasattr(self,'visit_complete'): | |||
if hasattr(self, 'visit_complete'): | |||
self.visit_complete() | |||
class Folder(FS): | |||
""" | |||
Represents a directory. | |||
@@ -497,4 +510,4 @@ class Folder(FS): | |||
Return a `FolderLister` object | |||
""" | |||
return FolderLister(self, pattern) | |||
return FolderLister(self, pattern) |
@@ -50,7 +50,6 @@ class Generator(object): | |||
logger.info("Configuring the template environment") | |||
self.template.configure(self.site.config) | |||
def rebuild_if_needed(self): | |||
""" | |||
Checks if the site requries a rebuild and builds if | |||
@@ -1 +1 @@ | |||
# -*- coding: utf-8 -*- | |||
# -*- coding: utf-8 -*- |
@@ -9,6 +9,7 @@ from hyde.fs import File, Folder | |||
HYDE_DATA = "HYDE_DATA" | |||
LAYOUTS = "layouts" | |||
class Layout(object): | |||
""" | |||
Represents a layout package | |||
@@ -24,9 +25,11 @@ class Layout(object): | |||
""" | |||
layout_folder = None | |||
if HYDE_DATA in os.environ: | |||
layout_folder = Layout._get_layout_folder(os.environ[HYDE_DATA], layout_name) | |||
layout_folder = Layout._get_layout_folder( | |||
os.environ[HYDE_DATA], layout_name) | |||
if not layout_folder: | |||
layout_folder = Layout._get_layout_folder(File(__file__).parent, layout_name) | |||
layout_folder = Layout._get_layout_folder( | |||
File(__file__).parent, layout_name) | |||
return layout_folder | |||
@staticmethod | |||
@@ -35,6 +38,6 @@ class Layout(object): | |||
Finds the layout folder from the given root folder. | |||
If it does not exist, return None | |||
""" | |||
layout_folder = Folder(str(root)).child_folder(LAYOUTS).child_folder(layout_name) | |||
layouts_folder = Folder(str(root)).child_folder(LAYOUTS) | |||
layout_folder = layouts_folder.child_folder(layout_name) | |||
return layout_folder if layout_folder.exists else None | |||
@@ -1,9 +1,12 @@ | |||
""" | |||
Contains data structures and utilities for hyde. | |||
""" | |||
class Expando(object): | |||
""" | |||
A generic expando class that creates attributes from the passed in dictionary. | |||
A generic expando class that creates attributes from | |||
the passed in dictionary. | |||
""" | |||
def __init__(self, d): | |||
@@ -21,12 +24,14 @@ class Expando(object): | |||
if isinstance(primitive, dict): | |||
return Expando(primitive) | |||
elif isinstance(primitive, (tuple, list, set, frozenset)): | |||
return type(primitive)(Expando.transform(attr) for attr in primitive) | |||
seq = type(primitive) | |||
return seq(Expando.transform(attr) for attr in primitive) | |||
else: | |||
return primitive | |||
from hyde.fs import File, Folder | |||
class Config(Expando): | |||
""" | |||
Represents the hyde configuration file | |||
@@ -34,12 +39,12 @@ class Config(Expando): | |||
def __init__(self, sitepath, config_dict=None): | |||
default_config = dict( | |||
content_root = 'content', | |||
deploy_root = 'deploy', | |||
media_root = 'media', | |||
layout_root = 'layout', | |||
media_url = '/media', | |||
site_url = '/' | |||
content_root='content', | |||
deploy_root='deploy', | |||
media_root='media', | |||
layout_root='layout', | |||
media_url='/media', | |||
site_url='/' | |||
) | |||
conf = dict(**default_config) | |||
if config_dict: | |||
@@ -47,7 +52,6 @@ class Config(Expando): | |||
super(Config, self).__init__(conf) | |||
self.sitepath = Folder(sitepath) | |||
@property | |||
def deploy_root_path(self): | |||
""" | |||
@@ -2,4 +2,4 @@ | |||
# 1. start_node | |||
# 2. start_resource | |||
# 3. end_resource | |||
# 4. end_node | |||
# 4. end_node |
@@ -9,9 +9,10 @@ from hyde.model import Config | |||
import logging | |||
from logging import NullHandler | |||
logger = logging.getLogger('hyde.site') | |||
logger = logging.getLogger('hyde.engine') | |||
logger.addHandler(NullHandler()) | |||
class Processable(object): | |||
""" | |||
A node or resource. | |||
@@ -38,6 +39,7 @@ class Processable(object): | |||
""" | |||
return self.source.path | |||
class Resource(Processable): | |||
""" | |||
Represents any file that is processed by hyde | |||
@@ -110,6 +112,10 @@ class Node(Processable): | |||
return resource | |||
def walk(self): | |||
""" | |||
Walks the node, first yielding itself then | |||
yielding the child nodes depth-first. | |||
""" | |||
yield self | |||
for child in self.child_nodes: | |||
for node in child.walk(): | |||
@@ -224,7 +230,7 @@ class RootNode(Node): | |||
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)) | |||
(afile, self.source_folder)) | |||
node = self.node_from_path(afile.parent) | |||
@@ -275,4 +281,4 @@ class Site(object): | |||
Walks the content and media folders to build up the sitemap. | |||
""" | |||
self.content.build() | |||
self.content.build() |
@@ -3,27 +3,30 @@ | |||
""" | |||
Abstract classes and utilities for template engines | |||
""" | |||
class Template(object): | |||
""" | |||
Interface for hyde template engines. To use a different template engine, | |||
the following interface must be implemented. | |||
""" | |||
def __init__(self, sitepath): | |||
self.sitepath = sitepath | |||
def configure(self, config): | |||
""" | |||
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 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. | |||
""" | |||
abstract | |||
def render(self, text, context): | |||
""" | |||
Given the text, and the context, this function | |||
must return the rendered string. | |||
Given the text, and the context, this function must return the | |||
rendered string. | |||
""" | |||
abstract | |||
@@ -36,4 +39,4 @@ class Template(object): | |||
# TODO: Find the appropriate template environment | |||
from hyde.ext.templates.jinja import Jinja2Template | |||
template = Jinja2Template(site.sitepath) | |||
return template | |||
return template |
@@ -3,4 +3,4 @@ | |||
Handles hyde version | |||
TODO: Use fabric like versioning scheme | |||
""" | |||
__version__ = '0.6.0' | |||
__version__ = '0.6.0' |
@@ -1 +1 @@ | |||
# -*- coding: utf-8 -*- | |||
# -*- coding: utf-8 -*- |