| @@ -7,7 +7,12 @@ from hyde.fs import File, Folder | |||
| import re | |||
| import subprocess | |||
| import traceback | |||
| import logging | |||
| from logging import NullHandler | |||
| logger = logging.getLogger('hyde.engine') | |||
| logger.addHandler(NullHandler()) | |||
| class LessCSSPlugin(Plugin): | |||
| """ | |||
| @@ -71,8 +76,10 @@ class LessCSSPlugin(Plugin): | |||
| source = File.make_temp(text) | |||
| target = File.make_temp('') | |||
| try: | |||
| subprocess.check_call([str(less), str(source), str(target)]) | |||
| except subprocess.CalledProcessError: | |||
| subprocess.check_output([str(less), str(source), str(target)]) | |||
| except subprocess.CalledProcessError, error: | |||
| logger.error(traceback.format_exc()) | |||
| logger.error(error.output) | |||
| raise self.template.exception_class( | |||
| "Cannot process less css. Error occurred when " | |||
| "processing [%s]" % resource.source_file) | |||
| @@ -54,35 +54,33 @@ class MetaPlugin(Plugin): | |||
| super(MetaPlugin, self).__init__(site) | |||
| def begin_site(self): | |||
| metadata = self.site.config.meta if hasattr(self.site.config, 'meta') else {} | |||
| self.site.meta = Metadata(metadata) | |||
| def begin_node(self, node): | |||
| """ | |||
| Look for nodemeta.yaml. Load and assign it to the node. | |||
| Initialize site meta data. | |||
| Go through all the nodes and resources to initialize | |||
| meta data at each level. | |||
| """ | |||
| nodemeta = node.get_resource('nodemeta.yaml') | |||
| parent_meta = node.parent.meta if node.parent else self.site.meta | |||
| if nodemeta: | |||
| nodemeta.is_processable = False | |||
| metadata = nodemeta.source_file.read_all() | |||
| if hasattr(node, 'meta') and node.meta: | |||
| node.meta.update(metadata) | |||
| else: | |||
| node.meta = Metadata(metadata, parent=parent_meta) | |||
| else: | |||
| node.meta = Metadata({}, parent=parent_meta) | |||
| config = self.site.config | |||
| metadata = config.meta if hasattr(config, 'meta') else {} | |||
| self.site.meta = Metadata(metadata) | |||
| for node in self.site.content.walk(): | |||
| self.__read_node__(node) | |||
| for resource in node.resources: | |||
| if not hasattr(resource, 'meta'): | |||
| resource.meta = Metadata({}, node.meta) | |||
| if resource.source_file.is_text: | |||
| self.__read_resource__(resource, resource.source_file.read_all()) | |||
| def begin_text_resource(self, resource, text): | |||
| def __read_resource__(self, resource, text): | |||
| """ | |||
| Load meta data by looking for the marker. | |||
| Reads the resource metadata and assigns it to | |||
| the resource. Load meta data by looking for the marker. | |||
| Once loaded, remove the meta area from the text. | |||
| """ | |||
| logger.info("Trying to load metadata from resource [%s]" % resource) | |||
| yaml_finder = re.compile( | |||
| r"^\s*(?:---|===)\s*\n((?:.|\n)+?)\n\s*(?:---|===)\s*\n", | |||
| re.MULTILINE) | |||
| r"^\s*(?:---|===)\s*\n((?:.|\n)+?)\n\s*(?:---|===)\s*\n", | |||
| re.MULTILINE) | |||
| match = re.match(yaml_finder, text) | |||
| if not match: | |||
| logger.info("No metadata found in resource [%s]" % resource) | |||
| @@ -98,3 +96,32 @@ class MetaPlugin(Plugin): | |||
| logger.info("Successfully loaded metadata from resource [%s]" | |||
| % resource) | |||
| return text | |||
| def __read_node__(self, node): | |||
| """ | |||
| Look for nodemeta.yaml. Load and assign it to the node. | |||
| """ | |||
| nodemeta = node.get_resource('nodemeta.yaml') | |||
| parent_meta = node.parent.meta if node.parent else self.site.meta | |||
| if nodemeta: | |||
| nodemeta.is_processable = False | |||
| metadata = nodemeta.source_file.read_all() | |||
| if hasattr(node, 'meta') and node.meta: | |||
| node.meta.update(metadata) | |||
| else: | |||
| node.meta = Metadata(metadata, parent=parent_meta) | |||
| else: | |||
| node.meta = Metadata({}, parent=parent_meta) | |||
| def begin_node(self, node): | |||
| """ | |||
| Look for nodemeta.yaml. Load and assign it to the node. | |||
| """ | |||
| self.__read_node__(node) | |||
| def begin_text_resource(self, resource, text): | |||
| """ | |||
| Update the meta data again, just in case it | |||
| has changed. Return text without meta data. | |||
| """ | |||
| return self.__read_resource__(resource, text) | |||
| @@ -79,7 +79,11 @@ class Jinja2Template(Template): | |||
| loader = FileSystemLoader(str(self.sitepath)) | |||
| self.env = Environment(loader=loader, | |||
| undefined=SilentUndefined, | |||
| extensions=[Markdown]) | |||
| trim_blocks=True, | |||
| extensions=[Markdown, | |||
| 'jinja2.ext.do', | |||
| 'jinja2.ext.loopcontrols', | |||
| 'jinja2.ext.with_']) | |||
| self.env.globals['media_url'] = media_url | |||
| self.env.globals['content_url'] = content_url | |||
| self.env.extend(config=config) | |||
| @@ -30,7 +30,10 @@ class FS(object): | |||
| def __init__(self, path): | |||
| super(FS, self).__init__() | |||
| self.path = os.path.expandvars(os.path.expanduser( | |||
| if path == os.sep: | |||
| self.path = path | |||
| else: | |||
| self.path = os.path.expandvars(os.path.expanduser( | |||
| str(path).strip().rstrip(os.sep))) | |||
| def __str__(self): | |||
| @@ -169,6 +169,35 @@ class Node(Processable): | |||
| for resource in node.resources: | |||
| yield resource | |||
| def walk_resources_sorted(self, attr='name', reverse=False, default=None): | |||
| """ | |||
| Walks the resources in this hierarchy sorted by | |||
| the given key. | |||
| """ | |||
| from operator import attrgetter | |||
| def safe_attrgetter(*items): | |||
| f = attrgetter(*items) | |||
| def wrapper(obj): | |||
| res = None | |||
| try: | |||
| res = f(obj) | |||
| except: | |||
| logger.error("Cannot get the requested items[%s]" | |||
| " from the object [%s]" % | |||
| (items, obj)) | |||
| res = default | |||
| return res | |||
| return wrapper | |||
| sorted_resources = sorted(self.walk_resources(), | |||
| key=safe_attrgetter(attr), | |||
| reverse=reverse) | |||
| for resource in sorted_resources: | |||
| yield resource | |||
| @property | |||
| def relative_path(self): | |||
| """ | |||
| @@ -103,6 +103,44 @@ def test_walk_resources(): | |||
| expected.sort() | |||
| assert pages == expected | |||
| def test_walk_resources_sorted(): | |||
| s = Site(TEST_SITE_ROOT) | |||
| s.load() | |||
| pages = [page.name for page in s.content.walk_resources_sorted(attr='name')] | |||
| expected = ["404.html", | |||
| "about.html", | |||
| "apple-touch-icon.png", | |||
| "merry-christmas.html", | |||
| "crossdomain.xml", | |||
| "favicon.ico", | |||
| "robots.txt", | |||
| "site.css" | |||
| ] | |||
| assert pages == sorted(expected) | |||
| pages = [page.name for page in | |||
| s.content.walk_resources_sorted(attr='name', reverse=True)] | |||
| assert pages == sorted(expected, reverse=True) | |||
| pages = [page.name for page in | |||
| s.content.walk_resources_sorted(attr='source_file.kind')] | |||
| assert pages == sorted(expected, key=lambda f: File(f).kind) | |||
| from datetime import datetime, timedelta | |||
| d = {} | |||
| t = datetime.now() | |||
| for name in expected: | |||
| d[name] = t | |||
| t = t - timedelta(days=1) | |||
| for page in s.content.walk_resources(): | |||
| page.meta = Expando(dict(time=d[page.name])) | |||
| pages = [page.name for page in | |||
| s.content.walk_resources_sorted(attr='meta.time', reverse=True)] | |||
| assert pages == expected | |||
| def test_contains_resource(): | |||
| s = Site(TEST_SITE_ROOT) | |||
| s.load() | |||