From 713a00d909fef8e0f8944a4339e26ba4f97402a6 Mon Sep 17 00:00:00 2001 From: Lakshmi Vyasarajan Date: Mon, 3 Jan 2011 10:55:06 +0530 Subject: [PATCH] Added hierarchical metadata plugin --- hyde/ext/plugins/meta.py | 51 ++++++++++-- hyde/model.py | 6 ++ hyde/site.py | 18 +++++ hyde/tests/ext/test_meta.py | 157 ++++++++++++++++++++++++++++++++++-- hyde/tests/test_model.py | 12 +++ hyde/tests/test_site.py | 14 ++++ 6 files changed, 245 insertions(+), 13 deletions(-) diff --git a/hyde/ext/plugins/meta.py b/hyde/ext/plugins/meta.py index b4a6627..fe80f35 100644 --- a/hyde/ext/plugins/meta.py +++ b/hyde/ext/plugins/meta.py @@ -16,9 +16,23 @@ class Metadata(Expando): """ Container class for yaml meta data. """ - def __init__(self, text): - super(Metadata, self).__init__(yaml.load(text)) + def __init__(self, data, parent=None): + + super(Metadata, self).__init__({}) + if parent: + self.update(parent.__dict__) + if data: + self.update(data) + + def update(self, data): + """ + Updates the metadata with new stuff + """ + if isinstance(data, dict): + super(Metadata, self).update(data) + else: + super(Metadata, self).update(yaml.load(data)) class MetaPlugin(Plugin): @@ -38,21 +52,46 @@ class MetaPlugin(Plugin): def __init__(self, site): 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. + """ + 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_text_resource(self, resource, text): """ 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) - # re from spjwebster's lanyon - yaml_finder = re.compile( r"^\s*---\s*\n((?:.|\n)+?)\n---\s*\n", re.MULTILINE) + yaml_finder = re.compile( + 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) return text text = text[match.end():] - resource.meta = Metadata(match.group(1)) + data = match.group(1) + if not hasattr(resource, 'meta') or not resource.meta: + resource.meta = Metadata(data, resource.node.meta) + else: + resource.meta.update(data) logger.info("Successfully loaded metadata from resource [%s]" % resource) - return text \ No newline at end of file + return text diff --git a/hyde/model.py b/hyde/model.py index fb664bc..0632df1 100644 --- a/hyde/model.py +++ b/hyde/model.py @@ -11,6 +11,12 @@ class Expando(object): def __init__(self, d): super(Expando, self).__init__() + self.update(d) + + def update(self, d): + """ + Updates the expando with a new dictionary + """ d = d or {} for key, value in d.items(): setattr(self, key, Expando.transform(value)) diff --git a/hyde/site.py b/hyde/site.py index badcae6..26b2465 100644 --- a/hyde/site.py +++ b/hyde/site.py @@ -85,6 +85,24 @@ class Node(Processable): self.child_nodes = [] self.resources = [] + def contains_resource(self, resource_name): + """ + Returns True if the given resource name exists as a file + in this node's source folder. + """ + + return File(self.source_folder.child(resource_name)).exists + + def get_resource(self, resource_name): + """ + Gets the resource if the given resource name exists as a file + in this node's source folder. + """ + + if self.contains_resource(resource_name): + return self.root.resource_from_path(self.source_folder.child(resource_name)) + return None + def add_child_node(self, folder): """ Creates a new child node and adds it to the list of child nodes. diff --git a/hyde/tests/ext/test_meta.py b/hyde/tests/ext/test_meta.py index d6df1d6..791ac27 100644 --- a/hyde/tests/ext/test_meta.py +++ b/hyde/tests/ext/test_meta.py @@ -20,18 +20,16 @@ class TestMeta(object): def setUp(self): TEST_SITE.make() - TEST_SITE.parent.child_folder('sites/test_jinja').copy_contents_to(TEST_SITE) + TEST_SITE.parent.child_folder( + 'sites/test_jinja').copy_contents_to(TEST_SITE) def tearDown(self): TEST_SITE.delete() - def test_can_load_front_matter(self): - d = { - 'title': 'A nice title', + d = {'title': 'A nice title', 'author': 'Lakshmi Vyas', - 'twitter': 'lakshmivyas' - } + 'twitter': 'lakshmivyas'} text = """ --- title: %(title)s @@ -68,4 +66,149 @@ twitter: %(twitter)s text = target.read_all() q = PyQuery(text) for k, v in d.items(): - assert v in q("span." + k).text() \ No newline at end of file + assert v in q("span." + k).text() + + def test_can_load_from_node_meta(self): + d = {'title': 'A nice title', + 'author': 'Lakshmi Vyas', + 'twitter': 'lakshmivyas'} + text = """ +--- +title: Even nicer title +--- +{%% extends "base.html" %%} + +{%% block main %%} + Hi! + + I am a test template to make sure jinja2 generation works well with hyde. + {{resource.meta.title}} + {{resource.meta.author}} + {{resource.meta.twitter}} +{%% endblock %%} +""" + about2 = File(TEST_SITE.child('content/about2.html')) + about2.write(text % d) + meta = File(TEST_SITE.child('content/nodemeta.yaml')) + meta.write(yaml.dump(d)) + s = Site(TEST_SITE) + s.config.plugins = ['hyde.ext.plugins.meta.MetaPlugin'] + gen = Generator(s) + gen.generate_all() + res = s.content.resource_from_path(about2.path) + assert hasattr(res, 'meta') + assert hasattr(res.meta, 'title') + assert hasattr(res.meta, 'author') + assert hasattr(res.meta, 'twitter') + assert res.meta.title == "Even nicer title" + assert res.meta.author == "Lakshmi Vyas" + assert res.meta.twitter == "lakshmivyas" + target = File(Folder(s.config.deploy_root_path).child('about2.html')) + text = target.read_all() + q = PyQuery(text) + for k, v in d.items(): + if not k == 'title': + assert v in q("span." + k).text() + assert q("span.title").text() == "Even nicer title" + + def test_can_load_from_site_meta(self): + d = {'title': 'A nice title', + 'author': 'Lakshmi Vyas'} + text = """ +--- +title: Even nicer title +--- +{%% extends "base.html" %%} + +{%% block main %%} + Hi! + + I am a test template to make sure jinja2 generation works well with hyde. + {{resource.meta.title}} + {{resource.meta.author}} + {{resource.meta.twitter}} +{%% endblock %%} +""" + about2 = File(TEST_SITE.child('content/about2.html')) + about2.write(text % d) + meta = File(TEST_SITE.child('content/nodemeta.yaml')) + meta.write(yaml.dump(d)) + s = Site(TEST_SITE) + s.config.plugins = ['hyde.ext.plugins.meta.MetaPlugin'] + s.config.meta = { + 'author': 'Lakshmi', + 'twitter': 'lakshmivyas' + } + gen = Generator(s) + gen.generate_all() + res = s.content.resource_from_path(about2.path) + assert hasattr(res, 'meta') + assert hasattr(res.meta, 'title') + assert hasattr(res.meta, 'author') + assert hasattr(res.meta, 'twitter') + assert res.meta.title == "Even nicer title" + assert res.meta.author == "Lakshmi Vyas" + assert res.meta.twitter == "lakshmivyas" + target = File(Folder(s.config.deploy_root_path).child('about2.html')) + text = target.read_all() + q = PyQuery(text) + for k, v in d.items(): + if not k == 'title': + assert v in q("span." + k).text() + assert q("span.title").text() == "Even nicer title" + + + def test_multiple_levels(self): + + page_d = {'title': 'An even nicer title'} + + blog_d = {'author': 'Lakshmi'} + + content_d = {'title': 'A nice title', + 'author': 'Lakshmi Vyas'} + + site_d = {'author': 'Lakshmi', + 'twitter': 'lakshmivyas'} + text = """ +--- +title: %(title)s +--- +{%% extends "base.html" %%} + +{%% block main %%} + Hi! + + I am a test template to make sure jinja2 generation works well with hyde. + {{resource.meta.title}} + {{resource.meta.author}} + {{resource.meta.twitter}} +{%% endblock %%} +""" + about2 = File(TEST_SITE.child('content/blog/about2.html')) + about2.write(text % page_d) + content_meta = File(TEST_SITE.child('content/nodemeta.yaml')) + content_meta.write(yaml.dump(content_d)) + content_meta = File(TEST_SITE.child('content/blog/nodemeta.yaml')) + content_meta.write(yaml.dump(blog_d)) + s = Site(TEST_SITE) + s.config.plugins = ['hyde.ext.plugins.meta.MetaPlugin'] + s.config.meta = site_d + gen = Generator(s) + gen.generate_all() + expected = {} + + expected.update(site_d) + expected.update(content_d) + expected.update(blog_d) + expected.update(page_d) + + res = s.content.resource_from_path(about2.path) + assert hasattr(res, 'meta') + for k, v in expected.items(): + assert hasattr(res.meta, k) + assert getattr(res.meta, k) == v + target = File(Folder(s.config.deploy_root_path).child('blog/about2.html')) + text = target.read_all() + q = PyQuery(text) + for k, v in expected.items(): + assert v in q("span." + k).text() diff --git a/hyde/tests/test_model.py b/hyde/tests/test_model.py index 1854c09..865d99f 100644 --- a/hyde/tests/test_model.py +++ b/hyde/tests/test_model.py @@ -26,6 +26,18 @@ def test_expando_three_levels(): assert x.b.c == d['b']['c'] assert x.b.d.e == d['b']['d']['e'] +def test_expando_update(): + d1 = {"a": 123, "b": "abc"} + x = Expando(d1) + assert x.a == d1['a'] + assert x.b == d1['b'] + d = {"b": {"c": 456, "d": {"e": "abc"}}, "f": "lmn"} + x.update(d) + assert x.a == d1['a'] + assert x.b.c == d['b']['c'] + assert x.b.d.e == d['b']['d']['e'] + assert x.f == d["f"] + TEST_SITE_ROOT = File(__file__).parent.child_folder('sites/test_jinja') import yaml class TestConfig(object): diff --git a/hyde/tests/test_site.py b/hyde/tests/test_site.py index 52f0e84..499c9ed 100644 --- a/hyde/tests/test_site.py +++ b/hyde/tests/test_site.py @@ -82,6 +82,20 @@ def test_walk_resources(): expected.sort() assert pages == expected +def test_contains_resource(): + s = Site(TEST_SITE_ROOT) + s.load() + path = 'blog/2010/december' + node = s.content.node_from_relative_path(path) + assert node.contains_resource('merry-christmas.html') + +def test_get_resource(): + s = Site(TEST_SITE_ROOT) + s.load() + path = 'blog/2010/december' + node = s.content.node_from_relative_path(path) + resource = node.get_resource('merry-christmas.html') + assert resource == s.content.resource_from_relative_path(Folder(path).child('merry-christmas.html')) class TestSiteWithConfig(object):