1
2
3
4
5
6
7
8
9
10
11 """
12 This is the default blosxom renderer. It tries to match the behavior
13 of the blosxom renderer.
14 """
15
16 __revision__ = "$Revision: 1012 $"
17
18 import os
19 import sys
20 import codecs
21
22 from Pyblosxom import tools
23 from Pyblosxom.renderers.base import RendererBase
24
26 """
27 This exception gets thrown when the flavour requested is not
28 available in this blog.
29 """
31 Exception.__init__(self)
32 self._msg = msg
33
35 """
36 PyBlosxom comes with flavours in taste.flav directories in the flavours
37 subdirectory of the Pyblosxom package. This method pulls the template
38 files for the associated taste (assuming it exists) or None if it
39 doesn't.
40
41 @param taste: The name of the taste. e.g. "html", "rss", ...
42 @type taste: string
43
44 @returns: A dict of template type to template file or None
45 @rtype: dict or None
46 """
47 path = __file__[:__file__.rfind(os.sep)]
48 path = path[:path.rfind(os.sep)+1] + "flavours" + os.sep
49
50 path = path + taste + ".flav"
51
52 if os.path.isdir(path):
53 template_files = os.listdir(path)
54 template_d = {}
55 for mem in template_files:
56 if not mem.endswith("." + taste):
57 continue
58 template_d[os.path.splitext(mem)[0]] = path + os.sep + mem
59 return template_d
60
61 return None
62
64 """
65 Tries to get the template files for the flavour of a certain
66 taste (html, rss, atom10, ...) in a directory. The files could
67 be in the directory or in a taste.flav subdirectory.
68
69 @param path: the path of the directory to look for the flavour
70 templates in
71 @type path: string
72
73 @param taste: the flavour files to look for (e.g. html, rss, atom10, ...)
74 @type taste: string
75
76 @returns: the map of template name to template file path
77 @rtype: map
78 """
79 template_d = {}
80
81
82 if os.path.isdir(path + os.sep + taste + ".flav"):
83 newpath = path + os.sep + taste + ".flav"
84 template_files = os.listdir(newpath)
85 for mem in template_files:
86 if not mem.endswith("." + taste):
87 continue
88 template_d[os.path.splitext(mem)[0]] = newpath + os.sep + mem
89 return template_d
90
91
92 template_files = os.listdir(path)
93 for mem in template_files:
94 if not mem.endswith("." + taste):
95 continue
96 template_d[os.path.splitext(mem)[0]] = path + os.sep + mem
97
98 if template_d:
99 return template_d
100
101 return None
102
104 """
105 This is the default blosxom renderer. It tries to match the behavior
106 of the blosxom renderer.
107 """
108 - def __init__(self, request, stdoutput = sys.stdout):
109 RendererBase.__init__(self, request, stdoutput)
110 config = request.getConfiguration()
111 sw = codecs.lookup(config.get('blog_encoding', 'iso-8859-1'))[3]
112 self._out = sw(self._out)
113 self.dayFlag = 1
114 self._request = request
115 self._encoding = config.get("blog_encoding", "iso-8859-1")
116 self.flavour = None
117
119 """
120 This retrieves all the template files for a given flavour taste.
121 This will first pull the templates for the default flavour
122 of this taste if there are any. Then it looks at EITHER
123 the configured datadir OR the flavourdir (if configured). It'll
124 go through directories overriding the template files it has
125 already picked up descending the category path of the PyBlosxom
126 request.
127
128 For example, if the user requested the "html" flavour and is
129 looking at an entry in the category "dev/pyblosxom", then
130 _getflavour will:
131
132 1. pick up the flavour files in the default html flavour
133 2. start in EITHER datadir OR flavourdir (if configured)
134 3. override the default html flavour files with html flavour
135 files in this directory or in html.flav/ subdirectory
136 4. override the html flavour files it's picked up so far
137 with html files in dev/ or dev/html.flav/
138 5. override the html flavour files it's picked up so far
139 with html files in dev/pyblosxom/ or
140 dev/pyblosxom/html.flav/
141
142 If it doesn't find any flavour files at all, then it returns
143 None which indicates the flavour doesn't exist in this blog.
144
145 @param taste: the taste to retrieve flavour files for.
146 @type taste: string
147
148 @returns: mapping of template name to template file data
149 @rtype: map
150 """
151 data = self._request.getData()
152 config = self._request.getConfiguration()
153 datadir = config["datadir"]
154
155
156
157 flavourdir = config.get("flavourdir", datadir)
158
159
160
161 template_d = get_included_flavour(taste)
162 if not template_d:
163 template_d = {}
164
165 pathinfo = list(data["path_info"])
166
167
168 new_files = get_flavour_from_dir(flavourdir, taste)
169 if new_files:
170 template_d.update(new_files)
171
172
173
174
175 while len(pathinfo) > 0:
176 flavourdir = os.path.join(flavourdir, pathinfo.pop(0))
177 if os.path.isfile(flavourdir):
178 break
179
180 if not os.path.isdir(flavourdir):
181 break
182
183 new_files = get_flavour_from_dir(flavourdir, taste)
184 if new_files:
185 template_d.update(new_files)
186
187
188 if not template_d:
189 raise NoSuchFlavourException("Flavour '%s' does not exist." % taste)
190
191 for k in template_d.keys():
192 flav_template = unicode(open(template_d[k]).read(),
193 config.get('blog_encoding', 'iso-8859-1'))
194 template_d[k] = flav_template
195
196 return template_d
197
199 """
200 @param entry: either a dict or the Entry object
201 @type entry: dict or Entry
202
203 @param template: the template string
204 @type template: string
205
206 @returns: the content string
207 @rtype: string
208 """
209 if template:
210 template = unicode(template)
211 finaltext = tools.parse(self._request, self._encoding,
212 entry, template)
213 return finaltext.replace(r'\$', '$')
214 return ""
215
216 - def _processEntry(self, entry, current_date):
217 """
218 Main workhorse of pyblosxom stories, comments and other miscelany goes
219 here
220
221 @param entry: either a dict or an Entry object
222 @type entry: dict or Entry object
223
224 @param current_date: the date of entries we're looking at
225 @type string
226
227 @returns: the output string and the new current date
228 @rtype: (string, string)
229 """
230 data = self._request.getData()
231 config = self._request.getConfiguration()
232
233 output = []
234
235 if data['content-type'] == 'text/plain':
236 s = tools.Stripper()
237 s.feed(entry.getData())
238 s.close()
239 p = [' ' + line for line in s.gettext().split('\n')]
240 entry.setData('\n'.join(p))
241
242 entry.update(data)
243 entry.update(config)
244
245 if entry["date"] and entry['date'] != current_date:
246 current_date = entry['date']
247 if not self.dayFlag:
248 self.outputTemplate(output, entry, 'date_foot')
249 self.dayFlag = 0
250 self.outputTemplate(output, entry, 'date_head')
251
252 self.outputTemplate(output, entry, 'story', override=1)
253
254 template = u""
255 args = self._run_callback("story_end",
256 { "entry": entry, "template": template })
257
258 return "".join(output) + args['template'], current_date
259
260 - def _processContent(self):
261 """
262 Processes the content for the story portion of a page.
263
264 @returns: the content string
265 @rtype: string
266 """
267 data = self._request.getData()
268
269 outputbuffer = []
270
271 if callable(self._content):
272
273
274 outputbuffer.append(self._content())
275
276 elif isinstance(self._content, dict):
277
278
279 self._content.update(data)
280 output = tools.parse(self._request, self._encoding,
281 self._content, self.flavour['story'])
282 outputbuffer.append(output)
283
284 elif isinstance(self._content, list):
285 current_date = ''
286
287 for entry in self._content:
288 output, current_date = self._processEntry(entry, current_date)
289 outputbuffer.append(output)
290
291 return self.write(u"".join(outputbuffer))
292
294 """
295 Figures out flavours and such and then renders the content according
296 to which flavour we're using.
297
298 @param header: whether (1) or not (0) to render the HTTP headers
299 @type header: boolean
300 """
301
302 if self.rendered == 1:
303 return
304
305 data = self._request.getData()
306 config = self._request.getConfiguration()
307
308 parsevars = tools.VariableDict()
309 parsevars.update(config)
310 parsevars.update(data)
311
312 try:
313 self.flavour = self._getflavour(data.get("flavour", "html"))
314
315 except NoSuchFlavourException, nsfe:
316 error_msg = nsfe._msg
317 try:
318 self.flavour = self._getflavour("error")
319 except NoSuchFlavourException, nsfe2:
320 self.flavour = get_included_flavour("error")
321 error_msg = error_msg + "And your error flavour doesn't exist."
322
323 self._content = { "title": "Flavour error",
324 "body": error_msg }
325
326 data['content-type'] = self.flavour['content_type'].strip()
327 if header:
328 if self._needs_content_type and data['content-type'] != "":
329 self.addHeader('Content-type', '%(content-type)s' % data)
330
331 self.showHeaders()
332
333 if self._content:
334 if self.flavour.has_key('head'):
335 self._outputFlavour(parsevars,'head')
336 if self.flavour.has_key('story'):
337 self._processContent()
338 if self.flavour.has_key('date_foot'):
339 self._outputFlavour(parsevars,'date_foot')
340 if self.flavour.has_key('foot'):
341 self._outputFlavour(parsevars,'foot')
342
343 self.rendered = 1
344
346 """
347 Find the flavour template for template_name, run any blosxom callbacks,
348 substitute vars into it and write the template to the output
349
350 @param entry: the EntryBase object
351 @type entry: L{Pyblosxom.entries.base.EntryBase}
352
353 @param template_name: - name of the flavour template
354 @type template_name: string
355 """
356 template = self.flavour[template_name]
357
358 args = self._run_callback(template_name,
359 { "entry": entry, "template": template })
360 template = args["template"]
361 entry = args["entry"]
362
363 self.write(tools.parse(self._request, self._encoding, entry, template))
364
366 """
367 Find the flavour template for template_name, run any blosxom callbacks,
368 substitute entry into it and append the template to the output.
369
370 If the entry has a "template_name" property and override is 1
371 (this happens in the story template), then we'll use that
372 template instead.
373
374 @param output: list of strings of the output
375 @type output: list
376
377 @param entry: the entry to render with this flavour template
378 @type entry: L{Pyblosxom.entries.base.EntryBase}
379
380 @param template_name: name of the flavour template to use
381 @type template_name: string
382
383 @param override: whether (1) or not (0) this template can
384 be overriden with the "template_name" property of the entry
385 @type override: boolean
386 """
387 template = ""
388 if override == 1:
389
390
391
392 actual_template_name = entry.get("template_name", template_name)
393 template = self.flavour.get(actual_template_name, '')
394
395 if not template:
396 template = self.flavour.get(template_name, '')
397
398
399 args = self._run_callback(template_name,
400 { "entry": entry, "template": template })
401
402 template = args["template"]
403 entry = args["entry"]
404
405 output.append(self._printTemplate(entry, template))
406
408 """
409 Makes calling blosxom callbacks a bit easier since they all have the
410 same mechanics. This function merely calls run_callback with
411 the arguments given and a mappingfunc.
412
413 The mappingfunc copies the "template" value from the output to the
414 input for the next function.
415
416 Refer to run_callback for more details.
417 """
418 input.update( { "renderer": self } )
419 input.update( { "request": self._request } )
420
421 return tools.run_callback(chain, input,
422 mappingfunc=lambda x,y: x,
423 defaultfunc=lambda x:x)
424
425 - def getContent(self):
426 """
427 Return the content field
428
429 This is exposed for blosxom callbacks.
430
431 @returns: content
432 """
433 return self._content
434
436 """
437 This is a null renderer.
438 """
439 pass
440
441
442