Package Pyblosxom :: Module tools
[hide private]
[frames] | no frames]

Source Code for Module Pyblosxom.tools

   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: tools.py 1061 2007-06-24 17:44:31Z willhelm $ 
  10  ####################################################################### 
  11  """ 
  12  Tools module 
  13   
  14  The swiss army knife for all things pyblosxom 
  15   
  16  @var month2num: A dict of literal months to its number format 
  17  @var num2month: A dict of number month format to its literal format 
  18  @var MONTHS: A list of valid literal and numeral months 
  19  @var VAR_REGEXP: Regular expression for detection and substituion of variables 
  20  """ 
  21   
  22  __revision__ = "$Revision: 1061 $" 
  23   
  24  # Python imports 
  25  import sgmllib 
  26  import re 
  27  import os 
  28  import time 
  29  import os.path 
  30  import stat 
  31  import sys 
  32  import locale 
  33  import urllib 
  34   
  35  try: 
  36      from xml.sax.saxutils import escape 
  37  except ImportError: 
  38      from cgi import escape 
  39   
  40   
  41  # Pyblosxom imports 
  42  import plugin_utils 
  43   
  44  # Month names tend to differ with locale 
  45  month2num = None 
  46  num2month = None 
  47  MONTHS    = None 
  48   
  49  # regular expression for detection and substituion of variables. 
  50  VAR_REGEXP = re.compile(ur'(?<!\\)\$((?:\w|\-|::\w)+(?:\(.*?(?<!\\)\))?)') 
  51   
  52   
  53  # reference to the pyblosxom config dict 
  54  _config = None 
  55   
