| @@ -7,7 +7,12 @@ from hyde.fs import File, Folder | |||||
| import re | import re | ||||
| import subprocess | import subprocess | ||||
| import traceback | |||||
| import logging | |||||
| from logging import NullHandler | |||||
| logger = logging.getLogger('hyde.engine') | |||||
| logger.addHandler(NullHandler()) | |||||
| class LessCSSPlugin(Plugin): | class LessCSSPlugin(Plugin): | ||||
| """ | """ | ||||
| @@ -71,8 +76,10 @@ class LessCSSPlugin(Plugin): | |||||
| source = File.make_temp(text) | source = File.make_temp(text) | ||||
| target = File.make_temp('') | target = File.make_temp('') | ||||
| try: | 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( | raise self.template.exception_class( | ||||
| "Cannot process less css. Error occurred when " | "Cannot process less css. Error occurred when " | ||||
| "processing [%s]" % resource.source_file) | "processing [%s]" % resource.source_file) | ||||
| @@ -54,35 +54,33 @@ class MetaPlugin(Plugin): | |||||
| super(MetaPlugin, self).__init__(site) | super(MetaPlugin, self).__init__(site) | ||||
| def begin_site(self): | 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. | Once loaded, remove the meta area from the text. | ||||
| """ | """ | ||||
| logger.info("Trying to load metadata from resource [%s]" % resource) | logger.info("Trying to load metadata from resource [%s]" % resource) | ||||
| yaml_finder = re.compile( | 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) | match = re.match(yaml_finder, text) | ||||
| if not match: | if not match: | ||||
| logger.info("No metadata found in resource [%s]" % resource) | logger.info("No metadata found in resource [%s]" % resource) | ||||
| @@ -98,3 +96,32 @@ class MetaPlugin(Plugin): | |||||
| logger.info("Successfully loaded metadata from resource [%s]" | logger.info("Successfully loaded metadata from resource [%s]" | ||||
| % resource) | % resource) | ||||
| return text | 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)) | loader = FileSystemLoader(str(self.sitepath)) | ||||
| self.env = Environment(loader=loader, | self.env = Environment(loader=loader, | ||||
| undefined=SilentUndefined, | 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['media_url'] = media_url | ||||
| self.env.globals['content_url'] = content_url | self.env.globals['content_url'] = content_url | ||||
| self.env.extend(config=config) | self.env.extend(config=config) | ||||
| @@ -30,7 +30,10 @@ class FS(object): | |||||
| def __init__(self, path): | def __init__(self, path): | ||||
| super(FS, self).__init__() | 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))) | str(path).strip().rstrip(os.sep))) | ||||
| def __str__(self): | def __str__(self): | ||||
| @@ -169,6 +169,35 @@ class Node(Processable): | |||||
| for resource in node.resources: | for resource in node.resources: | ||||
| yield resource | 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 | @property | ||||
| def relative_path(self): | def relative_path(self): | ||||
| """ | """ | ||||
| @@ -103,6 +103,44 @@ def test_walk_resources(): | |||||
| expected.sort() | expected.sort() | ||||
| assert pages == expected | 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(): | def test_contains_resource(): | ||||
| s = Site(TEST_SITE_ROOT) | s = Site(TEST_SITE_ROOT) | ||||
| s.load() | s.load() | ||||