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