56 -def initialize(config):
57 """ 58 Initialize the tools module. This gives the module a chance to use 59 configuration from the pyblosxom config.py file. 60 This should be called from Pyblosxom.pyblosxom.PyBlosxom.initialize. 61 """ 62 global _config 63 _config = config 64 65 # Month names tend to differ with locale 66 global month2num 67 month2num = { 'nil' : '00', 68 locale.nl_langinfo(locale.ABMON_1) : '01', 69 locale.nl_langinfo(locale.ABMON_2) : '02', 70 locale.nl_langinfo(locale.ABMON_3) : '03', 71 locale.nl_langinfo(locale.ABMON_4) : '04', 72 locale.nl_langinfo(locale.ABMON_5) : '05', 73 locale.nl_langinfo(locale.ABMON_6) : '06', 74 locale.nl_langinfo(locale.ABMON_7) : '07', 75 locale.nl_langinfo(locale.ABMON_8) : '08', 76 locale.nl_langinfo(locale.ABMON_9) : '09', 77 locale.nl_langinfo(locale.ABMON_10) : '10', 78 locale.nl_langinfo(locale.ABMON_11) : '11', 79 locale.nl_langinfo(locale.ABMON_12) : '12'} 80 81 # This is not python 2.1 compatible (Nifty though) 82 # num2month = dict(zip(month2num.itervalues(), month2num)) 83 global num2month 84 num2month = {} 85 for month_abbr, month_num in month2num.items(): 86 num2month[month_num] = month_abbr 87 num2month[int(month_num)] = month_abbr 88 89 # all the valid month possibilities 90 global MONTHS 91 MONTHS = num2month.keys() + month2num.keys()
92 93 94
95 -def cleanup():
96 """ 97 Cleanup the tools module. 98 This should be called from Pyblosxom.pyblosxom.PyBlosxom.cleanup. 99 """ 100 global _loghandler_registry 101 # try: 102 # import logging 103 # if _use_custom_logger: 104 # raise ImportError, "whatever" 105 # except ImportError: 106 # import _logging as logging 107 108 try: 109 logging.shutdown() 110 _loghandler_registry = {} 111 except ValueError: 112 pass
113 114
115 -def parse_args(args):
116 """ 117 Takes in a list of args and parses it out into a hashmap of arg-name 118 to value(s). 119 120 @param args: the list of command-line arguments 121 @type args: list of strings 122 123 @return: list of tuples of (arg, value) pairings 124 @rtype: list of tuples of (string, string) 125 """ 126 i = 0 127 optlist = [] 128 while (i < len(args)): 129 if args[i].startswith("-"): 130 if (i+1 < len(args)): 131 if not args[i+1].startswith("-"): 132 optlist.append((args[i], args[i+1])) 133 i = i + 1 134 else: 135 optlist.append((args[i], "")) 136 else: 137 optlist.append((args[i], "")) 138 139 else: 140 optlist.append(("", args[i])) 141 142 i = i + 1 143 return optlist
144 145 146 147 QUOTES = {"'": "&apos;", '"': "&quot;"} 148
149 -def escape_text(s):
150 """ 151 Takes in a string and escapes \' to &apos; and \" to &quot;. 152 If s is None, then we return None. 153 154 @param s: the input string to escape 155 @type s: string 156 157 @returns: the escaped string 158 @rtype: string 159 """ 160 if s == None: 161 return None 162 return escape(s, QUOTES)
163
164 -def urlencode_text(s):
165 """ 166 Calls urllib.quote on the string. If is None, then we return 167 None. 168 169 @param s: the string to be urlencoded. 170 @type s: string 171 172 @returns: the urlencoded string 173 @rtype: string 174 """ 175 if s == None: 176 return None 177 return urllib.quote(s)
178 179
180 -class VariableDict:
181 """ 182 Wraps around a standard dict allowing for escaped and urlencoding 183 of internal data by tacking on a _urlencoded or a _escaped 184 to the end of the key name. 185 """
186 - def __init__(self):
187 """ 188 Initializes the internal dict. 189 """ 190 self._dict = {}
191
192 - def __getitem__(self, key, default=None):
193 """ 194 If the key ends with _escaped, then this will retrieve 195 the value for the key and escape it. 196 197 If the key ends with _urlencoded, then this will retrieve 198 the value for the key and urlencode it. 199 200 Otherwise, this calls get(key, default) on the wrapped 201 dict. 202 203 @param key: the key to retrieve 204 @type key: string 205 206 @param default: the default value to use if the key doesn't 207 exist. 208 @type default: string 209 210 @returns: the value; escaped if the key ends in _escaped; 211 urlencoded if the key ends in _urlencoded. 212 @rtype: string 213 """ 214 if key.endswith("_escaped"): 215 key = key[:-8] 216 return escape_text(self._dict.get(key, default)) 217 218 if key.endswith("_urlencoded"): 219 key = key[:-11] 220 return urlencode_text(self._dict.get(key, default)) 221 222 return self._dict.get(key, default)
223
224 - def get(self, key, default=None):
225 """ 226 This turns around and calls __getitem__(key, default). 227 228 @param key: the key to retrieve 229 @type key: string 230 231 @param default: the default value to use if the key doesn't exist. 232 @type default: string 233 234 @returns: __getitem__(key, default) 235 @rtype: string 236 """ 237 return self.__getitem__(key, default)
238
239 - def __setitem__(self, key, value):
240 """ 241 This calls __setitem__(key, value) on the wrapped dict. 242 243 @param key: the key 244 @type key: string 245 246 @param value: the value 247 @type value: string 248 """ 249 self._dict.__setitem__(key, value)
250
251 - def update(self, newdict):
252 """ 253 This calls update(newdict) on the wrapped dict. 254 """ 255 self._dict.update(newdict)
256
257 - def has_key(self, key):
258 """ 259 If the key ends with _encoded or _urlencoded, we strip that off 260 and then check the wrapped dict to see if it has the adjusted key. 261 262 Otherwise we call has_key(key) on the wrapped dict. 263 264 @param key: the key to check for 265 @type key: string 266 267 @returns: 1 if the key exists, 0 if not 268 @rtype: boolean 269 """ 270 if key.endswith("_encoded"): 271 key = key[:-8] 272 273 if key.endswith("_urlencoded"): 274 key = key[:-11] 275 276 return self._dict.has_key(key)
277
278 - def keys(self):
279 """ 280 Returns a list of the keys that can be accessed through 281 __getitem__. 282 283 Note: this does not include the _encoded and _urlencoded 284 versions of these keys. 285 286 @returns: list of key names 287 @rtype: list of varies 288 """ 289 return self._dict.keys()
290
291 - def values(self):
292 """ 293 Returns a list of the values in this dict. 294 295 @returns: list of values 296 @rtype: list of strings 297 """ 298 return self._dict.values()
299
300 -class Stripper(sgmllib.SGMLParser):
301 """ 302 SGMLParser that removes HTML formatting code. 303 """
304 - def __init__(self):
305 """ 306 Initializes the instance. 307 """ 308 self.data = [] 309 sgmllib.SGMLParser.__init__(self)
310
311 - def unknown_starttag(self, tag, attrs):
312 """ 313 Implements unknown_starttag. Appends a space to the buffer. 314 """ 315 self.data.append(" ")
316
317 - def unknown_endtag(self, tag):
318 """ 319 Implements unknown_endtag. Appends a space to the buffer. 320 """ 321 self.data.append(" ") 322
323 - def handle_data(self, data):
324 """ 325 Implements handle_data. Appends data to the buffer. 326 """ 327 self.data.append(data) 328
329 - def gettext(self):
330 """ 331 Returns the buffer. 332 """ 333 return "".join(self.data) 334 335
336 -class Replacer:
337 """ 338 Class for replacing variables in a template 339 340 This class is a utility class used to provide a bound method to the 341 C{re.sub()} function. Gotten from OPAGCGI. 342 """
343 - def __init__(self, request, encoding, var_dict):
344 """ 345 Its only duty is to populate itself with the replacement dictionary 346 passed. 347 348 @param request: the Request object 349 @type request: Request 350 351 @param encoding: the encoding to use 352 @type encoding: string 353 354 @param var_dict: The dict for variable substitution 355 @type var_dict: dict 356 """ 357 self._request = request 358 self._encoding = encoding 359 self.var_dict = var_dict
360
361 - def replace(self, matchobj):
362 """ 363 The replacement method. 364 365 This is passed a match object by re.sub(), which it uses to index the 366 replacement dictionary and find the replacement string. 367 368 @param matchobj: A C{re} object containing substitutions 369 @type matchobj: C{re} object 370 371 @returns: Substitutions 372 @rtype: string 373 """ 374 request = self._request 375 key = matchobj.group(1) 376 377 if key.find("(") != -1 and key.find(")"): 378 args = key[key.find("(")+1:key.rfind(")")] 379 key = key[:key.find("(")] 380 381 else: 382 args = None 383 384 if self.var_dict.has_key(key): 385 r = self.var_dict[key] 386 387 # if the value turns out to be a function, then we call it 388 # with the args that we were passed. 389 if callable(r): 390 # FIXME - security issue here because we're using eval. 391 # course, the things it allows us to do can be done using 392 # plugins much more easily--so it's kind of a moot point. 393 if args: 394 r = eval("r(request, " + args + ")") 395 else: 396 r = r() 397 398 if not isinstance(r, str) and not isinstance(r, unicode): 399 r = str(r) 400 401 if not isinstance(r, unicode): 402 # convert strings to unicode, assumes strings in iso-8859-1 403 r = unicode(r, self._encoding, 'replace') 404 405 return r 406 407 else: 408 return u''
409
410 -def parse(request, encoding, var_dict, template):
411 """ 412 This method parses the open file object passed, replacing any keys 413 found using the replacement dictionary passed. Uses the L{Replacer} 414 object. From OPAGCGI library 415 416 @param request: the Request object 417 @type request: Request 418 419 @param encoding: the encoding to use 420 @type encoding: string 421 422 @param var_dict: The name value pair list containing variable replacements 423 @type var_dict: dict 424 425 @param template: A template file with placeholders for variable 426 replacements 427 @type template: string 428 429 @returns: Substituted template 430 @rtype: string 431 """ 432 if not isinstance(template, unicode): 433 # convert strings to unicode, assumes strings in iso-8859-1 434 template = unicode(template, encoding, 'replace') 435 436 replacer = Replacer(request, encoding, var_dict) 437 438 return u'' + VAR_REGEXP.sub(replacer.replace, template)
439 440
441 -def walk( request, root='.', recurse=0, pattern='', return_folders=0 ):
442 """ 443 This function walks a directory tree starting at a specified root folder, 444 and returns a list of all of the files (and optionally folders) that match 445 our pattern(s). Taken from the online Python Cookbook and modified to own 446 needs. 447 448 It will look at the config "ignore_directories" for a list of 449 directories to ignore. It uses a regexp that joins all the things 450 you list. So the following:: 451 452 config.py["ignore_directories"] = ["CVS", "dev/pyblosxom"] 453 454 turns into the regexp:: 455 456 .*?(CVS|dev/pyblosxom)$ 457 458 It will also skip all directories that start with a period. 459 460 @param request: the Request object 461 @type request: Request 462 463 @param root: Starting point to walk from 464 @type root: string 465 466 @param recurse: Depth of recursion, 467 - 0: All the way 468 - 1: Just this level 469 - I{n}: I{n} depth of recursion 470 @type recurse: integer 471 472 @param pattern: A C{re.compile}'d object 473 @type pattern: object 474 475 @param return_folders: If true, just return list of folders 476 @type return_folders: boolean 477 478 @returns: A list of file paths 479 @rtype: list 480 """ 481 # expand pattern 482 if not pattern: 483 ext = request.getData()['extensions'] 484 pattern = re.compile(r'.*\.(' + '|'.join(ext.keys()) + r')$') 485 486 ignore = request.getConfiguration().get("ignore_directories", None) 487 if isinstance(ignore, str): 488 ignore = [ignore] 489 490 if ignore: 491 ignore = map(re.escape, ignore) 492 ignorere = re.compile(r'.*?(' + '|'.join(ignore) + r')$') 493 else: 494 ignorere = None 495 496 # must have at least root folder 497 try: 498 os.listdir(root) 499 except os.error: 500 return [] 501 502 return __walk_internal(root, recurse, pattern, ignorere, return_folders)
503 504 # we do this for backwards compatibility reasons. 505 Walk = walk 506
507 -def __walk_internal( root, recurse, pattern, ignorere, return_folders ):
508 """ 509 Note: This is an internal function--don't use it and don't expect it to 510 stay the same between PyBlosxom releases. 511 """ 512 # initialize 513 result = [] 514 515 try: 516 names = os.listdir(root) 517 except: 518 return [] 519 520 # check each file 521 for name in names: 522 fullname = os.path.normpath(os.path.join(root, name)) 523 524 # grab if it matches our pattern and entry type 525 if pattern.match(name): 526 if (os.path.isfile(fullname) and not return_folders) or \ 527 (return_folders and os.path.isdir(fullname) and \ 528 (not ignorere or not ignorere.match(fullname))): 529 result.append(fullname) 530 531 # recursively scan other folders, appending results 532 if (recurse == 0) or (recurse > 1): 533 if name[0] != "." and os.path.isdir(fullname) and \ 534 not os.path.islink(fullname) and \ 535 (not ignorere or not ignorere.match(fullname)): 536 result = result + \ 537 __walk_internal(fullname, 538 (recurse > 1 and recurse - 1 or 0), 539 pattern, ignorere, return_folders) 540 541 return result
542 543
544 -def filestat(request, filename):
545 """ 546 Returns the filestat on a given file. We store the filestat 547 in case we've already retrieved it this time. 548 549 @param request: the Pyblosxom Request object 550 @type request: Request 551 552 @param filename: the name of the file to stat 553 @type filename: string 554 555 @returns: the mtime of the file (same as returned by time.localtime(...)) 556 @rtype: tuple of 9 ints 557 """ 558 data = request.getData() 559 filestat_cache = data.setdefault("filestat_cache", {}) 560 561 if filestat_cache.has_key(filename): 562 return filestat_cache[filename] 563 564 argdict = { "request": request, 565 "filename": filename, 566 "mtime": (0,) * 10 } 567 568 MT = stat.ST_MTIME 569 570 argdict = run_callback("filestat", 571 argdict, 572 mappingfunc = lambda x,y:y, 573 donefunc = lambda x:x and x["mtime"][MT] != 0, 574 defaultfunc = lambda x:x) 575 576 # no plugin handled cb_filestat; we default to asking the 577 # filesystem 578 if argdict["mtime"][MT] == 0: 579 argdict["mtime"] = os.stat(filename) 580 581 timetuple = time.localtime(argdict["mtime"][MT]) 582 filestat_cache[filename] = timetuple 583 584 return timetuple 585 586
587 -def what_ext(extensions, filepath):
588 """ 589 Takes in a filepath and a list of extensions and tries them all until 590 it finds the first extension that works. 591 592 @param extensions: the list of extensions to test 593 @type extensions: list of strings 594 595 @param filepath: the complete file path (minus the extension) to test 596 @type filepath: string 597 598 @return: the extension that was successful or None 599 @rtype: string 600 """ 601 for ext in extensions: 602 if os.path.isfile(filepath + '.' + ext): 603 return ext 604 return None
605 606
607 -def is_year(checks):
608 """ 609 Checks to see if the string is likely to be a year or not. In order to 610 be considered to be a year, it must pass the following criteria: 611 612 1. four digits 613 2. first two digits are either 19 or 20. 614 615 @param checks: the string to check for "year-hood" 616 @type checks: string 617 618 @return: 1 if checks is likely to be a year or 0 if it is not 619 @rtype: boolean 620 """ 621 if not checks: 622 return 0 623 624 if len(checks) == 4 and checks.isdigit() and \ 625 (checks.startswith("19") or checks.startswith("20")): 626 return 1 627 return 0
628 629
630 -def importname(modulename, name):
631 """ 632 Imports modules for modules that can only be determined during 633 runtime. 634 635 @param modulename: The base name of the module to import from 636 @type modulename: string 637 638 @param name: The name of the module to import from the modulename 639 @type name: string 640 641 @returns: If successful, returns an imported object reference, else 642 C{None} 643 @rtype: object 644 """ 645 try: 646 module = __import__(modulename, globals(), locals(), [name]) 647 except ImportError: 648 return None 649 try: 650 return vars(module)[name] 651 except: 652 return None
653 654
655 -def generateRandStr(minlen=5, maxlen=10):
656 """ 657 Generate a random string 658 659 Tool to generate a random string between C{minlen} to C{maxlen} 660 characters. 661 662 @param minlen: The minimum length the string should be 663 @type minlen: integer 664 665 @param maxlen: The maximum length the string could be 666 @type maxlen: integer 667 668 @returns: A string containing random characters 669 @rtype: string 670 """ 671 import random, string 672 chars = string.letters + string.digits 673 randstr = [] 674 randstr_size = random.randint(minlen, maxlen) 675 x = 0 676 while x < randstr_size: 677 randstr.append(random.choice(chars)) 678 x += 1 679 return "".join(randstr)
680 681
682 -def run_callback(chain, input, 683 mappingfunc = lambda x,y : x, 684 donefunc = lambda x : 0, 685 defaultfunc = None):
686 """ 687 Executes a callback chain on a given piece of data. 688 passed in is a dict of name/value pairs. Consult the documentation 689 for the specific callback chain you're executing. 690 691 Callback chains should conform to their documented behavior. 692 This function allows us to do transforms on data, handling data, 693 and also callbacks. 694 695 The difference in behavior is affected by the mappingfunc passed 696 in which converts the output of a given function in the chain 697 to the input for the next function. 698 699 If this is confusing, read through the code for this function. 700 701 @param chain: the callback chain to run 702 @type chain: string 703 704 @param input: data is a dict filled with name/value pairs--refer 705 to the callback chain documentation for what's in the data 706 dict. 707 @type input: dict 708 709 @param mappingfunc: the function that maps output arguments 710 to input arguments for the next iteration. It must take 711 two arguments: the original dict and the return from the 712 previous function. It defaults to returning the original 713 dict. 714 @type mappingfunc: function 715 716 @param donefunc: this function tests whether we're done doing 717 what we're doing. This function takes as input the output 718 of the most recent iteration. If this function returns 719 true (1) then we'll drop out of the loop. For example, 720 if you wanted a callback to stop running when one of the 721 registered functions returned a 1, then you would pass in: 722 donefunc=lambda x:x . 723 @type donefunc: function 724 725 @param defaultfunc: if this is set and we finish going through all 726 the functions in the chain and none of them have returned something 727 that satisfies the donefunc, then we'll execute the defaultfunc 728 with the latest version of the input dict. 729 @type defaultfunc: function 730 731 @returns: the transformed dict 732 @rtype: dict 733 """ 734 chain = plugin_utils.get_callback_chain(chain) 735 736 output = None 737 738 for func in chain: 739 # we call the function with the input dict it returns an output. 740 output = func(input) 741 742 # we fun the output through our donefunc to see if we should stop 743 # iterating through the loop. the donefunc should return a 1 744 # if we're done--all other values cause us to continue. 745 if donefunc(output) == 1: 746 break 747 748 # we pass the input we just used and the output we just got 749 # into the mappingfunc which will give us the input for the 750 # next iteration. in most cases, this consists of either 751 # returning the old input or the old output--depending on 752 # whether we're transforming the data through the chain or not. 753 input = mappingfunc(input, output) 754 755 # if we have a defaultfunc and we haven't satisfied the donefunc 756 # conditions, then we return whatever the defaultfunc returns 757 # when given the current version of the input. 758 if callable(defaultfunc) and donefunc(output) != 1: 759 return defaultfunc(input) 760 761 # we didn't call the defaultfunc--so we return the most recent 762 # output. 763 return output
764 765
766 -def create_entry(datadir, category, filename, mtime, title, metadata, body):
767 """ 768 Creates a new entry in the blog. 769 770 This is primarily used by the testing system, but it could be 771 used by scripts and other tools. 772 773 @param datadir: the directory of the datadir where the blog 774 entries are stored. 775 @type datadir: string 776 777 @param category: the category of the entry. 778 @type category: string 779 780 @param filename: the name of the blog entry (filename and extension). 781 @type filename: string 782 783 @param mtime: the mtime for the entry (seconds since the epoch). 784 @type mtime: float 785 786 @param title: the title for the entry. 787 @type title: string 788 789 @param metadata: any metadata for this entry. 790 @type metadata: dict 791 792 @param body: the content of the entry. 793 @type body: string 794 795 @raises IOError: if the datadir + category directory exists, but isn't 796 a directory. 797 """ 798 799 def addcr(s): 800 if not s.endswith("\n"): 801 return s + "\n" 802 return s
803 804 # format the metadata lines for the entry 805 metadatalines = ["#%s %s" % (key, metadata[key]) 806 for key in metadata.keys()] 807 808 entry = addcr(title) + "\n".join(metadatalines) + body 809 810 # create the category directories 811 d = os.path.join(datadir, category) 812 if not os.path.exists(d): 813 os.makedirs(d) 814 815 if not os.path.isdir(d): 816 raise IOError("%s exists, but isn't a directory." % d) 817 818 # create the filename 819 fn = os.path.join(datadir, category, filename) 820 821 # write the entry to disk 822 f = open(fn, "w") 823 f.write(entry) 824 f.close() 825 826 # set the mtime on the entry 827 os.utime(fn, (mtime, mtime)) 828
829 -def get_cache(request):
830 """ 831 Retrieves the cache from the request or fetches a new CacheDriver 832 instance. 833 834 @param request: the Request object for this run 835 @type request: Request 836 837 @returns: A BlosxomCache object reference 838 @rtype: L{Pyblosxom.cache.base.BlosxomCacheBase} subclass 839 """ 840 data = request.getData() 841 mycache = data.get("data_cache", "") 842 843 if not mycache: 844 config = request.getConfiguration() 845 846 cache_driver_config = config.get('cacheDriver', 'base') 847 cache_config = config.get('cacheConfig', '') 848 849 cache_driver = importname('Pyblosxom.cache', cache_driver_config) 850 mycache = cache_driver.BlosxomCache(request, cache_config) 851 852 data["data_cache"] = mycache 853 854 return mycache
855 856
857 -def update_static_entry(cdict, entry_filename):
858 """ 859 This is a utility function that allows plugins to easily update 860 statically rendered entries without going through all the rigamarole. 861 862 First we figure out whether this blog is set up for static rendering. 863 If not, then we return--no harm done. 864 865 If we are, then we call render_url for each static_flavour of the entry 866 and then for each static_flavour of the index page. 867 868 @param cdict: the config.py dict 869 @type cdict: dict 870 871 @param entry_filename: the filename of the entry (ex. /movies/xmen2) 872 @type entry_filename: string 873 """ 874 staticdir = cdict.get("static_dir", "") 875 876 if not staticdir: 877 return 878 879 staticflavours = cdict.get("static_flavours", ["html"]) 880 881 renderme = [] 882 for mem in staticflavours: 883 renderme.append( "/index" + "." + mem, "" ) 884 renderme.append( entry_filename + "." + mem, "" ) 885 886 for mem in renderme: 887 render_url_statically(cdict, mem[0], mem[1])
888 889
890 -def render_url_statically(cdict, url, q):
891 staticdir = cdict.get("static_dir", "") 892 893 response = render_url(cdict, url, q) 894 response.seek(0) 895 896 fn = os.path.normpath(staticdir + os.sep + url) 897 if not os.path.isdir(os.path.dirname(fn)): 898 os.makedirs(os.path.dirname(fn)) 899 900 # by using the response object the cheesy part of removing 901 # the HTTP headers from the file is history. 902 f = open(fn, "w") 903 f.write(response.read()) 904 f.close()
905
906 -def render_url(cdict, pathinfo, querystring=""):
907 """ 908 Takes a url and a querystring and renders the page that corresponds 909 with that by creating a Request and a PyBlosxom object and passing 910 it through. It then returns the resulting Response. 911 912 @param cdict: the config.py dict 913 @type cdict: dict 914 915 @param pathinfo: the path_info string. ex: /dev/pyblosxom/firstpost.html 916 @type pathinfo: string 917 918 @param querystring: the querystring (if any). ex: debug=yes 919 @type querystring: string 920 921 @returns: Response 922 """ 923 staticdir = cdict.get("static_dir", "") 924 925 # if there is no staticdir, then they're not set up for static 926 # rendering. 927 if not staticdir: 928 raise Exception("You must set static_dir in your config file.") 929 930 from pyblosxom import PyBlosxom 931 932 env = { 933 "HTTP_USER_AGENT": "static renderer", 934 "REQUEST_METHOD": "GET", 935 "HTTP_HOST": "localhost", 936 "PATH_INFO": pathinfo, 937 "QUERY_STRING": querystring, 938 "REQUEST_URI": pathinfo + "?" + querystring, 939 "PATH_INFO": pathinfo, 940 "HTTP_REFERER": "", 941 "REMOTE_ADDR": "", 942 "SCRIPT_NAME": "", 943 "wsgi.errors": sys.stderr, 944 "wsgi.input": None 945 } 946 data = {"STATIC": 1} 947 p = PyBlosxom(cdict, env, data) 948 p.run(static=True) 949 return p.getResponse()
950 951 952 953 #****************************** 954 # Logging 955 #****************************** 956 957 # If you have Python >=2.3 and want to use/test the custom logging 958 # implementation set this flag to True. 959 _use_custom_logger = False 960 961 try: 962 import logging 963 if _use_custom_logger: 964 raise ImportError, "whatever" 965 except ImportError: 966 import _logging as logging 967 968 # A dict to keep track of created log handlers. 969 # Used to prevent multiple handlers from beeing added to the same logger. 970 _loghandler_registry = {} 971 972
973 -class LogFilter(object):
974 """ 975 Filters out messages from log-channels that are not listed in the 976 log_filter config variable. 977 """
978 - def __init__(self, names=None):
979 """ 980 Initializes the filter to the list provided by the names 981 argument (or [] if names is None). 982 983 @param names: list of name strings to filter out 984 @type names: list of strings 985 """ 986 if names == None: 987 names = [] 988 self.names = names
989
990 - def filter(self, record):
991 if record.name in self.names: 992 return 1 993 return 0
994
995 -def getLogger(log_file=None):
996 """ 997 Creates and retuns a log channel. 998 If no log_file is given the system-wide logfile as defined in config.py 999 is used. If a log_file is given that's where the created logger logs to. 1000 1001 @param log_file: optional, the file to log to. 1002 @type log_file: C{str} 1003 1004 @return: a log channel (Logger instance) 1005 @rtype: L{logging.Logger} for Python >=2.3, 1006 L{Pyblosxom._logging.Logger} for Python <2.3 1007 """ 1008 custom_log_file = False 1009 if log_file == None: 1010 log_file = _config.get('log_file', 'stderr') 1011 f = sys._getframe(1) 1012 filename = f.f_code.co_filename 1013 module = f.f_globals["__name__"] 1014 # by default use the root logger 1015 log_name = "" 1016 for path in _config.get('plugin_dirs', []): 1017 if filename.startswith(path): 1018 # if it's a plugin, use the module name as the log channels 1019 # name 1020 log_name = module 1021 break 1022 # default to log level WARNING if it's not defined in config.py 1023 log_level = _config.get('log_level', 'warning') 1024 else: 1025 # handle custom log_file 1026 custom_log_file = True 1027 # figure out a name for the log channel 1028 log_name = os.path.splitext(os.path.basename(log_file))[0] 1029 # assume log_level debug (show everything) 1030 log_level = "debug" 1031 1032 global _loghandler_registry 1033 1034 # get the logger for this channel 1035 logger = logging.getLogger(log_name) 1036 # don't propagate messages up the logger hierarchy 1037 logger.propagate = 0 1038 1039 # setup the handler if it doesn't allready exist. 1040 # only add one handler per log channel. 1041 key = "%s|%s" % (log_file, log_name) 1042 if not key in _loghandler_registry: 1043 1044 # create the handler 1045 if log_file == "stderr": 1046 hdlr = logging.StreamHandler(sys.stderr) 1047 else: 1048 if log_file == "NONE": # user disabled logging 1049 if os.name == 'nt': # windoze 1050 log_file = "NUL" 1051 else: # assume *nix 1052 log_file = "/dev/null" 1053 try: 1054 hdlr = logging.FileHandler(log_file) 1055 except IOError: 1056 # couldn't open logfile, fallback to stderr 1057 hdlr = logging.StreamHandler(sys.stderr) 1058 1059 # create and set the formatter 1060 if log_name: 1061 fmtr_s = '%(asctime)s [%(levelname)s] %(name)s: %(message)s' 1062 else: # root logger 1063 fmtr_s = '%(asctime)s [%(levelname)s]: %(message)s' 1064 1065 hdlr.setFormatter(logging.Formatter(fmtr_s)) 1066 1067 logger.addHandler(hdlr) 1068 int_level = getattr(logging, log_level.upper()) 1069 logger.setLevel(int_level) 1070 1071 if not custom_log_file: 1072 # only log messages from plugins listed in log_filter. 1073 # add 'root' to the log_filter list to still allow application 1074 # level messages. 1075 log_filter = _config.get('log_filter', None) 1076 if log_filter: 1077 lfilter = LogFilter(log_filter) 1078 logger.addFilter(lfilter) 1079 1080 # remember that we've seen this handler 1081 _loghandler_registry[key] = True 1082 1083 return logger
1084 1085
1086 -def log_exception(log_file=None):
1087 """ 1088 Logs an exception to the given file. 1089 Uses the system-wide log_file as defined in config.py if none 1090 is given here. 1091 1092 @param log_file: optional, the file to log to 1093 @type log_file: C{str} 1094 """ 1095 log = getLogger(log_file) 1096 log.exception("Exception occured:")
1097 1098
1099 -def log_caller(frame_num=1, log_file=None):
1100 """ 1101 Logs some info about the calling function/method. 1102 Useful for debugging. 1103 1104 Usage:: 1105 import tools 1106 tools.log_caller() # logs frame 1 1107 tools.log_caller(2) 1108 tools.log_caller(3, log_file="/path/to/file") 1109 1110 @param frame_num: optional, index of the frame 1111 @type frame_num: C{int} 1112 1113 @param log_file: optional, the file to log to 1114 @type log_file: C{str} 1115 """ 1116 f = sys._getframe(frame_num) 1117 module = f.f_globals["__name__"] 1118 filename = f.f_code.co_filename 1119 line = f.f_lineno 1120 subr = f.f_code.co_name 1121 1122 log = getLogger(log_file) 1123 log.info("\n module: %s\n filename: %s\n line: %s\n subroutine: %s", 1124 module, filename, line, subr)
1125 1126 1127 1128 # %<------------------------- 1129 # BEGIN portalocking block from Python Cookbook. 1130 # LICENSE is located in docs/LICENSE.portalocker. 1131 # It's been modified for use in Pyblosxom. 1132 1133 # """Cross-platform (posix/nt) API for flock-style file locking. 1134 # 1135 # Synopsis: 1136 # 1137 # import portalocker 1138 # file = open("somefile", "r+") 1139 # portalocker.lock(file, portalocker.LOCK_EX) 1140 # file.seek(12) 1141 # file.write("foo") 1142 # file.close() 1143 # 1144 # If you know what you're doing, you may choose to 1145 # 1146 # portalocker.unlock(file) 1147 # 1148 # before closing the file, but why? 1149 # 1150 # Methods: 1151 # 1152 # lock( file, flags ) 1153 # unlock( file ) 1154 # 1155 # Constants: 1156 # 1157 # LOCK_EX 1158 # LOCK_SH 1159 # LOCK_NB 1160 # 1161 # I learned the win32 technique for locking files from sample code 1162 # provided by John Nielsen <nielsenjf@my-deja.com> in the documentation 1163 # that accompanies the win32 modules. 1164 # 1165 # Author: Jonathan Feinberg <jdf@pobox.com> 1166 # Version: $Id: tools.py 1061 2007-06-24 17:44:31Z willhelm $ 1167 1168 if os.name == 'nt': 1169 import win32con 1170 import win32file 1171 import pywintypes 1172 LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK 1173 LOCK_SH = 0 # the default 1174 LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY 1175 # is there any reason not to reuse the following structure? 1176 __overlapped = pywintypes.OVERLAPPED() 1177 elif os.name == 'posix': 1178 import fcntl 1179 LOCK_EX = fcntl.LOCK_EX 1180 LOCK_SH = fcntl.LOCK_SH 1181 LOCK_NB = fcntl.LOCK_NB 1182 else: 1183 raise RuntimeError("PortaLocker only defined for nt and posix platforms") 1184 1185 if os.name == 'nt':
1186 - def lock(f, flags):
1187 hfile = win32file._get_osfhandle(f.fileno()) 1188 win32file.LockFileEx(hfile, flags, 0, 0xffff0000, __overlapped)
1189
1190 - def unlock(f):
1191 hfile = win32file._get_osfhandle(f.fileno()) 1192 win32file.UnlockFileEx(hfile, 0, 0xffff0000, __overlapped)
1193 1194 elif os.name =='posix':
1195 - def lock(f, flags):
1196 fcntl.flock(f.fileno(), flags)
1197
1198 - def unlock(f):
1199 fcntl.flock(f.fileno(), fcntl.LOCK_UN)
1200 1201 # END portalocking block from Python Cookbook. 1202 # %<------------------------- 1203