@@ -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() | ||||