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