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