@@ -146,6 +146,43 @@ def __exit__(self, exception_type, exception_value, traceback):
146
146
self ._config .__exit__ (exception_type , exception_value , traceback )
147
147
148
148
149
+ class _OMD (OrderedDict ):
150
+ """Ordered multi-dict."""
151
+
152
+ def __setitem__ (self , key , value ):
153
+ super (_OMD , self ).__setitem__ (key , [value ])
154
+
155
+ def add (self , key , value ):
156
+ if key not in self :
157
+ super (_OMD , self ).__setitem__ (key , [value ])
158
+ return
159
+
160
+ super (_OMD , self ).__getitem__ (key ).append (value )
161
+
162
+ def setall (self , key , values ):
163
+ super (_OMD , self ).__setitem__ (key , values )
164
+
165
+ def __getitem__ (self , key ):
166
+ return super (_OMD , self ).__getitem__ (key )[- 1 ]
167
+
168
+ def getlast (self , key ):
169
+ return super (_OMD , self ).__getitem__ (key )[- 1 ]
170
+
171
+ def setlast (self , key , value ):
172
+ if key not in self :
173
+ super (_OMD , self ).__setitem__ (key , [value ])
174
+ return
175
+
176
+ prior = super (_OMD , self ).__getitem__ (key )
177
+ prior [- 1 ] = value
178
+
179
+ def getall (self , key ):
180
+ return super (_OMD , self ).__getitem__ (key )
181
+
182
+ def items_all (self ):
183
+ return [(k , self .get (k )) for k in self ]
184
+
185
+
149
186
class GitConfigParser (with_metaclass (MetaParserBuilder , cp .RawConfigParser , object )):
150
187
151
188
"""Implements specifics required to read git style configuration files.
@@ -200,7 +237,7 @@ def __init__(self, file_or_files, read_only=True, merge_includes=True):
200
237
contents into ours. This makes it impossible to write back an individual configuration file.
201
238
Thus, if you want to modify a single configuration file, turn this off to leave the original
202
239
dataset unaltered when reading it."""
203
- cp .RawConfigParser .__init__ (self , dict_type = OrderedDict )
240
+ cp .RawConfigParser .__init__ (self , dict_type = _OMD )
204
241
205
242
# Used in python 3, needs to stay in sync with sections for underlying implementation to work
206
243
if not hasattr (self , '_proxies' ):
@@ -348,7 +385,8 @@ def string_decode(v):
348
385
is_multi_line = True
349
386
optval = string_decode (optval [1 :])
350
387
# end handle multi-line
351
- cursect [optname ] = optval
388
+ # preserves multiple values for duplicate optnames
389
+ cursect .add (optname , optval )
352
390
else :
353
391
# check if it's an option with no value - it's just ignored by git
354
392
if not self .OPTVALUEONLY .match (line ):
@@ -362,7 +400,8 @@ def string_decode(v):
362
400
is_multi_line = False
363
401
line = line [:- 1 ]
364
402
# end handle quotations
365
- cursect [optname ] += string_decode (line )
403
+ optval = cursect .getlast (optname )
404
+ cursect .setlast (optname , optval + string_decode (line ))
366
405
# END parse section or option
367
406
# END while reading
368
407
@@ -442,9 +481,17 @@ def _write(self, fp):
442
481
git compatible format"""
443
482
def write_section (name , section_dict ):
444
483
fp .write (("[%s]\n " % name ).encode (defenc ))
445
- for (key , value ) in section_dict .items ():
446
- if key != "__name__" :
447
- fp .write (("\t %s = %s\n " % (key , self ._value_to_string (value ).replace ('\n ' , '\n \t ' ))).encode (defenc ))
484
+ for (key , value ) in section_dict .items_all ():
485
+ if key == "__name__" :
486
+ continue
487
+ elif isinstance (value , list ):
488
+ values = value
489
+ else :
490
+ # self._defaults isn't a multidict
491
+ values = [value ]
492
+
493
+ for v in values :
494
+ fp .write (("\t %s = %s\n " % (key , self ._value_to_string (v ).replace ('\n ' , '\n \t ' ))).encode (defenc ))
448
495
# END if key is not __name__
449
496
# END section writing
450
497
@@ -457,6 +504,27 @@ def items(self, section_name):
457
504
""":return: list((option, value), ...) pairs of all items in the given section"""
458
505
return [(k , v ) for k , v in super (GitConfigParser , self ).items (section_name ) if k != '__name__' ]
459
506
507
+ def items_all (self , section_name ):
508
+ """:return: list((option, [values...]), ...) pairs of all items in the given section"""
509
+ rv = OrderedDict ()
510
+ for k , v in self ._defaults :
511
+ rv [k ] = [v ]
512
+
513
+ for k , v in self ._sections [section_name ].items_all ():
514
+ if k == '__name__' :
515
+ continue
516
+
517
+ if k not in rv :
518
+ rv [k ] = v
519
+ continue
520
+
521
+ if rv [k ] == v :
522
+ continue
523
+
524
+ rv [k ].extend (v )
525
+
526
+ return rv .items ()
527
+
460
528
@needs_values
461
529
def write (self ):
462
530
"""Write changes to our file, if there are changes at all
@@ -508,7 +576,11 @@ def read_only(self):
508
576
return self ._read_only
509
577
510
578
def get_value (self , section , option , default = None ):
511
- """
579
+ """Get an option's value.
580
+
581
+ If multiple values are specified for this option in the section, the
582
+ last one specified is returned.
583
+
512
584
:param default:
513
585
If not None, the given default value will be returned in case
514
586
the option did not exist
@@ -523,6 +595,31 @@ def get_value(self, section, option, default=None):
523
595
return default
524
596
raise
525
597
598
+ return self ._string_to_value (valuestr )
599
+
600
+ def get_values (self , section , option , default = None ):
601
+ """Get an option's values.
602
+
603
+ If multiple values are specified for this option in the section, all are
604
+ returned.
605
+
606
+ :param default:
607
+ If not None, a list containing the given default value will be
608
+ returned in case the option did not exist
609
+ :return: a list of properly typed values, either int, float or string
610
+
611
+ :raise TypeError: in case the value could not be understood
612
+ Otherwise the exceptions known to the ConfigParser will be raised."""
613
+ try :
614
+ lst = self ._sections [section ].getall (option )
615
+ except Exception :
616
+ if default is not None :
617
+ return [default ]
618
+ raise
619
+
620
+ return [self ._string_to_value (valuestr ) for valuestr in lst ]
621
+
622
+ def _string_to_value (self , valuestr ):
526
623
types = (int , float )
527
624
for numtype in types :
528
625
try :
@@ -545,7 +642,9 @@ def get_value(self, section, option, default=None):
545
642
return True
546
643
547
644
if not isinstance (valuestr , string_types ):
548
- raise TypeError ("Invalid value type: only int, long, float and str are allowed" , valuestr )
645
+ raise TypeError (
646
+ "Invalid value type: only int, long, float and str are allowed" ,
647
+ valuestr )
549
648
550
649
return valuestr
551
650
@@ -572,6 +671,25 @@ def set_value(self, section, option, value):
572
671
self .set (section , option , self ._value_to_string (value ))
573
672
return self
574
673
674
+ @needs_values
675
+ @set_dirty_and_flush_changes
676
+ def add_value (self , section , option , value ):
677
+ """Adds a value for the given option in section.
678
+ It will create the section if required, and will not throw as opposed to the default
679
+ ConfigParser 'set' method. The value becomes the new value of the option as returned
680
+ by 'get_value', and appends to the list of values returned by 'get_values`'.
681
+
682
+ :param section: Name of the section in which the option resides or should reside
683
+ :param option: Name of the option
684
+
685
+ :param value: Value to add to option. It must be a string or convertible
686
+ to a string
687
+ :return: this instance"""
688
+ if not self .has_section (section ):
689
+ self .add_section (section )
690
+ self ._sections [section ].add (option , self ._value_to_string (value ))
691
+ return self
692
+
575
693
def rename_section (self , section , new_name ):
576
694
"""rename the given section to new_name
577
695
:raise ValueError: if section doesn't exit
@@ -584,8 +702,9 @@ def rename_section(self, section, new_name):
584
702
raise ValueError ("Destination section '%s' already exists" % new_name )
585
703
586
704
super (GitConfigParser , self ).add_section (new_name )
587
- for k , v in self .items (section ):
588
- self .set (new_name , k , self ._value_to_string (v ))
705
+ new_section = self ._sections [new_name ]
706
+ for k , vs in self .items_all (section ):
707
+ new_section .setall (k , vs )
589
708
# end for each value to copy
590
709
591
710
# This call writes back the changes, which is why we don't have the respective decorator
0 commit comments