@@ -146,6 +146,51 @@ 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 get (self , key , default = None ):
180
+ return super (_OMD , self ).get (key , [default ])[- 1 ]
181
+
182
+ def getall (self , key ):
183
+ return super (_OMD , self ).__getitem__ (key )
184
+
185
+ def items (self ):
186
+ """List of (key, last value for key)."""
187
+ return [(k , self [k ]) for k in self ]
188
+
189
+ def items_all (self ):
190
+ """List of (key, list of values for key)."""
191
+ return [(k , self .getall (k )) for k in self ]
192
+
193
+
149
194
class GitConfigParser (with_metaclass (MetaParserBuilder , cp .RawConfigParser , object )):
150
195
151
196
"""Implements specifics required to read git style configuration files.
@@ -200,7 +245,7 @@ def __init__(self, file_or_files, read_only=True, merge_includes=True):
200
245
contents into ours. This makes it impossible to write back an individual configuration file.
201
246
Thus, if you want to modify a single configuration file, turn this off to leave the original
202
247
dataset unaltered when reading it."""
203
- cp .RawConfigParser .__init__ (self , dict_type = OrderedDict )
248
+ cp .RawConfigParser .__init__ (self , dict_type = _OMD )
204
249
205
250
# Used in python 3, needs to stay in sync with sections for underlying implementation to work
206
251
if not hasattr (self , '_proxies' ):
@@ -348,7 +393,8 @@ def string_decode(v):
348
393
is_multi_line = True
349
394
optval = string_decode (optval [1 :])
350
395
# end handle multi-line
351
- cursect [optname ] = optval
396
+ # preserves multiple values for duplicate optnames
397
+ cursect .add (optname , optval )
352
398
else :
353
399
# check if it's an option with no value - it's just ignored by git
354
400
if not self .OPTVALUEONLY .match (line ):
@@ -362,7 +408,8 @@ def string_decode(v):
362
408
is_multi_line = False
363
409
line = line [:- 1 ]
364
410
# end handle quotations
365
- cursect [optname ] += string_decode (line )
411
+ optval = cursect .getlast (optname )
412
+ cursect .setlast (optname , optval + string_decode (line ))
366
413
# END parse section or option
367
414
# END while reading
368
415
@@ -442,9 +489,12 @@ def _write(self, fp):
442
489
git compatible format"""
443
490
def write_section (name , section_dict ):
444
491
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 ))
492
+ for (key , values ) in section_dict .items_all ():
493
+ if key == "__name__" :
494
+ continue
495
+
496
+ for v in values :
497
+ fp .write (("\t %s = %s\n " % (key , self ._value_to_string (v ).replace ('\n ' , '\n \t ' ))).encode (defenc ))
448
498
# END if key is not __name__
449
499
# END section writing
450
500
@@ -457,6 +507,22 @@ def items(self, section_name):
457
507
""":return: list((option, value), ...) pairs of all items in the given section"""
458
508
return [(k , v ) for k , v in super (GitConfigParser , self ).items (section_name ) if k != '__name__' ]
459
509
510
+ def items_all (self , section_name ):
511
+ """:return: list((option, [values...]), ...) pairs of all items in the given section"""
512
+ rv = _OMD (self ._defaults )
513
+
514
+ for k , vs in self ._sections [section_name ].items_all ():
515
+ if k == '__name__' :
516
+ continue
517
+
518
+ if k in rv and rv .getall (k ) == vs :
519
+ continue
520
+
521
+ for v in vs :
522
+ rv .add (k , v )
523
+
524
+ return rv .items_all ()
525
+
460
526
@needs_values
461
527
def write (self ):
462
528
"""Write changes to our file, if there are changes at all
@@ -508,7 +574,11 @@ def read_only(self):
508
574
return self ._read_only
509
575
510
576
def get_value (self , section , option , default = None ):
511
- """
577
+ """Get an option's value.
578
+
579
+ If multiple values are specified for this option in the section, the
580
+ last one specified is returned.
581
+
512
582
:param default:
513
583
If not None, the given default value will be returned in case
514
584
the option did not exist
@@ -523,6 +593,31 @@ def get_value(self, section, option, default=None):
523
593
return default
524
594
raise
525
595
596
+ return self ._string_to_value (valuestr )
597
+
598
+ def get_values (self , section , option , default = None ):
599
+ """Get an option's values.
600
+
601
+ If multiple values are specified for this option in the section, all are
602
+ returned.
603
+
604
+ :param default:
605
+ If not None, a list containing the given default value will be
606
+ returned in case the option did not exist
607
+ :return: a list of properly typed values, either int, float or string
608
+
609
+ :raise TypeError: in case the value could not be understood
610
+ Otherwise the exceptions known to the ConfigParser will be raised."""
611
+ try :
612
+ lst = self ._sections [section ].getall (option )
613
+ except Exception :
614
+ if default is not None :
615
+ return [default ]
616
+ raise
617
+
618
+ return [self ._string_to_value (valuestr ) for valuestr in lst ]
619
+
620
+ def _string_to_value (self , valuestr ):
526
621
types = (int , float )
527
622
for numtype in types :
528
623
try :
@@ -545,7 +640,9 @@ def get_value(self, section, option, default=None):
545
640
return True
546
641
547
642
if not isinstance (valuestr , string_types ):
548
- raise TypeError ("Invalid value type: only int, long, float and str are allowed" , valuestr )
643
+ raise TypeError (
644
+ "Invalid value type: only int, long, float and str are allowed" ,
645
+ valuestr )
549
646
550
647
return valuestr
551
648
@@ -572,6 +669,25 @@ def set_value(self, section, option, value):
572
669
self .set (section , option , self ._value_to_string (value ))
573
670
return self
574
671
672
+ @needs_values
673
+ @set_dirty_and_flush_changes
674
+ def add_value (self , section , option , value ):
675
+ """Adds a value for the given option in section.
676
+ It will create the section if required, and will not throw as opposed to the default
677
+ ConfigParser 'set' method. The value becomes the new value of the option as returned
678
+ by 'get_value', and appends to the list of values returned by 'get_values`'.
679
+
680
+ :param section: Name of the section in which the option resides or should reside
681
+ :param option: Name of the option
682
+
683
+ :param value: Value to add to option. It must be a string or convertible
684
+ to a string
685
+ :return: this instance"""
686
+ if not self .has_section (section ):
687
+ self .add_section (section )
688
+ self ._sections [section ].add (option , self ._value_to_string (value ))
689
+ return self
690
+
575
691
def rename_section (self , section , new_name ):
576
692
"""rename the given section to new_name
577
693
:raise ValueError: if section doesn't exit
@@ -584,8 +700,9 @@ def rename_section(self, section, new_name):
584
700
raise ValueError ("Destination section '%s' already exists" % new_name )
585
701
586
702
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 ))
703
+ new_section = self ._sections [new_name ]
704
+ for k , vs in self .items_all (section ):
705
+ new_section .setall (k , vs )
589
706
# end for each value to copy
590
707
591
708
# This call writes back the changes, which is why we don't have the respective decorator
0 commit comments