@@ -22,27 +22,28 @@ logger.setLevel(logging.DEBUG) | |||||
import sys | import sys | ||||
logger.addHandler(logging.StreamHandler(sys.stdout)) | logger.addHandler(logging.StreamHandler(sys.stdout)) | ||||
class Engine(Application): | class Engine(Application): | ||||
""" | """ | ||||
The Hyde Application | The Hyde Application | ||||
""" | """ | ||||
@command(description='hyde - a python static website generator', | @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__) | @version('-v', '--version', version='%(prog)s ' + __version__) | ||||
@store('-s', '--sitepath', default='.', help="Location of the hyde site") | @store('-s', '--sitepath', default='.', help="Location of the hyde site") | ||||
def main(self, args): | 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 | pass | ||||
@subcommand('create', 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') | @store('-l', '--layout', default='basic', help='Layout for the new site') | ||||
@true('-f', '--force', default=False, dest='overwrite', | @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): | def create(self, args): | ||||
""" | """ | ||||
The create command. Creates a new site from the template at the given | The create command. Creates a new site from the template at the given | ||||
@@ -50,24 +51,29 @@ class Engine(Application): | |||||
""" | """ | ||||
sitepath = Folder(args.sitepath) | sitepath = Folder(args.sitepath) | ||||
if sitepath.exists and not args.overwrite: | 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) | 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: | if not layout or not layout.exists: | ||||
raise HydeException( | 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) | layout.copy_contents_to(args.sitepath) | ||||
logger.info("Site creation complete") | logger.info("Site creation complete") | ||||
@subcommand('gen', help='Generate the site') | @subcommand('gen', help='Generate the site') | ||||
@store('-c', '--config-path', default='site.yaml', dest='config', | @store('-c', '--config-path', default='site.yaml', dest='config', | ||||
help='The configuration used to generate the site') | 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): | 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) | sitepath = Folder(args.sitepath) | ||||
config_file = sitepath.child(args.config) | config_file = sitepath.child(args.config) | ||||
@@ -79,4 +85,4 @@ class Engine(Application): | |||||
from hyde.generator import Generator | from hyde.generator import Generator | ||||
gen = Generator(site) | gen = Generator(site) | ||||
gen.generate_all() | |||||
gen.generate_all() |
@@ -16,16 +16,19 @@ class LoyalUndefined(Undefined): | |||||
def __call__(self, *args, **kwargs): | def __call__(self, *args, **kwargs): | ||||
return self | return self | ||||
@contextfunction | @contextfunction | ||||
def media_url(context, path): | def media_url(context, path): | ||||
site = context['site'] | site = context['site'] | ||||
return Folder(site.config.media_url).child(path) | return Folder(site.config.media_url).child(path) | ||||
@contextfunction | @contextfunction | ||||
def content_url(context, path): | def content_url(context, path): | ||||
site = context['site'] | site = context['site'] | ||||
return Folder(site.config.base_url).child(path) | return Folder(site.config.base_url).child(path) | ||||
# pylint: disable-msg=W0104,E0602,W0613,R0201 | # pylint: disable-msg=W0104,E0602,W0613,R0201 | ||||
class Jinja2Template(Template): | class Jinja2Template(Template): | ||||
""" | """ | ||||
@@ -22,6 +22,7 @@ logger.addHandler(NullHandler()) | |||||
__all__ = ['File', 'Folder'] | __all__ = ['File', 'Folder'] | ||||
class FS(object): | class FS(object): | ||||
""" | """ | ||||
The base file system object | The base file system object | ||||
@@ -70,7 +71,6 @@ class FS(object): | |||||
""" | """ | ||||
return len(self.path.rstrip(os.sep).split(os.sep)) | return len(self.path.rstrip(os.sep).split(os.sep)) | ||||
def ancestors(self, stop=None): | def ancestors(self, stop=None): | ||||
""" | """ | ||||
Generates the parents until stop or the absolute | Generates the parents until stop or the absolute | ||||
@@ -101,17 +101,22 @@ class FS(object): | |||||
""" | """ | ||||
if self == root: | if self == root: | ||||
return '' | 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): | 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') | 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) | return Folder(target_root).child(fragment) | ||||
@staticmethod | @staticmethod | ||||
@@ -132,6 +137,7 @@ class FS(object): | |||||
else: | else: | ||||
return FS.file_or_folder(Folder(destination).child(self.name)) | return FS.file_or_folder(Folder(destination).child(self.name)) | ||||
class File(FS): | class File(FS): | ||||
""" | """ | ||||
The File object. | The File object. | ||||
@@ -201,6 +207,7 @@ class File(FS): | |||||
shutil.copy(self.path, str(destination)) | shutil.copy(self.path, str(destination)) | ||||
return target | return target | ||||
class FSVisitor(object): | class FSVisitor(object): | ||||
""" | """ | ||||
Implements syntactic sugar for walking and listing folders | Implements syntactic sugar for walking and listing folders | ||||
@@ -235,7 +242,8 @@ class FSVisitor(object): | |||||
def __enter__(self): | def __enter__(self): | ||||
return 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): | class FolderWalker(FSVisitor): | ||||
@@ -252,15 +260,18 @@ class FolderWalker(FSVisitor): | |||||
the arguments. | 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): | def walk_all(self): | ||||
""" | """ | ||||
@@ -295,7 +306,7 @@ class FolderWalker(FSVisitor): | |||||
def __visit_folder__(folder): | def __visit_folder__(folder): | ||||
process_folder = True | process_folder = True | ||||
if hasattr(self,'visit_folder'): | |||||
if hasattr(self, 'visit_folder'): | |||||
process_folder = self.visit_folder(folder) | process_folder = self.visit_folder(folder) | ||||
# If there is no return value assume true | # If there is no return value assume true | ||||
# | # | ||||
@@ -304,11 +315,11 @@ class FolderWalker(FSVisitor): | |||||
return process_folder | return process_folder | ||||
def __visit_file__(a_file): | def __visit_file__(a_file): | ||||
if hasattr(self,'visit_file'): | |||||
if hasattr(self, 'visit_file'): | |||||
self.visit_file(a_file) | self.visit_file(a_file) | ||||
def __visit_complete__(): | def __visit_complete__(): | ||||
if hasattr(self,'visit_complete'): | |||||
if hasattr(self, 'visit_complete'): | |||||
self.visit_complete() | self.visit_complete() | ||||
for root, dirs, a_files in os.walk(self.folder.path): | 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): | 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): | 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): | 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): | 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): | 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 | Calls self.visit_folder first and then calls self.visit_file for | ||||
any files found. After all files and folders have been exhausted | any files found. After all files and folders have been exhausted | ||||
@@ -382,9 +394,10 @@ class FolderLister(FSVisitor): | |||||
elif hasattr(self, 'visit_file'): | elif hasattr(self, 'visit_file'): | ||||
if not self.pattern or fnmatch.fnmatch(a_file, self.pattern): | if not self.pattern or fnmatch.fnmatch(a_file, self.pattern): | ||||
self.visit_file(File(path)) | self.visit_file(File(path)) | ||||
if hasattr(self,'visit_complete'): | |||||
if hasattr(self, 'visit_complete'): | |||||
self.visit_complete() | self.visit_complete() | ||||
class Folder(FS): | class Folder(FS): | ||||
""" | """ | ||||
Represents a directory. | Represents a directory. | ||||
@@ -497,4 +510,4 @@ class Folder(FS): | |||||
Return a `FolderLister` object | 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") | logger.info("Configuring the template environment") | ||||
self.template.configure(self.site.config) | self.template.configure(self.site.config) | ||||
def rebuild_if_needed(self): | def rebuild_if_needed(self): | ||||
""" | """ | ||||
Checks if the site requries a rebuild and builds if | 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" | HYDE_DATA = "HYDE_DATA" | ||||
LAYOUTS = "layouts" | LAYOUTS = "layouts" | ||||
class Layout(object): | class Layout(object): | ||||
""" | """ | ||||
Represents a layout package | Represents a layout package | ||||
@@ -24,9 +25,11 @@ class Layout(object): | |||||
""" | """ | ||||
layout_folder = None | layout_folder = None | ||||
if HYDE_DATA in os.environ: | 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: | 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 | return layout_folder | ||||
@staticmethod | @staticmethod | ||||
@@ -35,6 +38,6 @@ class Layout(object): | |||||
Finds the layout folder from the given root folder. | Finds the layout folder from the given root folder. | ||||
If it does not exist, return None | 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 | return layout_folder if layout_folder.exists else None | ||||
@@ -1,9 +1,12 @@ | |||||
""" | """ | ||||
Contains data structures and utilities for hyde. | Contains data structures and utilities for hyde. | ||||
""" | """ | ||||
class Expando(object): | 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): | def __init__(self, d): | ||||
@@ -21,12 +24,14 @@ class Expando(object): | |||||
if isinstance(primitive, dict): | if isinstance(primitive, dict): | ||||
return Expando(primitive) | return Expando(primitive) | ||||
elif isinstance(primitive, (tuple, list, set, frozenset)): | 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: | else: | ||||
return primitive | return primitive | ||||
from hyde.fs import File, Folder | from hyde.fs import File, Folder | ||||
class Config(Expando): | class Config(Expando): | ||||
""" | """ | ||||
Represents the hyde configuration file | Represents the hyde configuration file | ||||
@@ -34,12 +39,12 @@ class Config(Expando): | |||||
def __init__(self, sitepath, config_dict=None): | def __init__(self, sitepath, config_dict=None): | ||||
default_config = dict( | 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) | conf = dict(**default_config) | ||||
if config_dict: | if config_dict: | ||||
@@ -47,7 +52,6 @@ class Config(Expando): | |||||
super(Config, self).__init__(conf) | super(Config, self).__init__(conf) | ||||
self.sitepath = Folder(sitepath) | self.sitepath = Folder(sitepath) | ||||
@property | @property | ||||
def deploy_root_path(self): | def deploy_root_path(self): | ||||
""" | """ | ||||
@@ -2,4 +2,4 @@ | |||||
# 1. start_node | # 1. start_node | ||||
# 2. start_resource | # 2. start_resource | ||||
# 3. end_resource | # 3. end_resource | ||||
# 4. end_node | |||||
# 4. end_node |
@@ -9,9 +9,10 @@ from hyde.model import Config | |||||
import logging | import logging | ||||
from logging import NullHandler | from logging import NullHandler | ||||
logger = logging.getLogger('hyde.site') | |||||
logger = logging.getLogger('hyde.engine') | |||||
logger.addHandler(NullHandler()) | logger.addHandler(NullHandler()) | ||||
class Processable(object): | class Processable(object): | ||||
""" | """ | ||||
A node or resource. | A node or resource. | ||||
@@ -38,6 +39,7 @@ class Processable(object): | |||||
""" | """ | ||||
return self.source.path | return self.source.path | ||||
class Resource(Processable): | class Resource(Processable): | ||||
""" | """ | ||||
Represents any file that is processed by hyde | Represents any file that is processed by hyde | ||||
@@ -110,6 +112,10 @@ class Node(Processable): | |||||
return resource | return resource | ||||
def walk(self): | def walk(self): | ||||
""" | |||||
Walks the node, first yielding itself then | |||||
yielding the child nodes depth-first. | |||||
""" | |||||
yield self | yield self | ||||
for child in self.child_nodes: | for child in self.child_nodes: | ||||
for node in child.walk(): | for node in child.walk(): | ||||
@@ -224,7 +230,7 @@ class RootNode(Node): | |||||
if not afile.is_descendant_of(self.source_folder): | if not afile.is_descendant_of(self.source_folder): | ||||
raise HydeException("The given file [%s] does not reside" | raise HydeException("The given file [%s] does not reside" | ||||
" in this hierarchy [%s]" % | " in this hierarchy [%s]" % | ||||
(afile, self.content_folder)) | |||||
(afile, self.source_folder)) | |||||
node = self.node_from_path(afile.parent) | 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. | 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 | Abstract classes and utilities for template engines | ||||
""" | """ | ||||
class Template(object): | class Template(object): | ||||
""" | """ | ||||
Interface for hyde template engines. To use a different template engine, | Interface for hyde template engines. To use a different template engine, | ||||
the following interface must be implemented. | the following interface must be implemented. | ||||
""" | """ | ||||
def __init__(self, sitepath): | def __init__(self, sitepath): | ||||
self.sitepath = sitepath | self.sitepath = sitepath | ||||
def configure(self, config): | 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 | abstract | ||||
def render(self, text, context): | 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 | abstract | ||||
@@ -36,4 +39,4 @@ class Template(object): | |||||
# TODO: Find the appropriate template environment | # TODO: Find the appropriate template environment | ||||
from hyde.ext.templates.jinja import Jinja2Template | from hyde.ext.templates.jinja import Jinja2Template | ||||
template = Jinja2Template(site.sitepath) | template = Jinja2Template(site.sitepath) | ||||
return template | |||||
return template |
@@ -3,4 +3,4 @@ | |||||
Handles hyde version | Handles hyde version | ||||
TODO: Use fabric like versioning scheme | TODO: Use fabric like versioning scheme | ||||
""" | """ | ||||
__version__ = '0.6.0' | |||||
__version__ = '0.6.0' |
@@ -1 +1 @@ | |||||
# -*- coding: utf-8 -*- | |||||
# -*- coding: utf-8 -*- |