changeset 404:27b10024f8d8

linker: Add ability to return the parent and ancestors of a page. Add reference documentation. Add tests.
author Ludovic Chabant <ludovic@chabant.com>
date Sun, 24 May 2015 18:15:22 -0700
parents 563e2e84ef6e
children 1970e7e3a18e
files docs/docs/99_reference/04_templating-data.md piecrust/data/linker.py tests/bakes/test_linker.yaml tests/test_data_linker.py
diffstat 4 files changed, 126 insertions(+), 24 deletions(-) [+]
line wrap: on
line diff
--- a/docs/docs/99_reference/04_templating-data.md	Thu May 21 07:33:08 2015 -0700
+++ b/docs/docs/99_reference/04_templating-data.md	Sun May 24 18:15:22 2015 -0700
@@ -97,6 +97,15 @@
   children of the current page. This is only possible for page sources that
   support hierarchical navigation (but most of those shipping with PieCrust do).
 
+    * `parent`: Returns the data for a parent page, _i.e._ a page with the same
+      name as the current page's directory name. So if the current page is
+      `/foo/bar/something.md`, this would return the data for a page named
+      `/foo/bar.md`.
+
+    * `ancestors`: Returns a list of data for all parents, all the way to the
+      root. In the previous example, this would return `/foo/bar.md` and
+      `/foo.md` (if they exist).
+
     * `siblings`: Returns a list of the current page's siblings, _i.e._ pages on
       the same "level" as the current one.
 
--- a/piecrust/data/linker.py	Thu May 21 07:33:08 2015 -0700
+++ b/piecrust/data/linker.py	Sun May 24 18:15:22 2015 -0700
@@ -13,26 +13,55 @@
         root page.
     """
     def __init__(self, source, page_path):
-        self._linker = Linker(source, page_path)
+        self._source = source
+        self._root_page_path = page_path
+        self._linker = None
+
+    @property
+    def parent(self):
+        self._load()
+        return self._linker.parent
+
+    @property
+    def ancestors(self):
+        cur = self.parent
+        while cur:
+            yield cur
+            cur = cur.parent
 
     @property
     def siblings(self):
+        self._load()
         return self._linker
 
     @property
     def children(self):
+        self._load()
         self._linker._load()
         if self._linker._self_item is None:
             return []
-        return self._linker._self_item._linker_info.child_linker
+        children = self._linker._self_item._linker_info.child_linker
+        if children is None:
+            return []
+        return children
 
     @property
     def root(self):
+        self._load()
         return self._linker.root
 
     def forpath(self, rel_path):
+        self._load()
         return self._linker.forpath(rel_path)
 
+    def _load(self):
+        if self._linker is not None:
+            return
+
+        dir_path = self._source.getDirpath(self._root_page_path)
+        self._linker = Linker(self._source, dir_path,
+                              root_page_path=self._root_page_path)
+
 
 class LinkedPageData(PaginationData):
     """ Class whose instances get returned when iterating on a `Linker`
@@ -46,12 +75,25 @@
         super(LinkedPageData, self).__init__(page)
         self.name = page._linker_info.name
         self.is_self = page._linker_info.is_self
-        self.children = page._linker_info.child_linker
         self.is_dir = page._linker_info.is_dir
         self.is_page = True
+        self._child_linker = page._linker_info.child_linker
 
         self.mapLoader('*', self._linkerChildLoader)
 
+    @property
+    def parent(self):
+        if self._child_linker is not None:
+            return self._child_linker.parent
+        print("No parent for ", self.url, self.title)
+        return None
+
+    @property
+    def children(self):
+        if self._child_linker is not None:
+            return self._child_linker
+        return []
+
     def _linkerChildLoader(self, data, name):
         if self.children and hasattr(self.children, name):
             return getattr(self.children, name)
@@ -106,7 +148,7 @@
         self.name = None
         self.is_dir = False
         self.is_self = False
-        self.child_linker = []
+        self.child_linker = None
 
 
 class _LinkedPage(object):
@@ -121,12 +163,12 @@
 class Linker(object):
     debug_render_doc = """Provides access to sibling and children pages."""
 
-    def __init__(self, source, root_page_path, *, name=None, dir_path=None):
+    def __init__(self, source, dir_path, *, root_page_path=None):
         self._source = source
+        self._dir_path = dir_path
         self._root_page_path = root_page_path
-        self._name = name
-        self._dir_path = dir_path
         self._items = None
+        self._parent = None
         self._self_item = None
 
         self.is_dir = True
@@ -148,17 +190,42 @@
 
         return LinkedPageData(item)
 
+    def __str__(self):
+        return self.name
+
     @property
     def name(self):
-        if self._name is None:
-            self._load()
-        return self._name
+        return self._source.getBasename(self._dir_path)
 
     @property
     def children(self):
         return self._iterItems(0)
 
     @property
