1 from __future__ import nested_scopes, generators
2
3
4
5
6
7
8
9
10
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
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
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
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
70
71
72
73
74
75
76
77 self._config = config
78 self._request = Request(config, environ, data)
79
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
91
92 if config.get('locale'):
93 try:
94 locale.setlocale(locale.LC_ALL, config['locale'])
95 except locale.Error:
96
97 pass
98
99
100 tools.initialize(config)
101
102 data["pyblosxom_version"] = VERSION_DATE
103 data['pi_bl'] = ''
104
105
106 if pyhttp.has_key('SCRIPT_NAME'):
107 if not config.has_key('base_url'):
108
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
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
126 plugin_utils.initialize_plugins(config.get("plugin_dirs", []),
127 config.get("load_plugins", None))
128
129
130
131 data['extensions'] = tools.run_callback("entryparser",
132 {'txt': blosxom_entry_parser},
133 mappingfunc=lambda x,y:y,
134 defaultfunc=lambda x:x)
135
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
145
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
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
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
188
189
190 if static == False:
191 self.getRequest().buffer_input_stream()
192
193
194 tools.run_callback("start", {'request': self._request})
195
196
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
206 tools.run_callback("end", {'request': self._request})
207
208
209
210 if static == False:
211 self.cleanup()
212
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
229 tools.run_callback("start", {'request': self._request})
230
231
232 handled = tools.run_callback(callback,
233 {'request': self._request},
234 mappingfunc=lambda x,y:x,
235 donefunc=lambda x:x)
236
237
238 tools.run_callback("end", {'request': self._request})
239
240 return handled
241
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
272 self.cleanup()
273
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
312 listing = tools.Walk(self._request, datadir)
313
314 for mem in listing:
315
316 ext = mem[mem.rfind(".")+1:]
317 if not ext in data["extensions"].keys():
318 continue
319
320
321 mtime = time.mktime(tools.filestat(self._request, mem))
322
323
324 mem = mem[len(datadir):mem.rfind(".")]
325
326
327 fn = os.path.normpath(staticdir + mem)
328
329
330 try:
331 smtime = os.stat(fn + "." + flavours[0])[8]
332 except:
333 smtime = 0
334
335
336
337 if smtime < mtime or not incremental:
338
339
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
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
363 for f in flavours:
364 renderme.append( (mem + "." + f, "") )
365
366 print "rendering %d entries." % len(renderme)
367
368
369 categories = categories.keys()
370 categories.sort()
371
372
373
374
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
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
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
413
414
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
430 self.cleanup()
431
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
446 """
447 This class is the WSGI application for PyBlosxom.
448 """
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
476
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
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
499
500 try:
501
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
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 """
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
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
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
582
583 self._configuration = config
584
585
586
587 self._http = EnvDict(self, environ)
588
589
590
591 if data == None:
592 self._data = dict()
593 else:
594 self._data = data
595
596
597
598
599 self._in = StringIO()
600
601
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
609
610 self._form = None
611
612 self._response = None
613
614
615 self.setResponse(Response(self))
616
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
651
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
661 self.getConfiguration()['stdoutput'] = response
662
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
687
701
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
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
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
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
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
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
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
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
807 """
808 Returns a representation of this request which is "Request".
809
810 @returns: "Request"
811 @rtype: string
812 """
813 return "Request"
814
815
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 """
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
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
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
866 """
867 Returns the status code and message of this response.
868
869 @returns: str
870 """
871 return self.status
872
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
899 """
900 Returns the headers of this response.
901
902 @returns: the HTTP response headers
903 @rtype: dict
904 """
905 return self.headers
906
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
932
933 pass
934
935
936
937
938
939
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
965
966
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
974 rend = config.get("renderer", "blosxom")
975
976
977 rend = tools.importname("Pyblosxom.renderers", rend)
978
979
980 rend = rend.Renderer(request, config.get("stdoutput", sys.stdout))
981
982 data['renderer'] = rend
983
984
985 data["timezone"] = time.tzname[time.localtime()[8]]
986
987
988
989 tools.run_callback("pathinfo",
990 {"request": request},
991 donefunc=lambda x:x != None,
992 defaultfunc=blosxom_process_path_info)
993
994
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
1001
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
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
1022 locale.setlocale(locale.LC_ALL, loc)
1023
1024
1025
1026
1027 tools.run_callback("prepare", {"request": request})
1028
1029
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
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
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
1092
1093 if len(lines) == 0:
1094 return { "title": "", "body": "" }
1095
1096
1097
1098
1099
1100
1101 title = lines.pop(0).strip()
1102 entryData['title'] = title
1103
1104
1105 while lines and lines[0].startswith("#"):
1106 meta = lines.pop(0)
1107 meta = meta[1:].strip()
1108 meta = meta.split(" ", 1)
1109 entryData[meta[0].strip()] = meta[1].strip()
1110
1111
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
1122 tools.run_callback('postformat',
1123 {'request': request,
1124 'entry_data': entryData})
1125
1126 return entryData
1127
1128
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
1162
1163 entrylist.sort()
1164 entrylist.reverse()
1165
1166
1167 if data['pi_yr']:
1168
1169
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
1180
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
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
1214
1215
1216
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
1234
1235 newpath, ext = os.path.splitext(path_info)
1236 if newpath.endswith("/index") and ext:
1237
1238
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
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
1260
1261 data['root_datadir'] = absolute_path[:-6]
1262 data['bl_type'] = 'dir'
1263
1264 else:
1265
1266
1267 ext = tools.what_ext(data["extensions"].keys(), absolute_path)
1268 if not ext:
1269
1270
1271 newpath, flav = os.path.splitext(absolute_path)
1272 if flav:
1273 ext = tools.what_ext(data["extensions"].keys(), newpath)
1274 if ext:
1275
1276
1277
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
1285 data["bl_type"] = "file"
1286 data["root_datadir"] = absolute_path + "." + ext
1287
1288 else:
1289 data["bl_type"] = "dir"
1290
1291
1292
1293
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
1300
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
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
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
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
1331
1332 if item == "index":
1333 item = ""
1334
1335
1336
1337
1338 if item or len(path_info) > 0:
1339 data["bl_type"] = "dir"
1340 data["root_datadir"] = absolute_path
1341
1342
1343
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
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
1361 data['path_info'] = path_info
1362
1363
1364
1365
1366
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
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
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
1469 print "]] checking config file [["
1470 print "config has %s properties set." % len(config)
1471 print ""
1472
1473
1474 required_config = ["datadir"]
1475
1476
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
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
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
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
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
1623 printq = lambda s : s
1624
1625
1626
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
1637
1638
1639
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
1655
1656
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
1689