1
2
3
4
5
6
7
8
9
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
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
42 import plugin_utils
43
44
45 month2num = None
46 num2month = None
47 MONTHS = None
48
49
50 VAR_REGEXP = re.compile(ur'(?<!\\)\$((?:\w|\-|::\w)+(?:\(.*?(?<!\\)\))?)')
51
52
53
54 _config = None
55
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
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
82
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
90 global MONTHS
91 MONTHS = num2month.keys() + month2num.keys()
92
93
94
96 """
97 Cleanup the tools module.
98 This should be called from Pyblosxom.pyblosxom.PyBlosxom.cleanup.
99 """
100 global _loghandler_registry
101
102
103
104
105
106
107
108 try:
109 logging.shutdown()
110 _loghandler_registry = {}
111 except ValueError:
112 pass
113
114
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 = {"'": "'", '"': """}
148
150 """
151 Takes in a string and escapes \' to ' and \" to ".
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
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
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 """
187 """
188 Initializes the internal dict.
189 """
190 self._dict = {}
191
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
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
252 """
253 This calls update(newdict) on the wrapped dict.
254 """
255 self._dict.update(newdict)
256
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
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
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
301 """
302 SGMLParser that removes HTML formatting code.
303 """
305 """
306 Initializes the instance.
307 """
308 self.data = []
309 sgmllib.SGMLParser.__init__(self)
310
312 """
313 Implements unknown_starttag. Appends a space to the buffer.
314 """
315 self.data.append(" ")
316
318 """
319 Implements unknown_endtag. Appends a space to the buffer.
320 """
321 self.data.append(" ")
322
324 """
325 Implements handle_data. Appends data to the buffer.
326 """
327 self.data.append(data)
328
330 """
331 Returns the buffer.
332 """
333 return "".join(self.data)
334
335
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
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
388
389 if callable(r):
390
391
392
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
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
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
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
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
505 Walk = walk
506
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
513 result = []
514
515 try:
516 names = os.listdir(root)
517 except:
518 return []
519
520
521 for name in names:
522 fullname = os.path.normpath(os.path.join(root, name))
523
524
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
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
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
577
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
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
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
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
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
740 output = func(input)
741
742
743
744
745 if donefunc(output) == 1:
746 break
747
748
749
750
751
752
753 input = mappingfunc(input, output)
754
755
756
757
758 if callable(defaultfunc) and donefunc(output) != 1:
759 return defaultfunc(input)
760
761
762
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
805 metadatalines = ["#%s %s" % (key, metadata[key])
806 for key in metadata.keys()]
807
808 entry = addcr(title) + "\n".join(metadatalines) + body
809
810
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
819 fn = os.path.join(datadir, category, filename)
820
821
822 f = open(fn, "w")
823 f.write(entry)
824 f.close()
825
826
827 os.utime(fn, (mtime, mtime))
828
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
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
901
902 f = open(fn, "w")
903 f.write(response.read())
904 f.close()
905
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
926
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
955
956
957
958
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
969
970 _loghandler_registry = {}
971
972
974 """
975 Filters out messages from log-channels that are not listed in the
976 log_filter config variable.
977 """
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
991 if record.name in self.names:
992 return 1
993 return 0
994
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
1015 log_name = ""
1016 for path in _config.get('plugin_dirs', []):
1017 if filename.startswith(path):
1018
1019
1020 log_name = module
1021 break
1022
1023 log_level = _config.get('log_level', 'warning')
1024 else:
1025
1026 custom_log_file = True
1027
1028 log_name = os.path.splitext(os.path.basename(log_file))[0]
1029
1030 log_level = "debug"
1031
1032 global _loghandler_registry
1033
1034
1035 logger = logging.getLogger(log_name)
1036
1037 logger.propagate = 0
1038
1039
1040
1041 key = "%s|%s" % (log_file, log_name)
1042 if not key in _loghandler_registry:
1043
1044
1045 if log_file == "stderr":
1046 hdlr = logging.StreamHandler(sys.stderr)
1047 else:
1048 if log_file == "NONE":
1049 if os.name == 'nt':
1050 log_file = "NUL"
1051 else:
1052 log_file = "/dev/null"
1053 try:
1054 hdlr = logging.FileHandler(log_file)
1055 except IOError:
1056
1057 hdlr = logging.StreamHandler(sys.stderr)
1058
1059
1060 if log_name:
1061 fmtr_s = '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
1062 else:
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
1073
1074
1075 log_filter = _config.get('log_filter', None)
1076 if log_filter:
1077 lfilter = LogFilter(log_filter)
1078 logger.addFilter(lfilter)
1079
1080
1081 _loghandler_registry[key] = True
1082
1083 return logger
1084
1085
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
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
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
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
1174 LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
1175
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
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
1199 fcntl.flock(f.fileno(), fcntl.LOCK_UN)
1200
1201
1202
1203