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