comparison piecrust/dataproviders/blog.py @ 853:f070a4fc033c

core: Continue PieCrust3 refactor, simplify pages. The asset pipeline is still the only function pipeline at this point. * No more `QualifiedPage`, and several other pieces of code deleted. * Data providers are simpler and more focused. For instance, the page iterator doesn't try to support other types of items. * Route parameters are proper known source metadata to remove the confusion between the two. * Make the baker and pipeline more correctly manage records and record histories. * Add support for record collapsing and deleting stale outputs in the asset pipeline.
author Ludovic Chabant <ludovic@chabant.com>
date Sun, 21 May 2017 00:06:59 -0700
parents
children 08e02c2a2a1a
comparison
equal deleted inserted replaced
852:4850f8c21b6e 853:f070a4fc033c
1 import time
2 import collections.abc
3 from piecrust.dataproviders.base import DataProvider
4 from piecrust.generation.taxonomy import Taxonomy
5
6
7 class BlogDataProvider(DataProvider, collections.abc.Mapping):
8 PROVIDER_NAME = 'blog'
9
10 debug_render_doc = """Provides a list of blog posts and yearly/monthly
11 archives."""
12 debug_render_dynamic = (['_debugRenderTaxonomies'] +
13 DataProvider.debug_render_dynamic)
14
15 def __init__(self, source, page, override):
16 super(BlogDataProvider, self).__init__(source, page, override)
17 self._yearly = None
18 self._monthly = None
19 self._taxonomies = {}
20 self._ctx_set = False
21
22 @property
23 def posts(self):
24 return self._posts()
25
26 @property
27 def years(self):
28 return self._buildYearlyArchive()
29
30 @property
31 def months(self):
32 return self._buildMonthlyArchive()
33
34 def __getitem__(self, name):
35 if name == 'posts':
36 return self._posts()
37 elif name == 'years':
38 return self._buildYearlyArchive()
39 elif name == 'months':
40 return self._buildMonthlyArchive()
41
42 if self._source.app.config.get('site/taxonomies/' + name) is not None:
43 return self._buildTaxonomy(name)
44
45 raise KeyError("No such item: %s" % name)
46
47 def __iter__(self):
48 keys = ['posts', 'years', 'months']
49 keys += list(self._source.app.config.get('site/taxonomies').keys())
50 return iter(keys)
51
52 def __len__(self):
53 return 3 + len(self._source.app.config.get('site/taxonomies'))
54
55 def _debugRenderTaxonomies(self):
56 return list(self._source.app.config.get('site/taxonomies').keys())
57
58 def _posts(self):
59 it = PageIterator(self._source, current_page=self._page)
60 it._iter_event += self._onIteration
61 return it
62
63 def _buildYearlyArchive(self):
64 if self._yearly is not None:
65 return self._yearly
66
67 self._yearly = []
68 yearly_index = {}
69 for post in self._source.getPages():
70 year = post.datetime.strftime('%Y')
71
72 posts_this_year = yearly_index.get(year)
73 if posts_this_year is None:
74 timestamp = time.mktime(
75 (post.datetime.year, 1, 1, 0, 0, 0, 0, 0, -1))
76 posts_this_year = BlogArchiveEntry(self._page, year, timestamp)
77 self._yearly.append(posts_this_year)
78 yearly_index[year] = posts_this_year
79
80 posts_this_year._data_source.append(post)
81 self._yearly = sorted(self._yearly,
82 key=lambda e: e.timestamp,
83 reverse=True)
84 self._onIteration()
85 return self._yearly
86
87 def _buildMonthlyArchive(self):
88 if self._monthly is not None:
89 return self._monthly
90
91 self._monthly = []
92 for post in self._source.getPages():
93 month = post.datetime.strftime('%B %Y')
94
95 posts_this_month = next(
96 filter(lambda m: m.name == month, self._monthly),
97 None)
98 if posts_this_month is None:
99 timestamp = time.mktime(
100 (post.datetime.year, post.datetime.month, 1,
101 0, 0, 0, 0, 0, -1))
102 posts_this_month = BlogArchiveEntry(self._page, month, timestamp)
103 self._monthly.append(posts_this_month)
104
105 posts_this_month._data_source.append(post)
106 self._monthly = sorted(self._monthly,
107 key=lambda e: e.timestamp,
108 reverse=True)
109 self._onIteration()
110 return self._monthly
111
112 def _buildTaxonomy(self, tax_name):
113 if tax_name in self._taxonomies:
114 return self._taxonomies[tax_name]
115
116 tax_cfg = self._page.app.config.get('site/taxonomies/' + tax_name)
117 tax = Taxonomy(tax_name, tax_cfg)
118
119 posts_by_tax_value = {}
120 for post in self._source.getPages():
121 tax_values = post.config.get(tax.setting_name)
122 if tax_values is None:
123 continue
124 if not isinstance(tax_values, list):
125 tax_values = [tax_values]
126 for val in tax_values:
127 posts = posts_by_tax_value.setdefault(val, [])
128 posts.append(post)
129
130 entries = []
131 for value, ds in posts_by_tax_value.items():
132 source = ArraySource(self._page.app, ds)
133 entries.append(BlogTaxonomyEntry(self._page, source, value))
134 self._taxonomies[tax_name] = sorted(entries, key=lambda k: k.name)
135
136 self._onIteration()
137 return self._taxonomies[tax_name]
138
139 def _onIteration(self):
140 if not self._ctx_set:
141 eis = self._page.app.env.exec_info_stack
142 if eis.current_page_info:
143 eis.current_page_info.render_ctx.addUsedSource(self._source)
144 self._ctx_set = True
145
146
147 class BlogArchiveEntry(object):
148 debug_render = ['name', 'timestamp', 'posts']
149 debug_render_invoke = ['name', 'timestamp', 'posts']
150
151 def __init__(self, page, name, timestamp):
152 self.name = name
153 self.timestamp = timestamp
154 self._page = page
155 self._data_source = []
156 self._iterator = None
157
158 def __str__(self):
159 return self.name
160
161 def __int__(self):
162 return int(self.name)
163
164 @property
165 def posts(self):
166 self._load()
167 self._iterator.reset()
168 return self._iterator
169
170 def _load(self):
171 if self._iterator is not None:
172 return
173 source = ArraySource(self._page.app, self._data_source)
174 self._iterator = PageIterator(source, current_page=self._page)
175
176
177 class BlogTaxonomyEntry(object):
178 debug_render = ['name', 'post_count', 'posts']
179 debug_render_invoke = ['name', 'post_count', 'posts']
180
181 def __init__(self, page, source, property_value):
182 self._page = page
183 self._source = source
184 self._property_value = property_value
185 self._iterator = None
186
187 def __str__(self):
188 return self._property_value
189
190 @property
191 def name(self):
192 return self._property_value
193
194 @property
195 def posts(self):
196 self._load()
197 self._iterator.reset()
198 return self._iterator
199
200 @property
201 def post_count(self):
202 return self._source.page_count
203
204 def _load(self):
205 if self._iterator is not None:
206 return
207
208 self._iterator = PageIterator(self._source, current_page=self._page)
209