Mercurial > piecrust2
comparison piecrust/data/linker.py @ 237:879fe1457e48
data: `Linker` refactor.
* Unify the `Linker` and `RecursiveLinker`.
* When a page and a directory share the same name, merge their entries in
the returned iterator.
* Tentative new templating interface.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Sun, 15 Feb 2015 22:46:23 -0800 |
parents | d6d0e4976beb |
children | f130365568ff |
comparison
equal
deleted
inserted
replaced
236:eaf18442bff8 | 237:879fe1457e48 |
---|---|
7 | 7 |
8 logger = logging.getLogger(__name__) | 8 logger = logging.getLogger(__name__) |
9 | 9 |
10 | 10 |
11 class LinkedPageData(PaginationData): | 11 class LinkedPageData(PaginationData): |
12 debug_render = ['name', 'is_dir', 'is_self'] | 12 """ Class whose instances get returned when iterating on a `Linker` |
13 | 13 or `RecursiveLinker`. It's just like what gets usually returned by |
14 def __init__(self, name, page, is_self=False): | 14 `Paginator` and other page iterators, but with a few additional data |
15 like hierarchical data. | |
16 """ | |
17 debug_render = ['is_dir', 'is_self'] + PaginationData.debug_render | |
18 | |
19 def __init__(self, page): | |
15 super(LinkedPageData, self).__init__(page) | 20 super(LinkedPageData, self).__init__(page) |
16 self.name = name | 21 self.name = page.config.get('__linker_name') |
17 self.is_self = is_self | 22 self.is_self = page.config.get('__linker_is_self') |
18 | 23 self.children = page.config.get('__linker_child') |
19 @property | 24 self.is_dir = (self.children is not None) |
20 def is_dir(self): | 25 self.is_page = True |
21 return False | 26 |
27 self.mapLoader('*', self._linkerChildLoader) | |
28 | |
29 def _linkerChildLoader(self, name): | |
30 return getattr(self.children, name) | |
31 | |
32 | |
33 class LinkedPageDataBuilderIterator(object): | |
34 """ Iterator that builds `LinkedPageData` out of pages. | |
35 """ | |
36 def __init__(self, it): | |
37 self.it = it | |
38 | |
39 def __iter__(self): | |
40 for item in self.it: | |
41 yield LinkedPageData(item) | |
22 | 42 |
23 | 43 |
24 class LinkerSource(IPaginationSource): | 44 class LinkerSource(IPaginationSource): |
25 def __init__(self, pages): | 45 """ Source iterator that returns pages given by `Linker`. |
26 self._pages = pages | 46 """ |
47 def __init__(self, pages, orig_source): | |
48 self._pages = list(pages) | |
49 self._orig_source = None | |
50 if isinstance(orig_source, IPaginationSource): | |
51 self._orig_source = orig_source | |
27 | 52 |
28 def getItemsPerPage(self): | 53 def getItemsPerPage(self): |
29 raise NotImplementedError() | 54 raise NotImplementedError() |
30 | 55 |
31 def getSourceIterator(self): | 56 def getSourceIterator(self): |
32 return self._pages | 57 return self._pages |
33 | 58 |
34 def getSorterIterator(self, it): | 59 def getSorterIterator(self, it): |
60 # We don't want to sort the pages -- we expect the original source | |
61 # to return hierarchical items in the order it wants already. | |
35 return None | 62 return None |
36 | 63 |
37 def getTailIterator(self, it): | 64 def getTailIterator(self, it): |
38 return None | 65 return LinkedPageDataBuilderIterator(it) |
39 | 66 |
40 def getPaginationFilter(self, page): | 67 def getPaginationFilter(self, page): |
41 return None | 68 return None |
42 | 69 |
43 def getSettingAccessor(self): | 70 def getSettingAccessor(self): |
44 return lambda i, n: i.get(n) | 71 if self._orig_source: |
45 | 72 return self._orig_source.getSettingAccessor() |
46 | 73 return None |
47 class LinkedPageDataIterator(object): | |
48 def __init__(self, items): | |
49 self._items = list(items) | |
50 self._index = -1 | |
51 | |
52 def __iter__(self): | |
53 return self | |
54 | |
55 def __next__(self): | |
56 self._index += 1 | |
57 if self._index >= len(self._items): | |
58 raise StopIteration() | |
59 return self._items[self._index] | |
60 | |
61 def sort(self, name): | |
62 def key_getter(item): | |
63 return item[name] | |
64 self._items = sorted(self._item, key=key_getter) | |
65 return self | |
66 | 74 |
67 | 75 |
68 class Linker(object): | 76 class Linker(object): |
69 debug_render_doc = """Provides access to sibling and children pages.""" | 77 debug_render_doc = """Provides access to sibling and children pages.""" |
70 | 78 |
71 def __init__(self, source, *, name=None, dir_path=None, page_path=None): | 79 def __init__(self, source, *, name=None, dir_path=None, page_path=None): |
72 self.source = source | 80 self._source = source |
73 self._name = name | 81 self._name = name |
74 self._dir_path = dir_path | 82 self._dir_path = dir_path |
75 self._root_page_path = page_path | 83 self._root_page_path = page_path |
76 self._cache = None | 84 self._items = None |
77 self._is_listable = None | 85 |
86 self.is_dir = True | |
87 self.is_page = False | |
88 self.is_self = False | |
78 | 89 |
79 def __iter__(self): | 90 def __iter__(self): |
80 self._load() | 91 return iter(self.pages) |
81 return LinkedPageDataIterator(self._cache.values()) | |
82 | 92 |
83 def __getattr__(self, name): | 93 def __getattr__(self, name): |
84 self._load() | 94 self._load() |
85 try: | 95 try: |
86 return self._cache[name] | 96 item = self._items[name] |
87 except KeyError: | 97 except KeyError: |
88 raise AttributeError() | 98 raise AttributeError() |
89 | 99 |
100 if isinstance(item, Linker): | |
101 return item | |
102 | |
103 return LinkedPageData(item) | |
104 | |
90 @property | 105 @property |
91 def name(self): | 106 def name(self): |
92 if not self._name: | 107 if self._name is None: |
93 self._load() | 108 self._load() |
94 return self._name | 109 return self._name |
95 | 110 |
96 @property | 111 @property |
97 def is_dir(self): | 112 def children(self): |
98 return True | 113 return self._iterItems(0) |
99 | 114 |
100 @property | 115 @property |
101 def is_self(self): | 116 def pages(self): |
102 return False | 117 return self._iterItems(0, filter_page_items) |
118 | |
119 @property | |
120 def directories(self): | |
121 return self._iterItems(0, filter_directory_items) | |
122 | |
123 @property | |
124 def all(self): | |
125 return self._iterItems() | |
126 | |
127 @property | |
128 def allpages(self): | |
129 return self._iterItems(-1, filter_page_items) | |
130 | |
131 @property | |
132 def alldirectories(self): | |
133 return self._iterItems(-1, filter_directory_items) | |
134 | |
135 @property | |
136 def root(self): | |
137 return self.forpath('/') | |
138 | |
139 def forpath(self, rel_path): | |
140 return Linker(self._source, | |
141 name='.', dir_path=rel_path, | |
142 page_path=self._root_page_path) | |
143 | |
144 def _iterItems(self, max_depth=-1, filter_func=None): | |
145 items = walk_linkers(self, max_depth=max_depth, | |
146 filter_func=filter_func) | |
147 src = LinkerSource(items, self._source) | |
148 return PageIterator(src) | |
103 | 149 |
104 def _load(self): | 150 def _load(self): |
105 if self._cache is not None: | 151 if self._items is not None: |
106 return | 152 return |
107 | 153 |
108 self._is_listable = isinstance(self.source, IListableSource) | 154 is_listable = isinstance(self._source, IListableSource) |
109 if self._is_listable and self._root_page_path is not None: | 155 if not is_listable: |
156 raise Exception("Source '%s' can't be listed." % self._source.name) | |
157 | |
158 if self._root_page_path is not None: | |
110 if self._name is None: | 159 if self._name is None: |
111 self._name = self.source.getBasename(self._root_page_path) | 160 self._name = self._source.getBasename(self._root_page_path) |
112 if self._dir_path is None: | 161 if self._dir_path is None: |
113 self._dir_path = self.source.getDirpath(self._root_page_path) | 162 self._dir_path = self._source.getDirpath(self._root_page_path) |
114 | 163 |
115 self._cache = collections.OrderedDict() | 164 if self._dir_path is None: |
116 if not self._is_listable or self._dir_path is None: | 165 raise Exception("This linker has no directory to start from.") |
117 return | 166 |
118 | 167 items = list(self._source.listPath(self._dir_path)) |
119 items = self.source.listPath(self._dir_path) | 168 self._items = collections.OrderedDict() |
120 with self.source.app.env.page_repository.startBatchGet(): | 169 with self._source.app.env.page_repository.startBatchGet(): |
121 for is_dir, name, data in items: | 170 for is_dir, name, data in items: |
171 # If `is_dir` is true, `data` will be the directory's source | |
172 # path. If not, it will be a page factory. | |
122 if is_dir: | 173 if is_dir: |
123 self._cache[name] = Linker(self.source, | 174 item = Linker(self._source, |
124 name=name, dir_path=data) | 175 name=name, dir_path=data) |
125 else: | 176 else: |
126 page = data.buildPage() | 177 item = data.buildPage() |
127 is_root_page = (self._root_page_path == data.rel_path) | 178 item.config.set('__linker_name', name) |
128 self._cache[name] = LinkedPageData(name, page, | 179 item.config.set('__linker_is_self', |
129 is_root_page) | 180 item.rel_path == self._root_page_path) |
130 | 181 |
131 | 182 existing = self._items.get(name) |
132 class RecursiveLinker(Linker): | 183 if existing is None: |
133 def __init__(self, source, *args, **kwargs): | 184 self._items[name] = item |
134 super(RecursiveLinker, self).__init__(source, *args, **kwargs) | 185 elif is_dir: |
135 | 186 # The current item is a directory. The existing item |
136 def __iter__(self): | 187 # should be a page. |
137 return iter(self.pages) | 188 existing.config.set('__linker_child', item) |
138 | 189 else: |
139 def __getattr__(self, name): | 190 # The current item is a page. The existing item should |
140 if name == 'pages': | 191 # be a directory. |
141 return self.getpages() | 192 item.config.set('__linker_child', existing) |
142 if name == 'siblings': | 193 self._items[name] = item |
143 return self.getsiblings() | 194 |
144 raise AttributeError() | 195 |
145 | 196 def filter_page_items(item): |
146 def getpages(self): | 197 return not isinstance(item, Linker) |
147 src = LinkerSource(self._iterateLinkers()) | 198 |
148 return PageIterator(src) | 199 |
149 | 200 def filter_directory_items(item): |
150 def getsiblings(self): | 201 return isinstance(item, linker) |
151 src = LinkerSource(self._iterateLinkers(0)) | 202 |
152 return PageIterator(src) | 203 |
153 | 204 def walk_linkers(linker, depth=0, max_depth=-1, filter_func=None): |
154 def frompath(self, rel_path): | |
155 return RecursiveLinker(self.source, name='.', dir_path=rel_path) | |
156 | |
157 def _iterateLinkers(self, max_depth=-1): | |
158 self._load() | |
159 if not self._is_listable: | |
160 return | |
161 yield from walk_linkers(self, 0, max_depth) | |
162 | |
163 | |
164 def walk_linkers(linker, depth=0, max_depth=-1): | |
165 linker._load() | 205 linker._load() |
166 for item in linker._cache.values(): | 206 for item in linker._items.values(): |
167 if item.is_dir: | 207 if not filter_func or filter_func(item): |
168 if max_depth < 0 or depth + 1 <= max_depth: | |
169 yield from walk_linkers(item, depth + 1, max_depth) | |
170 else: | |
171 yield item | 208 yield item |
172 | 209 |
210 if (isinstance(item, Linker) and | |
211 (max_depth < 0 or depth + 1 <= max_depth)): | |
212 yield from walk_linkers(item, depth + 1, max_depth) | |
213 |