Package Pyblosxom :: Package renderers :: Module blosxom
[hide private]
[frames] | no frames]

Source Code for Module Pyblosxom.renderers.blosxom

  1  ####################################################################### 
  2  # This file is part of PyBlosxom. 
  3  # 
  4  # Copyright (c) 2003, 2004, 2005, 2006 Wari Wahab 
  5  #  
  6  # PyBlosxom is distributed under the MIT license.  See the file LICENSE 
  7  # for distribution details. 
  8  # 
  9  # $Id: blosxom.py 1012 2007-05-19 17:57:07Z willhelm $ 
 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   
25 -class NoSuchFlavourException(Exception):
26 """ 27 This exception gets thrown when the flavour requested is not 28 available in this blog. 29 """
30 - def __init__(self, msg):
31 Exception.__init__(self) 32 self._msg = msg
33
34 -def get_included_flavour(taste):
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
63 -def get_flavour_from_dir(path, taste):
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 # if we have a taste.flav directory, we check there 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 # now we check the directory itself for flavour templates 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
103 -class BlosxomRenderer(RendererBase):
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
118 - def _getflavour(self, taste='html'):
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 # if they have flavourdir set, then we look there. otherwise 156 # we look in the datadir. 157 flavourdir = config.get("flavourdir", datadir) 158 159 # first we grab the flavour files for the included flavour (if 160 # we have one). 161 template_d = get_included_flavour(taste) 162 if not template_d: 163 template_d = {} 164 165 pathinfo = list(data["path_info"]) 166 167 # check the root of flavourdir for templates 168 new_files = get_flavour_from_dir(flavourdir, taste) 169 if new_files: 170 template_d.update(new_files) 171 172 # go through all the directories from the flavourdir all 173 # the way up to the root_datadir. this way template files 174 # can override template files in parent directories. 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 # if we still haven't found our flavour files, we raise an exception 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
198 - def _printTemplate(self, entry, template):
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 # if the content is a callable function, then we just spit out 273 # whatever it returns as a string 274 outputbuffer.append(self._content()) 275 276 elif isinstance(self._content, dict): 277 # if the content is a dict, then we parse it as if it were an 278 # entry--except it's distinctly not an EntryBase derivative 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
293 - def render(self, header=1):
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 # if we've already rendered, then we don't want to do so again 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
345 - def _outputFlavour(self, entry, template_name):
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
365 - def outputTemplate(self, output, entry, template_name, override=0):
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 # here we do a quick override... if the entry has a template 390 # field we use that instead of the template_name argument 391 # passed in. 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 # we run this through the regular callbacks 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
407 - def _run_callback(self, chain, input):
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
435 -class Renderer(BlosxomRenderer):
436 """ 437 This is a null renderer. 438 """ 439 pass
440 441 # vim: shiftwidth=4 tabstop=4 expandtab 442