+    def parent(self):
+        if self._dir_path == '':
+            return None
+
+        if self._parent is None:
+            parent_name = self._source.getBasename(self._dir_path)
+            parent_dir_path = self._source.getDirpath(self._dir_path)
+            for is_dir, name, data in self._source.listPath(parent_dir_path):
+                if not is_dir and name == parent_name:
+                    parent_page = data.buildPage()
+                    item = _LinkedPage(parent_page)
+                    item._linker_info.name = parent_name
+                    item._linker_info.child_linker = Linker(
+                            self._source, parent_dir_path,
+                            root_page_path=self._root_page_path)
+                    self._parent = LinkedPageData(item)
+                    break
+            else:
+                self._parent = Linker(self._source, parent_dir_path,
+                                      root_page_path=self._root_page_path)
+
+        return self._parent
+
+    @property
     def pages(self):
         return self._iterItems(0, filter_page_items)
 
@@ -183,8 +250,8 @@
         return self.forpath('/')
 
     def forpath(self, rel_path):
-        return Linker(self._source, self._root_page_path,
-                      name='.', dir_path=rel_path)
+        return Linker(self._source, rel_path,
+                      root_page_path=self._root_page_path)
 
     def _iterItems(self, max_depth=-1, filter_func=None):
         items = walk_linkers(self, max_depth=max_depth,
@@ -200,14 +267,6 @@
         if not is_listable:
             raise Exception("Source '%s' can't be listed." % self._source.name)
 
-        if self._name is None:
-            self._name = self._source.getBasename(self._root_page_path)
-        if self._dir_path is None:
-            self._dir_path = self._source.getDirpath(self._root_page_path)
-
-        if self._dir_path is None:
-            raise Exception("This linker has no directory to start from.")
-
         items = list(self._source.listPath(self._dir_path))
         self._items = collections.OrderedDict()
         with self._source.app.env.page_repository.startBatchGet():
@@ -215,8 +274,8 @@
                 # If `is_dir` is true, `data` will be the directory's source
                 # path. If not, it will be a page factory.
                 if is_dir:
-                    item = Linker(self._source, self._root_page_path,
-                                  name=name, dir_path=data)
+                    item = Linker(self._source, data,
+                                  root_page_path=self._root_page_path)
                 else:
                     page = data.buildPage()
                     is_self = (page.rel_path == self._root_page_path)
--- a/tests/bakes/test_linker.yaml	Thu May 21 07:33:08 2015 -0700
+++ b/tests/bakes/test_linker.yaml	Sun May 24 18:15:22 2015 -0700
@@ -46,3 +46,34 @@
         Bar
         Foo SELFIE!
         Other
+---
+in:
+    pages/foo.md: "---\ntitle: Foo\n---\n"
+    pages/foo/one.md: |
+        {{family.parent.url}} {{family.parent.title}}
+outfiles:
+    foo/one.html: /foo.html Foo
+---
+in:
+    pages/foo.md: "---\ntitle: Foo\n---\n"
+    pages/foo/bar.md: "---\ntitle: Bar\n---\n"
+    pages/foo/bar/one.md: |
+        {{family.parent.url}} {{family.parent.title}}
+        {{family.parent.parent.url}} {{family.parent.parent.title}}
+outfiles:
+    foo/bar/one.html: |
+        /foo/bar.html Bar
+        /foo.html Foo
+---
+in:
+    pages/foo.md: "---\ntitle: Foo\n---\n"
+    pages/foo/bar.md: "---\ntitle: Bar\n---\n"
+    pages/foo/bar/one.md: |
+        {% for p in family.ancestors -%}
+        {{p.url}} {{p.title}}
+        {% endfor %}
+outfiles:
+    foo/bar/one.html: |
+        /foo/bar.html Bar
+        /foo.html Foo
+
--- a/tests/test_data_linker.py	Thu May 21 07:33:08 2015 -0700
+++ b/tests/test_data_linker.py	Sun May 24 18:15:22 2015 -0700
@@ -1,3 +1,4 @@
+import os.path
 import pytest
 from piecrust.data.linker import Linker
 from .mockutil import mock_fs, mock_fs_scope
@@ -39,7 +40,8 @@
         app = fs.getApp()
         app.config.set('site/pretty_urls', True)
         src = app.getSource('pages')
-        linker = Linker(src, page_path)
+        linker = Linker(src, os.path.dirname(page_path),
+                        root_page_path=page_path)
         actual = list(iter(linker))
 
         assert len(actual) == len(expected)
@@ -83,7 +85,8 @@
         app = fs.getApp()
         app.config.set('site/pretty_urls', True)
         src = app.getSource('pages')
-        linker = Linker(src, page_path)
+        linker = Linker(src, os.path.dirname(page_path),
+                        root_page_path=page_path)
         actual = list(iter(linker.allpages))
 
         assert len(actual) == len(expected)