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

Source Code for Module Pyblosxom.pyblosxom

   1  from __future__ import nested_scopes, generators 
   2  ####################################################################### 
   3  # This file is part of PyBlosxom. 
   4  # 
   5  # Copyright (c) 2003, 2004, 2005, 2006 Wari Wahab 
   6  #  
   7  # PyBlosxom is distributed under the MIT license.  See the file LICENSE 
   8  # for distribution details. 
   9  # 
  10  # $Id: pyblosxom.py 1068 2007-07-02 18:26:02Z willhelm $ 
  11  ####################################################################### 
  12  """ 
  13  This is the main module for PyBlosxom functionality.  PyBlosxom's setup  
  14  and default handlers are defined here. 
  15  """ 
  16   
  17  __revision__ = "$Revision: 1068 $" 
  18   
  19   
  20   
  21  # Python imports 
  22  import os 
  23  import time 
  24  import re 
  25  import locale 
  26  import sys 
  27  import os.path 
  28  import cgi 
  29  try:  
  30      from cStringIO import StringIO 
  31  except ImportError:  
  32      from StringIO import StringIO 
  33   
  34  # Pyblosxom imports 
  35  import tools 
  36  import plugin_utils 
  37  from entries.fileentry import FileEntry 
  38   
  39  VERSION = "1.4" 
  40  VERSION_DATE = VERSION + " 7/2/2007" 
  41  VERSION_SPLIT = tuple(VERSION.split('.')) 
  42   
  43   
  44   
