| Home | Trees | Indices | Help |
|
|---|
|
|
1 """GNUmed configuration handling.
2 """
3 #==================================================================
4 # $Source: /home/ncq/Projekte/cvs2git/vcs-mirror/gnumed/gnumed/client/pycommon/gmCfg2.py,v $
5 __version__ = "$Revision: 1.20 $"
6 __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
7 __licence__ = "GPL"
8
9
10 import logging, sys, codecs, re as regex, shutil, os, types
11
12
13 if __name__ == "__main__":
14 sys.path.insert(0, '../../')
15 from Gnumed.pycommon import gmBorg
16
17
18 _log = logging.getLogger('gm.cfg')
19 _log.info(__version__)
20 #==================================================================
21 # helper functions
22 #==================================================================
24
25 group_seen = False
26 option_seen = False
27 in_list = False
28
29 for line in src:
30
31 # after option already ?
32 if option_seen:
33 sink.write(line)
34 continue
35
36 # start of list ?
37 if regex.match('(?P<list_name>.+)(\s|\t)*=(\s|\t)*\$(?P=list_name)\$', line) is not None:
38 in_list = True
39 sink.write(line)
40 continue
41
42 # end of list ?
43 if regex.match('\$.+\$.*', line) is not None:
44 in_list = False
45 sink.write(line)
46 continue
47
48 # our group ?
49 if line.strip() == u'[%s]' % group:
50 group_seen = True
51 sink.write(line)
52 continue
53
54 # another group ?
55 if regex.match('\[.+\].*', line) is not None:
56 # next group but option not seen yet ?
57 if group_seen and not option_seen:
58 sink.write(u'%s = %s\n\n\n' % (option, value))
59 option_seen = True
60 sink.write(line)
61 continue
62
63 # our option ?
64 if regex.match('%s(\s|\t)*=' % option, line) is not None:
65 if group_seen:
66 sink.write(u'%s = %s\n' % (option, value))
67 option_seen = True
68 continue
69 sink.write(line)
70 continue
71
72 # something else (comment, empty line, or other option)
73 sink.write(line)
74
75 # all done ?
76 if option_seen:
77 return
78
79 # need to add group ?
80 if not group_seen:
81 sink.write('[%s]\n' % group)
82
83 # We either just added the group or it was the last group
84 # but did not contain the option. It must have been the
85 # last group then or else the following group would have
86 # triggered the option writeout.
87 sink.write(u'%s = %s\n' % (option, value))
88 #==================================================================
90
91 group_seen = False
92 option_seen = False
93 in_list = False
94
95 for line in src:
96
97 # found option but still in (old) list ?
98 if option_seen and in_list:
99 # end of (old) list ?
100 if regex.match('\$.+\$.*', line) is not None:
101 in_list = False
102 sink.write(line)
103 continue
104 continue
105
106 # after option already and not in (old) list anymore ?
107 if option_seen and not in_list:
108 sink.write(line)
109 continue
110
111 # start of list ?
112 match = regex.match('(?P<list_name>.+)(\s|\t)*=(\s|\t)*\$(?P=list_name)\$', line)
113 if match is not None:
114 in_list = True
115 # our list ?
116 if group_seen and (match.group('list_name') == option):
117 option_seen = True
118 sink.write(line)
119 sink.write('\n'.join(value))
120 sink.write('\n')
121 continue
122 sink.write(line)
123 continue
124
125 # end of list ?
126 if regex.match('\$.+\$.*', line) is not None:
127 in_list = False
128 sink.write(line)
129 continue
130
131 # our group ?
132 if line.strip() == u'[%s]' % group:
133 group_seen = True
134 sink.write(line)
135 continue
136
137 # another group ?
138 if regex.match('\[%s\].*' % group, line) is not None:
139 # next group but option not seen yet ?
140 if group_seen and not option_seen:
141 option_seen = True
142 sink.write('%s = $%s$\n' % (option, option))
143 sink.write('\n'.join(value))
144 sink.write('\n')
145 continue
146 sink.write(line)
147 continue
148
149 # something else (comment, empty line, or other option)
150 sink.write(line)
151
152 # all done ?
153 if option_seen:
154 return
155
156 # need to add group ?
157 if not group_seen:
158 sink.write('[%s]\n' % group)
159
160 # We either just added the group or it was the last group
161 # but did not contain the option. It must have been the
162 # last group then or else the following group would have
163 # triggered the option writeout.
164 sink.write('%s = $%s$\n' % (option, option))
165 sink.write('\n'.join(value))
166 sink.write('\n')
167 sink.write('$%s$\n' % option)
168 #==================================================================
169 -def set_option_in_INI_file(filename=None, group=None, option=None, value=None, encoding='utf8'):
170
171 _log.debug('setting option "%s" to "%s" in group [%s]', option, value, group)
172 _log.debug('file: %s (%s)', filename, encoding)
173
174 src = codecs.open(filename = filename, mode = 'rU', encoding = encoding)
175 # FIXME: add "." right before the *name* part of filename - this
176 # FIXME: requires proper parsing (think of /home/lala/ -> ./home/lala vs /home/lala/gnumed/.gnumed.conf)
177 sink_name = '%s.gmCfg2.new.conf' % filename
178 sink = codecs.open(filename = sink_name, mode = 'wb', encoding = encoding)
179
180 # is value a list ?
181 if isinstance(value, type([])):
182 __set_list_in_INI_file(src, sink, group, option, value)
183 else:
184 __set_opt_in_INI_file(src, sink, group, option, value)
185
186 sink.close()
187 src.close()
188
189 shutil.copy2(sink_name, filename)
190 os.remove(sink_name)
191 #==================================================================
193 """Parse an iterable for INI-style data.
194
195 Returns a dict by sections containing a dict of values per section.
196 """
197 _log.debug(u'parsing INI-style data stream [%s]' % stream)
198
199 data = {}
200 current_group = None
201 current_option = None
202 current_option_path = None
203 inside_list = False
204 line_idx = 0
205
206 for line in stream:
207 line = line.replace(u'\015', u'').replace(u'\012', u'').strip()
208 line_idx += 1
209
210 if inside_list:
211 if line == u'$%s$' % current_option: # end of list
212 inside_list = False
213 continue
214 data[current_option_path].append(line)
215 continue
216
217 # noise
218 if line == u'' or line.startswith(u'#') or line.startswith(u';'):
219 continue
220
221 # group
222 if line.startswith(u'['):
223 if not line.endswith(u']'):
224 _log.error(u'group line does not end in "]", aborting')
225 _log.error(line)
226 raise ValueError('INI-stream parsing error')
227 group = line.strip(u'[]').strip()
228 if group == u'':
229 _log.error(u'group name is empty, aborting')
230 _log.error(line)
231 raise ValueError('INI-stream parsing error')
232 current_group = group
233 continue
234
235 # option
236 if current_group is None:
237 _log.warning('option found before first group, ignoring')
238 _log.error(line)
239 continue
240
241 name, remainder = regex.split('\s*[=:]\s*', line, maxsplit = 1)
242 if name == u'':
243 _log.error('option name empty, aborting')
244 _log.error(line)
245 raise ValueError('INI-stream parsing error')
246
247 if remainder.strip() == u'':
248 if (u'=' not in line) and (u':' not in line):
249 _log.error('missing name/value separator (= or :), aborting')
250 _log.error(line)
251 raise ValueError('INI-stream parsing error')
252
253 current_option = name
254 current_option_path = '%s::%s' % (current_group, current_option)
255 if data.has_key(current_option_path):
256 _log.warning(u'duplicate option [%s]', current_option_path)
257
258 value = remainder.split(u'#', 1)[0].strip()
259
260 # start of list ?
261 if value == '$%s$' % current_option:
262 inside_list = True
263 data[current_option_path] = []
264 continue
265
266 data[current_option_path] = value
267
268 if inside_list:
269 _log.critical('unclosed list $%s$ detected at end of config stream [%s]', current_option, stream)
270 raise SyntaxError('end of config stream but still in list')
271
272 return data
273 #==================================================================
275
277 try:
278 self.__cfg_data
279 except AttributeError:
280 self.__cfg_data = {}
281 self.source_files = {}
282 #--------------------------------------------------
284 """Get the value of a configuration option in a config file.
285
286 <source_order> the order in which config files are searched
287 a list of tuples (source, policy)
288 policy:
289 return: return only this value immediately
290 append: append to list of potential values to return
291 extend: if the value per source happens to be a list
292 extend (rather than append to) the result list
293
294 returns NONE when there's no value for an option
295 """
296 if source_order is None:
297 source_order = [(u'internal', u'return')]
298 results = []
299 for source, policy in source_order:
300 if group is None:
301 group = source
302 option_path = u'%s::%s' % (group, option)
303 try: source_data = self.__cfg_data[source]
304 except KeyError:
305 _log.error('invalid config source [%s]', source)
306 _log.debug('currently known sources: %s', self.__cfg_data.keys())
307 #raise
308 continue
309
310 try: value = source_data[option_path]
311 except KeyError:
312 _log.debug('option [%s] not in group [%s] in source [%s]', option, group, source)
313 continue
314 _log.debug(u'option [%s] found in source [%s]', option_path, source)
315
316 if policy == u'return':
317 return value
318
319 if policy == u'extend':
320 if isinstance(value, types.ListType):
321 results.extend(value)
322 else:
323 results.append(value)
324 else:
325 results.append(value)
326
327 if len(results) == 0:
328 return None
329
330 return results
331 #--------------------------------------------------
333 """Set a particular option to a particular value.
334
335 Note that this does NOT PERSIST the option anywhere !
336 """
337 if None in [option, value]:
338 raise ValueError('neither <option> nor <value> can be None')
339 if source is None:
340 source = u'internal'
341 try:
342 self.__cfg_data[source]
343 except KeyError:
344 self.__cfg_data[source] = {}
345 if group is None:
346 group = source
347 option_path = u'%s::%s' % (group, option)
348 self.__cfg_data[source][option_path] = value
349 #--------------------------------------------------
350 # API: source related
351 #--------------------------------------------------
353
354 try:
355 data = parse_INI_stream(stream = stream)
356 except ValueError:
357 _log.exception('error parsing source <%s> from [%s]', source, stream)
358 raise
359
360 if self.__cfg_data.has_key(source):
361 _log.warning('overriding source <%s> with [%s]', source, stream)
362
363 self.__cfg_data[source] = data
364 #--------------------------------------------------
366 """Add a source (a file) to the instance."""
367
368 _log.info('file source "%s": %s (%s)', source, file, encoding)
369
370 for existing_source, existing_file in self.source_files.iteritems():
371 if existing_file == file:
372 if source != existing_source:
373 _log.warning('file [%s] already known as source [%s]', file, existing_source)
374 _log.warning('adding it as source [%s] may provoke trouble', source)
375
376 cfg_file = None
377 if file is not None:
378 try:
379 cfg_file = codecs.open(filename = file, mode = 'rU', encoding = encoding)
380 except IOError:
381 _log.error('cannot open [%s], keeping as dummy source', file)
382
383 if cfg_file is None:
384 file = None
385 if self.__cfg_data.has_key(source):
386 _log.warning('overriding source <%s> with dummy', source)
387 self.__cfg_data[source] = {}
388 else:
389 self.add_stream_source(source = source, stream = cfg_file)
390 cfg_file.close()
391
392 self.source_files[source] = file
393 #--------------------------------------------------
395 """Remove a source from the instance."""
396
397 _log.info('removing source <%s>', source)
398
399 try:
400 del self.__cfg_data[source]
401 except KeyError:
402 _log.warning("source <%s> doesn't exist", source)
403
404 try:
405 del self.source_files[source]
406 except KeyError:
407 pass
408 #--------------------------------------------------
410 if file not in self.source_files.values():
411 return
412
413 for src, fname in self.source_files.iteritems():
414 if fname == file:
415 self.add_file_source(source = src, file = fname, encoding = encoding)
416 # don't break the loop because there could be other sources
417 # with the same file (not very reasonable, I know)
418 #break
419 #--------------------------------------------------
421 """Add command line parameters to config data.
422
423 short:
424 string containing one-letter options such as u'h?' for -h -?
425 long:
426 list of strings
427 'conf-file=' -> --conf-file=<...>
428 'debug' -> --debug
429 """
430 _log.info('adding command line arguments')
431 _log.debug('raw command line is:')
432 _log.debug('%s', sys.argv)
433
434 import getopt
435
436 if long_options is None:
437 long_options = []
438
439 opts, remainder = getopt.gnu_getopt (
440 sys.argv[1:],
441 short_options,
442 long_options
443 )
444
445 data = {}
446 for opt, val in opts:
447 if val == u'':
448 data[u'%s::%s' % (u'cli', opt)] = True
449 else:
450 data[u'%s::%s' % (u'cli', opt)] = val
451
452 self.__cfg_data[u'cli'] = data
453 #==================================================================
454 # main
455 #==================================================================
456 if __name__ == "__main__":
457
458 logging.basicConfig(level = logging.DEBUG)
459 #-----------------------------------------
461 cfg = gmCfgData()
462 cfg.add_cli(short_options=u'h?', long_options=[u'help', u'conf-file='])
463 cfg.set_option('internal option', True)
464 print cfg.get(option = '--help', source_order = [('cli', 'return')])
465 print cfg.get(option = '-?', source_order = [('cli', 'return')])
466 fname = cfg.get(option = '--conf-file', source_order = [('cli', 'return')])
467 if fname is not None:
468 cfg.add_file_source(source = 'explicit', file = fname)
469 #-----------------------------------------
471 src = [
472 '# a comment',
473 '',
474 '[empty group]',
475 '[second group]',
476 'some option = in second group',
477 '# another comment',
478 '[test group]',
479 '',
480 'test list = $test list$',
481 'old 1',
482 'old 2',
483 '$test list$',
484 '# another group:',
485 '[dummy group]'
486 ]
487
488 __set_list_in_INI_file (
489 src = src,
490 sink = sys.stdout,
491 group = u'test group',
492 option = u'test list',
493 value = list('123')
494 )
495 #-----------------------------------------
497 src = [
498 '# a comment',
499 '[empty group]',
500 '# another comment',
501 '',
502 '[second group]',
503 'some option = in second group',
504 '',
505 '[trap group]',
506 'trap list = $trap list$',
507 'dummy 1',
508 'test option = a trap',
509 'dummy 2',
510 '$trap list$',
511 '',
512 '[test group]',
513 'test option = for real (old)',
514 ''
515 ]
516
517 __set_opt_in_INI_file (
518 src = src,
519 sink = sys.stdout,
520 group = u'test group',
521 option = u'test option',
522 value = u'for real (new)'
523 )
524 #-----------------------------------------
525 if len(sys.argv) > 1 and sys.argv[1] == 'test':
526 test_gmCfgData()
527 #test_set_list_opt()
528 #test_set_opt()
529
530 #==================================================================
531 # $Log: gmCfg2.py,v $
532 # Revision 1.20 2009-06-10 21:00:01 ncq
533 # - add remove-source
534 #
535 # Revision 1.19 2009/05/08 07:59:05 ncq
536 # - .panic -> .critical
537 #
538 # Revision 1.18 2008/09/09 20:15:42 ncq
539 # - warn on same-file different-source
540 #
541 # Revision 1.17 2008/08/31 16:12:12 ncq
542 # - when getting from multiple sources, if policy is "extend",
543 # flatten list options into a single result list
544 #
545 # Revision 1.16 2008/08/31 14:51:42 ncq
546 # - properly handle explicit file=None for dummy sources
547 #
548 # Revision 1.15 2008/08/03 20:03:11 ncq
549 # - do not simply add "." before the entire path of the dummy
550 # conf file when setting option - it should go right before the name part
551 #
552 # Revision 1.14 2008/07/17 21:30:01 ncq
553 # - detect unterminated list option
554 #
555 # Revision 1.13 2008/07/16 10:36:25 ncq
556 # - fix two bugs in INI parsing
557 # - better logging, some cleanup
558 # - .reload_file_source
559 #
560 # Revision 1.12 2008/07/07 11:33:57 ncq
561 # - a bit of cleanup
562 #
563 # Revision 1.11 2008/05/21 13:58:50 ncq
564 # - factor out add_stream_source from add_file_source
565 #
566 # Revision 1.10 2008/03/09 20:15:29 ncq
567 # - don't fail on non-existing sources
568 # - cleanup
569 # - better docs
570 #
571 # Revision 1.9 2008/01/27 21:09:38 ncq
572 # - set_option_in_INI_file() and tests
573 #
574 # Revision 1.8 2008/01/11 16:10:35 ncq
575 # - better logging
576 #
577 # Revision 1.7 2008/01/07 14:12:33 ncq
578 # - add some documentation to add_cli()
579 #
580 # Revision 1.6 2007/12/26 22:43:28 ncq
581 # - source order needs policy
582 #
583 # Revision 1.5 2007/12/26 21:50:45 ncq
584 # - missing continue
585 # - better test suite
586 #
587 # Revision 1.4 2007/12/26 21:11:11 ncq
588 # - need codecs
589 #
590 # Revision 1.3 2007/12/26 20:47:22 ncq
591 # - need to create internal source if doesn't exist
592 #
593 # Revision 1.2 2007/12/26 20:18:03 ncq
594 # - fix test suite
595 #
596 # Revision 1.1 2007/12/23 11:53:13 ncq
597 # - a much improved cfg options interface
598 # - no database handling yet
599 #
600 #
601
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Mon Dec 5 04:00:31 2011 | http://epydoc.sourceforge.net |