Mercurial > piecrust2
diff piecrust/data/linker.py @ 854:08e02c2a2a1a
core: Keep refactoring, this time to prepare for generator sources.
- Make a few APIs simpler.
- Content pipelines create their own jobs, so that generator sources can
keep aborting in `getContents`, but rely on their pipeline to generate
pages for baking.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Sun, 04 Jun 2017 23:34:28 -0700 |
parents | f070a4fc033c |
children | fddaf43424e2 |
line wrap: on
line diff
--- a/piecrust/data/linker.py Sun May 21 00:06:59 2017 -0700 +++ b/piecrust/data/linker.py Sun Jun 04 23:34:28 2017 -0700 @@ -1,16 +1,18 @@ import logging -import collections -from piecrust.data.pagedata import LazyPageConfigLoaderHasNoValue from piecrust.data.paginationdata import PaginationData -from piecrust.dataproviders.page_iterator import PageIterator +from piecrust.sources.base import ( + REL_PARENT_GROUP, REL_LOGICAL_PARENT_ITEM, REL_LOGICAl_CHILD_GROUP) logger = logging.getLogger(__name__) -class PageLinkerData(object): - """ Entry template data to get access to related pages from a given - root page. +_unloaded = object() + + +class Linker: + """ A template-exposed data class that lets the user navigate the + logical hierarchy of pages in a page source. """ debug_render = ['parent', 'ancestors', 'siblings', 'children', 'root', 'forpath'] @@ -22,68 +24,68 @@ 'children': '_debugRenderChildren', 'root': '_debugRenderRoot'} - def __init__(self, source, page_path): - self._source = source - self._root_page_path = page_path - self._linker = None - self._is_loaded = False + def __init__(self, page): + self._page = page + self._content_item = page.content_item + self._source = page.source + self._app = page.app + + self._parent = _unloaded + self._ancestors = None + self._siblings = None + self._children = None @property def parent(self): - self._load() - if self._linker is not None: - return self._linker.parent - return None + if self._parent is _unloaded: + pi = self._source.getRelatedContents(self._content_item, + REL_LOGICAL_PARENT_ITEM) + if pi is not None: + pipage = self._app.getPage(self._source, pi) + self._parent = PaginationData(pipage) + else: + self._parent = None + return self._parent @property def ancestors(self): - cur = self.parent - while cur: - yield cur - cur = cur.parent + if self._ancestors is None: + cur_item = self._content_item + self._ancestors = [] + while True: + pi = self._source.getRelatedContents( + cur_item, REL_LOGICAL_PARENT_ITEM) + if pi is not None: + pipage = self._app.getPage(self._source, pi) + self._ancestors.append(PaginationData(pipage)) + cur_item = pi + else: + break + return self._ancestors @property def siblings(self): - self._load() - if self._linker is None: - return [] - return self._linker + if self._siblings is None: + self._siblings = [] + parent_group = self._source.getRelatedContents( + self._content_item, REL_PARENT_GROUP) + for i in self._source.getContents(parent_group): + if not i.is_group: + ipage = self._app.getPage(self._source, i) + self._siblings.append(PaginationData(ipage)) + return self._siblings @property def children(self): - self._load() - if self._linker is None: - return [] - self._linker._load() - if self._linker._self_item is None: - return [] - children = self._linker._self_item._linker_info.child_linker - if children is None: - return [] - return children - - @property - def root(self): - self._load() - if self._linker is None: - return None - return self._linker.root - - def forpath(self, rel_path): - self._load() - if self._linker is None: - return None - return self._linker.forpath(rel_path) - - def _load(self): - if self._is_loaded: - return - - self._is_loaded = True - - dir_path = self._source.getDirpath(self._root_page_path) - self._linker = Linker(self._source, dir_path, - root_page_path=self._root_page_path) + if self._children is None: + self._children = [] + child_group = self._source.getRelatedContents( + self._content_item, REL_LOGICAl_CHILD_GROUP) + if child_group: + for i in self._source.getContents(child_group): + ipage = self._app.getPage(self._source, i) + self._children.append(PaginationData(ipage)) + return self._children def _debugRenderAncestors(self): return [i.name for i in self.ancestors] @@ -100,257 +102,3 @@ return r.name return None - -class LinkedPageData(PaginationData): - """ Class whose instances get returned when iterating on a `Linker` - or `RecursiveLinker`. It's just like what gets usually returned by - `Paginator` and other page iterators, but with a few additional data - like hierarchical data. - """ - debug_render = (['is_dir', 'is_self', 'parent', 'children'] + - PaginationData.debug_render) - debug_render_invoke = (['is_dir', 'is_self', 'parent', 'children'] + - PaginationData.debug_render_invoke) - - def __init__(self, page): - super(LinkedPageData, self).__init__(page) - self.name = page._linker_info.name - self.is_self = page._linker_info.is_self - 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 - 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) - raise LazyPageConfigLoaderHasNoValue - - -class LinkedPageDataBuilderIterator(object): - """ Iterator that builds `LinkedPageData` out of pages. - """ - def __init__(self, it): - self.it = it - - def __iter__(self): - for item in self.it: - yield LinkedPageData(item) - - -class LinkerSource(IPaginationSource): - """ Source iterator that returns pages given by `Linker`. - """ - def __init__(self, pages, orig_source): - self._pages = list(pages) - self._orig_source = None - if isinstance(orig_source, IPaginationSource): - self._orig_source = orig_source - - def getItemsPerPage(self): - raise NotImplementedError() - - def getSourceIterator(self): - return self._pages - - def getSorterIterator(self, it): - # We don't want to sort the pages -- we expect the original source - # to return hierarchical items in the order it wants already. - return None - - def getTailIterator(self, it): - return LinkedPageDataBuilderIterator(it) - - def getPaginationFilter(self, page): - return None - - def getSettingAccessor(self): - if self._orig_source: - return self._orig_source.getSettingAccessor() - return None - - -class _LinkerInfo(object): - def __init__(self): - self.name = None - self.is_dir = False - self.is_self = False - self.child_linker = None - - -class _LinkedPage(object): - def __init__(self, page): - self._page = page - self._linker_info = _LinkerInfo() - - def __getattr__(self, name): - return getattr(self._page, name) - - -class Linker(object): - debug_render_doc = """Provides access to sibling and children pages.""" - - 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._items = None - self._parent = None - self._self_item = None - - self.is_dir = True - self.is_page = False - self.is_self = False - - def __iter__(self): - return iter(self.pages) - - def __getattr__(self, name): - self._load() - try: - item = self._items[name] - except KeyError: - raise AttributeError() - - if isinstance(item, Linker): - return item - - return LinkedPageData(item) - - def __str__(self): - return self.name - - @property - def name(self): - 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) - - @property - def directories(self): - return self._iterItems(0, filter_directory_items) - - @property - def all(self): - return self._iterItems() - - @property - def allpages(self): - return self._iterItems(-1, filter_page_items) - - @property - def alldirectories(self): - return self._iterItems(-1, filter_directory_items) - - @property - def root(self): - return self.forpath('/') - - def forpath(self, 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, - filter_func=filter_func) - src = LinkerSource(items, self._source) - return PageIterator(src) - - def _load(self): - if self._items is not None: - return - - items = list(self._source.listPath(self._dir_path)) - self._items = collections.OrderedDict() - for is_dir, name, data in items: - # 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, data, - root_page_path=self._root_page_path) - else: - page = data.buildPage() - is_self = (page.rel_path == self._root_page_path) - item = _LinkedPage(page) - item._linker_info.name = name - item._linker_info.is_self = is_self - if is_self: - self._self_item = item - - existing = self._items.get(name) - if existing is None: - self._items[name] = item - elif is_dir: - # The current item is a directory. The existing item - # should be a page. - existing._linker_info.child_linker = item - existing._linker_info.is_dir = True - else: - # The current item is a page. The existing item should - # be a directory. - item._linker_info.child_linker = existing - item._linker_info.is_dir = True - self._items[name] = item - - -def filter_page_items(item): - return not isinstance(item, Linker) - - -def filter_directory_items(item): - return isinstance(item, Linker) - - -def walk_linkers(linker, depth=0, max_depth=-1, filter_func=None): - linker._load() - for item in linker._items.values(): - if not filter_func or filter_func(item): - yield item - - if (isinstance(item, Linker) and - (max_depth < 0 or depth + 1 <= max_depth)): - yield from walk_linkers(item, depth + 1, max_depth) -