45 -class PyBlosxom:
46 """ 47 This is the main class for PyBlosxom functionality. It handles 48 initialization, defines default behavior, and also pushes the 49 request through all the steps until the output is rendered and 50 we're complete. 51 """
52 - def __init__(self, config, environ, data=None):
53 """ 54 Sets configuration and environment. 55 Creates the L{Request} object. 56 57 @param config: A dict containing the configuration variables. 58 @type config: dict 59 60 @param environ: A dict containing the environment variables. 61 @type environ: dict 62 63 @param data: A dict containing data variables. 64 @type data: dict 65 """ 66 config['pyblosxom_name'] = "pyblosxom" 67 config['pyblosxom_version'] = VERSION_DATE 68 69 # wbg 10/6/2005 - the plugin changes won't happen until 70 # PyBlosxom 1.4. so i'm commenting this out until then. 71 # add the included plugins directory 72 # p = config.get("plugin_dirs", []) 73 # f = __file__[:__file__.rfind(os.sep)] + os.sep + "plugins" 74 # p.append(f) 75 # config['plugin_dirs'] = p 76 77 self._config = config 78 self._request = Request(config, environ, data)
79
80 - def initialize(self):
81 """ 82 The initialize step further initializes the Request by setting 83 additional information in the _data dict, registering plugins, 84 and entryparsers. 85 """ 86 data = self._request.getData() 87 pyhttp = self._request.getHttp() 88 config = self._request.getConfiguration() 89 90 # Initialize the locale, if wanted (will silently fail if locale 91 # is not # available) 92 if config.get('locale'): 93 try: 94 locale.setlocale(locale.LC_ALL, config['locale']) 95 except locale.Error: 96 # Invalid locale 97 pass 98 99 # initialize the tools module 100 tools.initialize(config) 101 102 data["pyblosxom_version"] = VERSION_DATE 103 data['pi_bl'] = '' 104 105 # Get our URL and configure the base_url param 106 if pyhttp.has_key('SCRIPT_NAME'): 107 if not config.has_key('base_url'): 108 # allow http and https 109 config['base_url'] = '%s://%s%s' % \ 110 (pyhttp['wsgi.url_scheme'], 111 pyhttp['HTTP_HOST'], 112 pyhttp['SCRIPT_NAME']) 113 else: 114 config['base_url'] = config.get('base_url', '') 115 116 # take off the trailing slash for base_url 117 if config['base_url'].endswith("/"): 118 config['base_url'] = config['base_url'][:-1] 119 120 datadir = config["datadir"] 121 if datadir.endswith("/") or datadir.endswith("\\"): 122 datadir = datadir[:-1] 123 config['datadir'] = datadir 124 125 # import and initialize plugins 126 plugin_utils.initialize_plugins(config.get("plugin_dirs", []), 127 config.get("load_plugins", None)) 128 129 # entryparser callback is run here first to allow other plugins 130 # register what file extensions can be used 131 data['extensions'] = tools.run_callback("entryparser", 132 {'txt': blosxom_entry_parser}, 133 mappingfunc=lambda x,y:y, 134 defaultfunc=lambda x:x)
135
136 - def cleanup(self):
137 """ 138 This cleans up PyBlosxom after a run. Mostly it calls 139 tools.cleanup which in turn shuts down the logging. 140 141 This should be called when Pyblosxom has done all its work 142 right before exiting. 143 """ 144 # log some useful stuff for debugging 145 # this will only be logged if the log_level is "debug" 146 log = tools.getLogger() 147 response = self.getResponse() 148 log.debug("status = %s" % response.status) 149 log.debug("headers = %s" % response.headers) 150 151 tools.cleanup()
152
153 - def getRequest(self):
154 """ 155 Returns the L{Request} object for this PyBlosxom instance. 156 157 @returns: the request object 158 @rtype: L{Request} 159 """ 160 return self._request
161
162 - def getResponse(self):
163 """ 164 Returns the L{Response} object which handles all output 165 related functionality for this PyBlosxom instance. 166 167 @see: L{Response} 168 169 @returns: the reponse object 170 @rtype: L{Response} 171 """ 172 return self._request.getResponse()
173
174 - def run(self, static=False):
175 """ 176 This is the main loop for PyBlosxom. This method will run the 177 handle callback to allow registered handlers to handle the request. 178 If nothing handles the request, then we use the 179 default_blosxom_handler. 180 181 @param static: True if we should execute in "static rendering mode" 182 and False otherwise 183 @type static: boolean 184 """ 185 self.initialize() 186 187 # buffer the input stream in a StringIO instance if dynamic rendering 188 # is used. This is done to have a known/consistent way of accessing 189 # incomming data. 190 if static == False: 191 self.getRequest().buffer_input_stream() 192 193 # run the start callback 194 tools.run_callback("start", {'request': self._request}) 195 196 # allow anyone else to handle the request at this point 197 handled = tools.run_callback("handle", 198 {'request': self._request}, 199 mappingfunc=lambda x,y:x, 200 donefunc=lambda x:x) 201 202 if not handled == 1: 203 blosxom_handler(self._request) 204 205 # do end callback 206 tools.run_callback("end", {'request': self._request}) 207 208 # we're done, clean up. 209 # only call this if we're not in static rendering mode. 210 if static == False: 211 self.cleanup()
212
213 - def runCallback(self, callback="help"):
214 """ 215 This method executes the start callback (initializing plugins), 216 executes the requested callback, and then executes the end 217 callback. This is useful for scripts outside of PyBlosxom that 218 need to do things inside of the PyBlosxom framework. 219 220 @param callback: the callback to execute 221 @type callback: string 222 223 @returns: the result of calling the callback 224 @rtype: varies 225 """ 226 self.initialize() 227 228 # run the start callback 229 tools.run_callback("start", {'request': self._request}) 230 231 # invoke all callbacks for the 'callback' 232 handled = tools.run_callback(callback, 233 {'request': self._request}, 234 mappingfunc=lambda x,y:x, 235 donefunc=lambda x:x) 236 237 # do end callback 238 tools.run_callback("end", {'request': self._request}) 239 240 return handled
241
242 - def runRenderOne(self, url, headers):
243 """ 244 Renders a single page from the blog. 245 246 @param url: the url to render--this has to be relative to 247 PyBlosxom 248 @type url: string 249 250 @param headers: 1 if you want the headers rendered and 0 if not. 251 @type headers: int 252 """ 253 self.initialize() 254 255 config = self._request.getConfiguration() 256 data = self._request.getData() 257 258 if url.find("?") != -1: 259 url = url[:url.find("?")] 260 query = url[url.find("?")+1:] 261 else: 262 query = "" 263 264 url = url.replace(os.sep, "/") 265 response = tools.render_url(config, url, query) 266 response.sendHeaders(sys.stdout) 267 response.sendBody(sys.stdout) 268 269 print response.read() 270 271 # we're done, clean up 272 self.cleanup()
273
274 - def runStaticRenderer(self, incremental=0):
275 """ 276 This will go through all possible things in the blog and statically 277 render everything to the "static_dir" specified in the config file. 278 279 This figures out all the possible path_info settings and calls 280 self.run() a bazillion times saving each file. 281 282 @param incremental: whether (1) or not (0) to incrementally 283 render the pages. if we're incrementally rendering pages, 284 then we render only the ones that have changed. 285 @type incremental: boolean 286 """ 287 self.initialize() 288 289 config = self._request.getConfiguration() 290 data = self._request.getData() 291 print "Performing static rendering." 292 if incremental: 293 print "Incremental is set." 294 295 staticdir = config.get("static_dir", "") 296 datadir = config["datadir"] 297 298 if not staticdir: 299 raise Exception("You must set static_dir in your config file.") 300 301 flavours = config.get("static_flavours", ["html"]) 302 303 renderme = [] 304 305 monthnames = config.get("static_monthnames", 1) 306 monthnumbers = config.get("static_monthnumbers", 0) 307 308 dates = {} 309 categories = {} 310 311 # first we handle entries and categories 312 listing = tools.Walk(self._request, datadir) 313 314 for mem in listing: 315 # skip the ones that have bad extensions 316 ext = mem[mem.rfind(".")+1:] 317 if not ext in data["extensions"].keys(): 318 continue 319 320 # grab the mtime of the entry file 321 mtime = time.mktime(tools.filestat(self._request, mem)) 322 323 # remove the datadir from the front and the bit at the end 324 mem = mem[len(datadir):mem.rfind(".")] 325 326 # this is the static filename 327 fn = os.path.normpath(staticdir + mem) 328 329 # grab the mtime of one of the statically rendered file 330 try: 331 smtime = os.stat(fn + "." + flavours[0])[8] 332 except: 333 smtime = 0 334 335 # if the entry is more recent than the static, we want to 336 # re-render 337 if smtime < mtime or not incremental: 338 339 # grab the categories 340 temp = os.path.dirname(mem).split(os.sep) 341 for i in range(len(temp)+1): 342 p = os.sep.join(temp[0:i]) 343 categories[p] = 0 344 345 # grab the date 346 mtime = time.localtime(mtime) 347 year = time.strftime("%Y", mtime) 348 month = time.strftime("%m", mtime) 349 day = time.strftime("%d", mtime) 350 351 dates[year] = 1 352 353 if monthnumbers: 354 dates[year + "/" + month] = 1 355 dates[year + "/" + month + "/" + day] = 1 356 357 if monthnames: 358 monthname = tools.num2month[month] 359 dates[year + "/" + monthname] = 1 360 dates[year + "/" + monthname + "/" + day] = 1 361 362 # toss in the render queue 363 for f in flavours: 364 renderme.append( (mem + "." + f, "") ) 365 366 print "rendering %d entries." % len(renderme) 367 368 # handle categories 369 categories = categories.keys() 370 categories.sort() 371 372 # if they have stuff in their root category, it'll add a "/" 373 # to the category list and we want to remove that because it's 374 # a duplicate of "". 375 if "/" in categories: 376 categories.remove("/") 377 378 print "rendering %d category indexes." % len(categories) 379 380 for mem in categories: 381 mem = os.path.normpath( mem + "/index." ) 382 for f in flavours: 383 renderme.append( (mem + f, "") ) 384 385 # now we handle dates 386 dates = dates.keys() 387 dates.sort() 388 389 dates = ["/" + d for d in dates] 390 391 print "rendering %d date indexes." % len(dates) 392 393 for mem in dates: 394 mem = os.path.normpath( mem + "/index." ) 395 for f in flavours: 396 renderme.append( (mem + f, "") ) 397 398 # now we handle arbitrary urls 399 additional_stuff = config.get("static_urls", []) 400 print "rendering %d arbitrary urls." % len(additional_stuff) 401 402 for mem in additional_stuff: 403 if mem.find("?") != -1: 404 url = mem[:mem.find("?")] 405 query = mem[mem.find("?")+1:] 406 else: 407 url = mem 408 query = "" 409 410 renderme.append( (url, query) ) 411 412 # now we pass the complete render list to all the plugins 413 # via cb_staticrender_filelist and they can add to the filelist 414 # any ( url, query ) tuples they want rendered. 415 print "(before) building %s files." % len(renderme) 416 tools.run_callback("staticrender_filelist", 417 {'request': self._request, 418 'filelist': renderme, 419 'flavours': flavours}) 420 421 print "building %s files." % len(renderme) 422 423 for url, q in renderme: 424 url = url.replace(os.sep, "/") 425 print "rendering '%s' ..." % url 426 427 tools.render_url_statically(config, url, q) 428 429 # we're done, clean up 430 self.cleanup()
431
432 - def testInstallation(self):
433 """ 434 Goes through and runs some basic tests of the installation 435 to make sure things are working. 436 437 FIXME - This could probably use some work. Maybe make this like 438 MoinMoin's SystemInfo page? 439 """ 440 tools.initialize(self._config) 441 test_installation(self._request) 442 tools.cleanup()
443 444
445 -class PyBlosxomWSGIApp:
446 """ 447 This class is the WSGI application for PyBlosxom. 448 """
449 - def __init__(self, configini=None):
450 """ 451 Make WSGI app for PyBlosxom 452 453 @param configini: dict encapsulating information from a config.ini 454 file or any other property file that will override the config.py 455 file. 456 @type configini: dict 457 """ 458 if configini == None: 459 configini = {} 460 461 _config = {} 462 for key, value in configini.items(): 463 if isinstance(value, basestring) and value.isdigit(): 464 _config[key] = int(value) 465 else: 466 _config[key] = value 467 self.config = _config 468 if "codebase" in _config: 469 sys.path.insert(0, _config["codebase"])
470
471 - def __call__(self, env, start_response):
472 """ 473 Runs the WSGI app. 474 """ 475 # ensure that PATH_INFO exists. a few plugins break if this is 476 # missing. 477 if "PATH_INFO" not in env: 478 env["PATH_INFO"] = "" 479 480 p = PyBlosxom(self.config, env) 481 p.run() 482 483 pyresponse = p.getResponse() 484 start_response(pyresponse.status, list(pyresponse.headers.items())) 485 pyresponse.seek(0) 486 return [pyresponse.read()]
487
488 -def pyblosxom_app_factory(global_config, **local_config):
489 """ 490 App factory for paste. 491 """ 492 from paste import cgitb_catcher 493 494 conf = global_config.copy() 495 conf.update(local_config) 496 conf.update(dict(local_config=local_config, global_config=global_config)) 497 498 # FIXME - should we allow people to do their entire configuration 499 # in an ini file? 500 try: 501 # update the config.py data with config.ini data 502 import config 503 configpy = dict(config.py) 504 configpy.update(conf) 505 conf = configpy 506 except: 507 pass 508 509 return cgitb_catcher.make_cgitb_middleware(PyBlosxomWSGIApp(conf), 510 global_config)
511 512
513 -class EnvDict(dict):
514 """ 515 Wrapper arround a dict to provide a backwards compatible way 516 to get the L{form<cgi.FieldStorage>} with syntax as:: 517 518 request.getHttp()['form'] 519 520 instead of:: 521 522 request.getForm() 523 """
524 - def __init__(self, request, env):
525 """ 526 Wraps an environment (which is a dict) and a request. 527 528 @param request: the Request object for this request 529 @type request: Request 530 531 @param env: the environment for this request 532 @type env: dict 533 """ 534 dict.__init__(self) 535 self._request = request 536 self.update(env)
537
538 - def __getitem__(self, key):
539 """ 540 If the key argument is "form", we return the _request.getForm(). 541 Otherwise this returns the item for that key in the wrapped 542 dict. 543 544 @param key: the key requested 545 @type key: string 546 547 @returns: varies 548 @rtype: varies 549 """ 550 if key == "form": 551 return self._request.getForm() 552 else: 553 return dict.__getitem__(self, key)
554
555 -class Request(object):
556 """ 557 This class holds the PyBlosxom request. It holds configuration 558 information, HTTP/CGI information, and data that we calculate 559 and transform over the course of execution. 560 561 There should be only one instance of this class floating around 562 and it should get created by pyblosxom.cgi and passed into the 563 PyBlosxom instance which will do further manipulation on the 564 Request instance. 565 """
566 - def __init__(self, config, environ, data):
567 """ 568 Sets configuration and environment. 569 Creates the L{Response} object which handles all output 570 related functionality. 571 572 @param config: A dict containing the configuration variables. 573 @type config: dict 574 575 @param environ: A dict containing the environment variables. 576 @type environ: dict 577 578 @param data: A dict containing data variables. 579 @type data: dict 580 """ 581 # this holds configuration data that the user changes 582 # in config.py 583 self._configuration = config 584 585 # this holds HTTP/CGI oriented data specific to the request 586 # and the environment in which the request was created 587 self._http = EnvDict(self, environ) 588 589 # this holds run-time data which gets created and transformed 590 # by pyblosxom during execution 591 if data == None: 592 self._data = dict() 593 else: 594 self._data = data 595 596 # this holds the input stream. 597 # initialized for dynamic rendering in Pyblosxom.run. 598 # for static rendering there is no input stream. 599 self._in = StringIO() 600 601 # copy methods to the Request object. 602 self.read = self._in.read 603 self.readline = self._in.readline 604 self.readlines = self._in.readlines 605 self.seek = self._in.seek 606 self.tell = self._in.tell 607 608 # this holds the FieldStorage instance. 609 # initialized when request.getForm is called the first time 610 self._form = None 611 612 self._response = None 613 614 # create and set the Response 615 self.setResponse(Response(self))
616
617 - def __iter__(self):
618 """ 619 Can't copy the __iter__ method over from the StringIO instance cause 620 iter looks for the method in the class instead of the instance. 621 622 See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/252151 623 """ 624 return self._in
625
626 - def buffer_input_stream(self):
627 """ 628 Buffer the input stream in a StringIO instance. 629 This is done to have a known/consistent way of accessing incomming 630 data. For example the input stream passed by mod_python does not 631 offer the same functionallity as sys.stdin. 632 """ 633 # TODO: tests on memory consumption when uploading huge files 634 pyhttp = self.getHttp() 635 winput = pyhttp['wsgi.input'] 636 method = pyhttp["REQUEST_METHOD"] 637 638 # there's no data on stdin for a GET request. pyblosxom 639 # will block indefinitely on the read for a GET request with 640 # thttpd. 641 if method != "GET": 642 try: 643 length = int(pyhttp.get("CONTENT_LENGTH", 0)) 644 except ValueError: 645 length = 0 646 647 if length > 0: 648 self._in.write(winput.read(length)) 649 # rewind to start 650 self._in.seek(0)
651
652 - def setResponse(self, response):
653 """ 654 Sets the L{Response} object. 655 656 @param response: A pyblosxom Response object 657 @type response: L{Response} 658 """ 659 self._response = response 660 # for backwards compatibility 661 self.getConfiguration()['stdoutput'] = response
662
663 - def getResponse(self):
664 """ 665 Returns the L{Response} object which handles all output 666 related functionality. 667 668 @returns: L{Response} 669 @rtype: object 670 """ 671 return self._response
672
673 - def __getform(self):
674 """ 675 Parses and returns the form data submitted by the client. 676 Rewinds the input buffer after calling cgi.FieldStorage. 677 678 @returns: L{cgi.FieldStorage} 679 @rtype: object 680 """ 681 form = cgi.FieldStorage(fp=self._in, 682 environ=self._http, 683 keep_blank_values=0) 684 # rewind the input buffer 685 self._in.seek(0) 686 return form
687
688 - def getForm(self):
689 """ 690 Returns the form data submitted by the client. 691 The L{form<cgi.FieldStorage>} instance is created 692 only when requested to prevent overhead and unnecessary 693 consumption of the input stream. 694 695 @returns: L{cgi.FieldStorage} 696 @rtype: object 697 """ 698 if self._form == None: 699 self._form = self.__getform() 700 return self._form
701
702 - def getConfiguration(self):
703 """ 704 Returns the _actual_ configuration dict. The configuration 705 dict holds values that the user sets in their config.py file. 706 707 Modifying the contents of the dict will affect all downstream 708 processing. 709 710 @returns: the configuration dict 711 @rtype: dict 712 """ 713 return self._configuration
714
715 - def getHttp(self):
716 """ 717 Returns the _actual_ http dict. Holds HTTP/CGI data derived 718 from the environment of execution. 719 720 Modifying the contents of the dict will affect all downstream 721 processing. 722 723 @returns: the http environment dict 724 @rtype: dict 725 """ 726 return self._http
727
728 - def getData(self):
729 """ 730 Returns the _actual_ data dict. Holds run-time data which is 731 created and transformed by pyblosxom during execution. 732 733 Modifying the contents of the dict will affect all downstream 734 processing. 735 736 @returns: the run-time data dict 737 @rtype: dict 738 """ 739 return self._data
740
741 - def __populatedict(self, currdict, newdict):
742 """ 743 Internal helper method for populating an existing dict with 744 data from the new dict. 745 746 @param currdict: the existing dict to update 747 @type currdict: dict 748 749 @param newdict: the new dict with values to update with 750 @type newdict: dict 751 """ 752 currdict.update(newdict)
753
754 - def addHttp(self, d):
755 """ 756 Takes in a dict and adds/overrides values in the existing 757 http dict with the new values. 758 759 @param d: the dict with the new keys/values to add 760 @type d: dict 761 """ 762 self.__populatedict(self._http, d)
763
764 - def addData(self, d):
765 """ 766 Takes in a dict and adds/overrides values in the existing 767 data dict with the new values. 768 769 @param d: the dict with the new keys/values to add 770 @type d: dict 771 """ 772 self.__populatedict(self._data, d)
773
774 - def addConfiguration(self, newdict):
775 """ 776 Takes in a dict and adds/overrides values in the existing 777 configuration dict with the new values. 778 779 @param newdict: the dict with the new keys/values to add 780 @type newdict: dict 781 """ 782 self.__populatedict(self._configuration, newdict)
783
784 - def __getattr__(self, name, default=None):
785 """ 786 Sort of simulates the dict except we only have three 787 valid attributes: config, data, and http. 788 789 @param name: the name of the attribute to get 790 @type name: string 791 792 @param default: varies 793 @type default: varies 794 """ 795 if name in ["config", "configuration", "conf"]: 796 return self._configuration 797 798 if name == "data": 799 return self._data 800 801 if name == "http": 802 return self._http 803 804 return default
805
806 - def __repr__(self):
807 """ 808 Returns a representation of this request which is "Request". 809 810 @returns: "Request" 811 @rtype: string 812 """ 813 return "Request"
814 815
816 -class Response(object):
817 """ 818 Response class to handle all output related tasks in one place. 819 820 This class is basically a wrapper arround a StringIO instance. 821 It also provides methods for managing http headers. 822 """
823 - def __init__(self, request):
824 """ 825 Sets the L{Request} object that leaded to this response. 826 Creates a L{StringIO} that is used as a output buffer. 827 828 @param request: request object. 829 @type request: L{Request} 830 """ 831 self._request = request 832 self._out = StringIO() 833 self._headers_sent = False 834 self.headers = {} 835 self.status = "200 OK" 836 837 self.close = self._out.close 838 self.flush = self._out.flush 839 self.read = self._out.read 840 self.readline = self._out.readline 841 self.readlines = self._out.readlines 842 self.seek = self._out.seek 843 self.tell = self._out.tell 844 self.write = self._out.write 845 self.writelines = self._out.writelines
846
847 - def __iter__(self):
848 """ 849 Can't copy the __iter__ method over from the StringIO instance cause 850 iter looks for the method in the class instead of the instance. 851 852 See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/252151 853 """ 854 return self._out
855
856 - def setStatus(self, status):
857 """ 858 Sets the status code for this response. 859 860 @param status: A status code and message like '200 OK'. 861 @type status: str 862 """ 863 self.status = status
864
865 - def getStatus(self):
866 """ 867 Returns the status code and message of this response. 868 869 @returns: str 870 """ 871 return self.status
872
873 - def addHeader(self, *args):
874 """ 875 Populates the HTTP header with lines of text. 876 Sets the status code on this response object if the given argument 877 list containes a 'Status' header. 878 879 @param args: Paired list of headers 880 @type args: argument lists 881 @raises ValueError: This happens when the parameters are not correct 882 """ 883 args = list(args) 884 if not len(args) % 2: 885 while args: 886 key = args.pop(0).strip() 887 if key.find(' ') != -1 or key.find(':') != -1: 888 raise ValueError, 'There should be no spaces in header keys' 889 value = args.pop(0).strip() 890 891 if key.lower() == "status": 892 self.setStatus(str(value)) 893 else: 894 self.headers.update({key: str(value)}) 895 else: 896 raise ValueError, 'Headers recieved are not in the correct form'
897
898 - def getHeaders(self):
899 """ 900 Returns the headers of this response. 901 902 @returns: the HTTP response headers 903 @rtype: dict 904 """ 905 return self.headers
906
907 - def sendHeaders(self, out):
908 """ 909 Send HTTP Headers to the given output stream. 910 911 @param out: File like object 912 @type out: file 913 """ 914 out.write("Status: %s\n" % self.status) 915 out.write('\n'.join(['%s: %s' % (hkey, self.headers[hkey]) 916 for hkey in self.headers.keys()])) 917 out.write('\n\n') 918 self._headers_sent = True
919
920 - def sendBody(self, out):
921 """ 922 Send the response body to the given output stream. 923 924 @param out: File like object 925 @type out: file 926 """ 927 self.seek(0) 928 try: 929 out.write(self.read()) 930 except IOError: 931 # this is usually a Broken Pipe because the client dropped the 932 # connection. so we skip it. 933 pass
934 935 936 # 937 # blosxom behavior stuff 938 # 939
940 -def blosxom_handler(request):
941 """ 942 This is the default blosxom handler. 943 944 It calls the renderer callback to get a renderer. If there is 945 no renderer, it uses the blosxom renderer. 946 947 It calls the pathinfo callback to process the path_info http 948 variable. 949 950 It calls the filelist callback to build a list of entries to 951 display. 952 953 It calls the prepare callback to do any additional preparation 954 before rendering the entries. 955 956 Then it tells the renderer to render the entries. 957 958 @param request: A standard request object 959 @type request: L{Pyblosxom.pyblosxom.Request} object 960 """ 961 config = request.getConfiguration() 962 data = request.getData() 963 964 # go through the renderer callback to see if anyone else 965 # wants to render. this renderer gets stored in the data dict 966 # for downstream processing. 967 rend = tools.run_callback('renderer', 968 {'request': request}, 969 donefunc = lambda x: x != None, 970 defaultfunc = lambda x: None) 971 972 if not rend: 973 # get the renderer we want to use 974 rend = config.get("renderer", "blosxom") 975 976 # import the renderer 977 rend = tools.importname("Pyblosxom.renderers", rend) 978 979 # get the renderer object 980 rend = rend.Renderer(request, config.get("stdoutput", sys.stdout)) 981 982 data['renderer'] = rend 983 984 # generate the timezone variable 985 data["timezone"] = time.tzname[time.localtime()[8]] 986 987 # process the path info to determine what kind of blog entry(ies) 988 # this is 989 tools.run_callback("pathinfo", 990 {"request": request}, 991 donefunc=lambda x:x != None, 992 defaultfunc=blosxom_process_path_info) 993 994 # call the filelist callback to generate a list of entries 995 data["entry_list"] = tools.run_callback("filelist", 996 {"request": request}, 997 donefunc=lambda x:x != None, 998 defaultfunc=blosxom_file_list_handler) 999 1000 # figure out the blog-level mtime which is the mtime of the head of 1001 # the entry_list 1002 entry_list = data["entry_list"] 1003 if isinstance(entry_list, list) and len(entry_list) > 0: 1004 mtime = entry_list[0].get("mtime", time.time()) 1005 else: 1006 mtime = time.time() 1007 mtime_tuple = time.localtime(mtime) 1008 mtime_gmtuple = time.gmtime(mtime) 1009 1010 data["latest_date"] = time.strftime('%a, %d %b %Y', mtime_tuple) 1011 1012 # Make sure we get proper 'English' dates when using standards 1013 loc = locale.getlocale(locale.LC_ALL) 1014 locale.setlocale(locale.LC_ALL, 'C') 1015 1016 data["latest_w3cdate"] = time.strftime('%Y-%m-%dT%H:%M:%SZ', 1017 mtime_gmtuple) 1018 data['latest_rfc822date'] = time.strftime('%a, %d %b %Y %H:%M GMT', 1019 mtime_gmtuple) 1020 1021 # set the locale back 1022 locale.setlocale(locale.LC_ALL, loc) 1023 1024 # we pass the request with the entry_list through the prepare callback 1025 # giving everyone a chance to transform the data. the request is 1026 # modified in place. 1027 tools.run_callback("prepare", {"request": request}) 1028 1029 # now we pass the entry_list through the renderer 1030 entry_list = data["entry_list"] 1031 renderer = data['renderer'] 1032 1033 if renderer and not renderer.rendered: 1034 if entry_list: 1035 renderer.setContent(entry_list) 1036 # Log it as success 1037 tools.run_callback("logrequest", 1038 {'filename':config.get('logfile',''), 1039 'return_code': '200', 1040 'request': request}) 1041 else: 1042 renderer.addHeader('Status', '404 Not Found') 1043 renderer.setContent( 1044 {'title': 'The page you are looking for is not available', 1045 'body': 'Somehow I cannot find the page you want. ' + 1046 'Go Back to <a href="%s">%s</a>?' 1047 % (config["base_url"], config["blog_title"])}) 1048 # Log it as failure 1049 tools.run_callback("logrequest", 1050 {'filename':config.get('logfile',''), 1051 'return_code': '404', 1052 'request': request}) 1053 renderer.render() 1054 1055 elif not renderer: 1056 output = config.get('stdoutput', sys.stdout) 1057 output.write("Content-Type: text/plain\n\n" + \ 1058 "There is something wrong with your setup.\n" + \ 1059 "Check your config files and verify that your " + \ 1060 "configuration is correct.\n") 1061 1062 cache = tools.get_cache(request) 1063 if cache: 1064 cache.close()
1065 1066
1067 -def blosxom_entry_parser(filename, request):
1068 """ 1069 Open up a *.txt file and read its contents. The first line 1070 becomes the title of the entry. The other lines are the 1071 body of the entry. 1072 1073 @param filename: A filename to extract data and metadata from 1074 @type filename: string 1075 1076 @param request: A standard request object 1077 @type request: L{Pyblosxom.pyblosxom.Request} object 1078 1079 @returns: A dict containing parsed data and meta data with the 1080 particular file (and plugin) 1081 @rtype: dict 1082 """ 1083 config = request.getConfiguration() 1084 1085 entryData = {} 1086 1087 f = open(filename, "r") 1088 lines = f.readlines() 1089 f.close() 1090 1091 # the file has nothing in it... so we're going to return 1092 # a blank entry data object. 1093 if len(lines) == 0: 1094 return { "title": "", "body": "" } 1095 1096 # NOTE: you can probably use the next bunch of lines verbatim 1097 # for all entryparser plugins. this pulls the first line off as 1098 # the title, the next bunch of lines that start with # as 1099 # metadata lines, and then everything after that is the body 1100 # of the entry. 1101 title = lines.pop(0).strip() 1102 entryData['title'] = title 1103 1104 # absorb meta data lines which begin with a # 1105 while lines and lines[0].startswith("#"): 1106 meta = lines.pop(0) 1107 meta = meta[1:].strip() # remove the hash 1108 meta = meta.split(" ", 1) 1109 entryData[meta[0].strip()] = meta[1].strip() 1110 1111 # Call the preformat function 1112 args = {'parser': entryData.get('parser', config.get('parser', 'plain')), 1113 'story': lines, 1114 'request': request} 1115 otmp = tools.run_callback('preformat', 1116 args, 1117 donefunc = lambda x:x != None, 1118 defaultfunc = lambda x: ''.join(x['story'])) 1119 entryData['body'] = otmp 1120 1121 # Call the postformat callbacks 1122 tools.run_callback('postformat', 1123 {'request': request, 1124 'entry_data': entryData}) 1125 1126 return entryData
1127 1128
1129 -def blosxom_file_list_handler(args):
1130 """ 1131 This is the default handler for getting entries. It takes the 1132 request object in and figures out which entries based on the 1133 default behavior that we want to show and generates a list of 1134 EntryBase subclass objects which it returns. 1135 1136 @param args: dict containing the incoming Request object 1137 @type args: object 1138 1139 @returns: the content we want to render 1140 @rtype: list of EntryBase objects 1141 """ 1142 request = args["request"] 1143 1144 data = request.getData() 1145 config = request.getConfiguration() 1146 1147 if data['bl_type'] == 'dir': 1148 filelist = tools.Walk(request, 1149 data['root_datadir'], 1150 int(config.get("depth", "0"))) 1151 elif data['bl_type'] == 'file': 1152 filelist = [data['root_datadir']] 1153 else: 1154 filelist = [] 1155 1156 entrylist = [] 1157 for ourfile in filelist: 1158 e = FileEntry(request, ourfile, data['root_datadir']) 1159 entrylist.append((e._mtime, e)) 1160 1161 # this sorts entries by mtime in reverse order. entries that have 1162 # no mtime get sorted to the top. 1163 entrylist.sort() 1164 entrylist.reverse() 1165 1166 # Match dates with files if applicable 1167 if data['pi_yr']: 1168 # This is called when a date has been requested, e.g. 1169 # /some/category/2004/Sep 1170 month = (data['pi_mo'] in tools.month2num.keys() and \ 1171 tools.month2num[data['pi_mo']] or \ 1172 data['pi_mo']) 1173 matchstr = "^" + data["pi_yr"] + month + data["pi_da"] 1174 valid_list = [x for x in entrylist if re.match(matchstr, 1175 x[1]._fulltime)] 1176 else: 1177 valid_list = entrylist 1178 1179 # This is the maximum number of entries we can show on the front page 1180 # (zero indicates show all entries) 1181 maxe = config.get("num_entries", 5) 1182 if maxe and not data["pi_yr"]: 1183 valid_list = valid_list[:maxe] 1184 data["debugme"] = "done" 1185 1186 valid_list = [x[1] for x in valid_list] 1187 1188 return valid_list
1189
1190 -def blosxom_process_path_info(args):
1191 """ 1192 Process HTTP PATH_INFO for URI according to path specifications, fill in 1193 data dict accordingly. 1194 1195 The paths specification looks like this: 1196 - C{/foo.html} and C{/cat/foo.html} - file foo.* in / and /cat 1197 - C{/cat} - category 1198 - C{/2002} - category 1199 - C{/2002} - year 1200 - C{/2002/Feb} (or 02) - Year and Month 1201 - C{/cat/2002/Feb/31} - year and month day in category. 1202 1203 @param args: dict containing the incoming Request object 1204 @type args: object 1205 """ 1206 request = args['request'] 1207 config = request.getConfiguration() 1208 data = request.getData() 1209 pyhttp = request.getHttp() 1210 1211 form = request.getForm() 1212 1213 # figure out which flavour to use. the flavour is determined 1214 # by looking at the "flav" post-data variable, the "flav" 1215 # query string variable, the "default_flavour" setting in the 1216 # config.py file, or "html" 1217 flav = config.get("default_flavour", "html") 1218 if form.has_key("flav"): 1219 flav = form["flav"].value 1220 1221 data['flavour'] = flav 1222 1223 data['pi_yr'] = '' 1224 data['pi_mo'] = '' 1225 data['pi_da'] = '' 1226 1227 path_info = pyhttp.get("PATH_INFO", "") 1228 1229 data['root_datadir'] = config['datadir'] 1230 1231 data["pi_bl"] = path_info 1232 1233 # first we check to see if this is a request for an index and we can pluck 1234 # the extension (which is certainly a flavour) right off. 1235 newpath, ext = os.path.splitext(path_info) 1236 if newpath.endswith("/index") and ext: 1237 # there is a flavour-like thing, so that's our new flavour 1238 # and we adjust the path_info to the new filename 1239 data["flavour"] = ext[1:] 1240 path_info = newpath 1241 1242 while path_info and path_info.startswith("/"): 1243 path_info = path_info[1:] 1244 1245 absolute_path = os.path.join(config["datadir"], path_info) 1246 1247 path_info = path_info.split("/") 1248 1249 if os.path.isdir(absolute_path): 1250 1251 # this is an absolute path 1252 1253 data['root_datadir'] = absolute_path 1254 data['bl_type'] = 'dir' 1255 1256 elif absolute_path.endswith("/index") and \ 1257 os.path.isdir(absolute_path[:-6]): 1258 1259 # this is an absolute path with /index at the end of it 1260 1261 data['root_datadir'] = absolute_path[:-6] 1262 data['bl_type'] = 'dir' 1263 1264 else: 1265 # this is either a file or a date 1266 1267 ext = tools.what_ext(data["extensions"].keys(), absolute_path) 1268 if not ext: 1269 # it's possible we didn't find the file because it's got a flavour 1270 # thing at the end--so try removing it and checking again. 1271 newpath, flav = os.path.splitext(absolute_path) 1272 if flav: 1273 ext = tools.what_ext(data["extensions"].keys(), newpath) 1274 if ext: 1275 # there is a flavour-like thing, so that's our new flavour 1276 # and we adjust the absolute_path and path_info to the new 1277 # filename 1278 data["flavour"] = flav[1:] 1279 absolute_path = newpath 1280 path_info, flav = os.path.splitext("/".join(path_info)) 1281 path_info = path_info.split("/") 1282 1283 if ext: 1284 # this is a file 1285 data["bl_type"] = "file" 1286 data["root_datadir"] = absolute_path + "." + ext 1287 1288 else: 1289 data["bl_type"] = "dir" 1290 1291 # it's possible to have category/category/year/month/day 1292 # (or something like that) so we pluck off the categories 1293 # here. 1294 pi_bl = "" 1295 while len(path_info) > 0 and \ 1296 not (len(path_info[0]) == 4 and path_info[0].isdigit()): 1297 pi_bl = os.path.join(pi_bl, path_info.pop(0)) 1298 1299 # handle the case where we do in fact have a category 1300 # preceeding the date. 1301 if pi_bl: 1302 pi_bl = pi_bl.replace("\\", "/") 1303 data["pi_bl"] = pi_bl 1304 data["root_datadir"] = os.path.join(config["datadir"], pi_bl) 1305 1306 if len(path_info) > 0: 1307 item = path_info.pop(0) 1308 # handle a year token 1309 if len(item) == 4 and item.isdigit(): 1310 data['pi_yr'] = item 1311 item = "" 1312 1313 if (len(path_info) > 0): 1314 item = path_info.pop(0) 1315 # handle a month token 1316 if item in tools.MONTHS: 1317 data['pi_mo'] = item 1318 item = "" 1319 1320 if (len(path_info) > 0): 1321 item = path_info.pop(0) 1322 # handle a day token 1323 if len(item) == 2 and item.isdigit(): 1324 data["pi_da"] = item 1325 item = "" 1326 1327 if len(path_info) > 0: 1328 item = path_info.pop(0) 1329 1330 # if the last item we picked up was "index", then we 1331 # just ditch it because we don't need it. 1332 if item == "index": 1333 item = "" 1334 1335 # if we picked off an item we don't recognize and/or 1336 # there is still stuff in path_info to pluck out, then 1337 # it's likely this wasn't a date. 1338 if item or len(path_info) > 0: 1339 data["bl_type"] = "dir" 1340 data["root_datadir"] = absolute_path 1341 1342 1343 # figure out the blog_title_with_path data variable 1344 blog_title = config["blog_title"] 1345 1346 if data['pi_bl'] != '': 1347 data['blog_title_with_path'] = '%s : %s' % (blog_title, data['pi_bl']) 1348 else: 1349 data['blog_title_with_path'] = blog_title 1350 1351 # construct our final URL 1352 data['url'] = '%s%s' % (config['base_url'], data['pi_bl']) 1353 url = config['base_url'] 1354 if data['pi_bl'].startswith("/"): 1355 url = url + data['pi_bl'] 1356 else: 1357 url = url + "/" + data['pi_bl'] 1358 data['url'] = url 1359 1360 # set path_info to our latest path_info 1361 data['path_info'] = path_info
1362 1363 1364 1365 # 1366 # command line stuff 1367 # 1368 1369 HELP = """Syntax: %(script)s [path-opts] [args] 1370 1371 PATH OPTIONS: 1372 1373 -c, --config 1374 1375 This specifies the location of the config.py file for the blog 1376 you want to work with. If the config.py file is in the current 1377 directory, then you don't need to specify this. 1378 1379 Note: %(script)s will use the "codebase" parameter in your config.py 1380 file to locate the version of PyBlosxom you're using if there 1381 is one. If there isn't one, then %(script)s expects PyBlosxom to 1382 be installed as a Python package on your system. 1383 1384 ARGUMENTS: 1385 1386 -v, --version 1387 1388 Prints the PyBlosxom version and some other information. 1389 1390 -h, --help 1391 1392 Prints this help text 1393 1394 -h, --headers 1395 1396 When rendering a url, this will also render the HTTP headers. 1397 1398 -r, --render <url> 1399 1400 Renders a url of your blog. 1401 1402 %(script)s -r http://www.joesblog.com/cgi-bin/pyblosxom.cgi/index.html 1403 1404 will pull off the base_url from the front leaving "/index.html" and 1405 will render "/index.html" to stdout. 1406 1407 %(script)s -c ~/cgi-bin/config.py -r /index.html 1408 1409 will use the config.py file located in ~/cgi-bin/ and render 1410 "/index.html" from the PyBlosxom root. 1411 1412 -s, --static [incremental] 1413 1414 Statically renders your blog. Use "incremental" to do an incremental 1415 rendering. 1416 1417 -t, --test 1418 1419 Tests your installation. 1420 1421 1422 EXAMPLES: 1423 1424 1425 Additional flags and options may be available through plugins that 1426 you have installed. Refer to plugin documentation (usually found 1427 at the top of the plugin file) for more information. 1428 """ 1429 1430
1431 -def test_installation(request):
1432 """ 1433 This function gets called when someone starts up pyblosxom.cgi 1434 from the command line with no REQUEST_METHOD environment variable. 1435 1436 It: 1437 1438 1. tests properties in their config.py file 1439 2. verifies they have a datadir and that it exists 1440 3. initializes all the plugins they have installed 1441 4. runs "cb_verify_installation"--plugins can print out whether 1442 they are installed correctly (i.e. have valid config property 1443 settings and can read/write to data files) 1444 5. exits 1445 1446 The goal is to be as useful and informative to the user as we can be 1447 without being overly verbose and confusing. 1448 1449 This is designed to make it much much much easier for a user to 1450 verify their PyBlosxom installation is working and also to install 1451 new plugins and verify that their configuration is correct. 1452 1453 @param request: the Request object 1454 @type request: object 1455 """ 1456 config = request.getConfiguration() 1457 1458 # BASE STUFF 1459 print "Welcome to PyBlosxom's installation verification system." 1460 print "------" 1461 print "]] printing diagnostics [[" 1462 print "pyblosxom: %s" % VERSION_DATE 1463 print "sys.version: %s" % sys.version.replace("\n", " ") 1464 print "os.name: %s" % os.name 1465 print "codebase: %s" % config.get("codebase", "--default--") 1466 print "------" 1467 1468 # CONFIG FILE 1469 print "]] checking config file [[" 1470 print "config has %s properties set." % len(config) 1471 print "" 1472 1473 # these are required by the blog 1474 required_config = ["datadir"] 1475 1476 # these are nice to have optional properties 1477 nice_to_have_config = ["blog_title", "blog_author", "blog_description", 1478 "blog_language", "blog_encoding", "blog_icbm", 1479 "base_url", "depth", "num_entries", "renderer", 1480 "cacheDriver", "cacheConfig", "plugin_dirs", 1481 "load_plugins", "blog_email", "blog_rights", 1482 "default_flavour", "flavourdir", "log_file", 1483 "log_level", "logdir", ] 1484 1485 config_keys = config.keys() 1486 1487 # remove keys that are auto-generated 1488 config_keys.remove("pyblosxom_version") 1489 config_keys.remove("pyblosxom_name") 1490 1491 missing_required_props = [] 1492 missing_optionsal_props = [] 1493 1494 missing_required_props = [m 1495 for m in required_config 1496 if m not in config_keys] 1497 1498 missing_optional_props = [m 1499 for m in nice_to_have_config 1500 if m not in config_keys] 1501 1502 all_keys = nice_to_have_config + required_config 1503 1504 config_keys = [m 1505 for m in config_keys 1506 if m not in all_keys] 1507 config_keys.sort() 1508 1509 if missing_required_props: 1510 print "" 1511 print "Missing properties must be set in order for your blog to" 1512 print "work." 1513 print "" 1514 for mem in missing_required_props: 1515 print " missing required property: '%s'" % mem 1516 print "" 1517 print "This must be done before we can go further. Exiting." 1518 return 1519 1520 if missing_optional_props: 1521 print "" 1522 print "You're missing optional properties. These are not required, " 1523 print "but some of them may interest you. Refer to the documentation " 1524 print "for more information." 1525 print "" 1526 for mem in missing_optional_props: 1527 print " missing optional property: '%s'" % mem 1528 1529 if config_keys: 1530 print "" 1531 print "These are properties PyBlosxom doesn't know about. They " 1532 print "could be used by plugins or could be ones you've added." 1533 print "Remove them if you know they're not used." 1534 print "" 1535 for mem in config_keys: 1536 print " i don't know about: '%s'" % mem 1537 print "" 1538 1539 print "PASS: config file is fine." 1540 1541 print "------" 1542 print "]] checking datadir [[" 1543 1544 # DATADIR 1545 if not os.path.isdir(config["datadir"]): 1546 print "datadir '%s' does not exist." % config["datadir"] 1547 print "You need to create your datadir and give it appropriate" 1548 print "permissions." 1549 print "" 1550 print "This must be done before we can go further. Exiting." 1551 return 1552 1553 print "PASS: datadir is there." 1554 print " Note: this does NOT check whether your webserver has " 1555 print " permissions to view files therein!" 1556 1557 print "------" 1558 print "Now we're going to verify your plugin configuration." 1559 1560 if config.has_key("plugin_dirs"): 1561 plugin_utils.initialize_plugins(config["plugin_dirs"], 1562 config.get("load_plugins", None)) 1563 1564 no_verification_support = [] 1565 1566 for mem in plugin_utils.plugins: 1567 if hasattr(mem, "verify_installation"): 1568 print "=== plugin: '%s'" % mem.__name__ 1569 print " file: %s" % mem.__file__ 1570 print " version: %s" % (str(getattr(mem, "__version__"))) 1571 1572 try: 1573 if mem.verify_installation(request) == 1: 1574 print " PASS" 1575 else: 1576 print " FAIL!!!" 1577 except AssertionError, error_message: 1578 print " FAIL!!! ", error_message 1579 1580 else: 1581 mn = mem.__name__ 1582 mf = mem.__file__ 1583 no_verification_support.append( "'%s' (%s)" % (mn, mf)) 1584 1585 if len(no_verification_support) > 0: 1586 print "" 1587 print "The following plugins do not support installation " + \ 1588 "verification:" 1589 for mem in no_verification_support: 1590 print " %s" % mem 1591 else: 1592 print "You have chosen not to load any plugins."
1593 1594
1595 -def command_line_handler(scriptname, argv):
1596 """ 1597 Handles calling PyBlosxom from the command line. This can be 1598 called from two different things: pyblosxom.cgi and pyblcmd. 1599 1600 @param scriptname: the name of the script (ex. "pyblcmd") 1601 @type scriptname: string 1602 1603 @param argv: the arguments passed in 1604 @type argv: list of strings 1605 1606 @returns: the exit code 1607 """ 1608 def printq(s): 1609 print s
1610 1611 # parse initial command line variables 1612 optlist = tools.parse_args(argv) 1613 for mem in optlist: 1614 if mem[0] in ["-c", "--config"]: 1615 m = mem[1] 1616 if m.endswith("config.py"): 1617 m = m[0:-9] 1618 printq("Appending %s to sys.path for config.py location." % m) 1619 sys.path.append(m) 1620 1621 elif mem[0] in ["-q", "--quiet"]: 1622 # this quiets the printing by doing nothing with the input 1623 printq = lambda s : s 1624 1625 # the configuration properties are in a dict named "py" in 1626 # the config module 1627 printq("Trying to import the config module....") 1628 try: 1629 from config import py as cfg 1630 except: 1631 print "Error: Cannot find your config.py file. Please execute %s in\n" \ 1632 % scriptname 1633 print "the directory with your config.py file in it." 1634 return 0 1635 1636 # If the user defined a "codebase" property in their config file, 1637 # then we insert that into our sys.path because that's where the 1638 # PyBlosxom installation is. 1639 # NOTE: this _has_ to come before any PyBlosxom calls. 1640 if cfg.has_key("codebase"): 1641 sys.path.append(cfg["codebase"]) 1642 1643 printq("PyBlosxom version: %s" % VERSION_DATE) 1644 1645 if len(argv) == 0: 1646 print HELP % { "script": scriptname } 1647 return 0 1648 1649 p = PyBlosxom(cfg, {}) 1650 headers = 0 1651 1652 for mem in optlist: 1653 if mem[0] in ["-v", "--version"]: 1654 # we print the version already, so if we do it again here 1655 # it'd be doing it twice. 1656 # print get_version() 1657 return 0 1658 1659 elif mem[0] in ["-h", "--help"]: 1660 print get_help() 1661 return 0 1662 1663 elif mem[0] in ["--static", "-s"]: 1664 if mem[1].startswith("incr"): 1665 incremental = 1 1666 else: 1667 incremental = 0 1668 1669 p.runStaticRenderer(incremental) 1670 1671 elif mem[0] in ["--headers", "-h"]: 1672 headers = 1 1673 1674 elif mem[0] in ["--render", "-r"]: 1675 url = mem[1] 1676 if url.startswith(cfg.get("base_url", "")): 1677 url = url[len(cfg.get("base_url", "")):] 1678 1679 printq("Rendering '%s'" % url) 1680 1681 p.runRenderOne(url, headers) 1682 1683 elif mem[0] in ["--test", "-t"]: 1684 p.testInstallation() 1685 1686 return 0 1687 1688 # vim: shiftwidth=4 tabstop=4 expandtab 1689