@@ -146,6 +146,43 @@ def __exit__(self, exception_type, exception_value, traceback):
146146        self ._config .__exit__ (exception_type , exception_value , traceback )
147147
148148
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+ 
149186class  GitConfigParser (with_metaclass (MetaParserBuilder , cp .RawConfigParser , object )):
150187
151188    """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):
200237            contents into ours. This makes it impossible to write back an individual configuration file. 
201238            Thus, if you want to modify a single configuration file, turn this off to leave the original 
202239            dataset unaltered when reading it.""" 
203-         cp .RawConfigParser .__init__ (self , dict_type = OrderedDict )
240+         cp .RawConfigParser .__init__ (self , dict_type = _OMD )
204241
205242        # Used in python 3, needs to stay in sync with sections for underlying implementation to work 
206243        if  not  hasattr (self , '_proxies' ):
@@ -348,7 +385,8 @@ def string_decode(v):
348385                        is_multi_line  =  True 
349386                        optval  =  string_decode (optval [1 :])
350387                    # end handle multi-line 
351-                     cursect [optname ] =  optval 
388+                     # preserves multiple values for duplicate optnames 
389+                     cursect .add (optname , optval )
352390                else :
353391                    # check if it's an option with no value - it's just ignored by git 
354392                    if  not  self .OPTVALUEONLY .match (line ):
@@ -362,7 +400,8 @@ def string_decode(v):
362400                    is_multi_line  =  False 
363401                    line  =  line [:- 1 ]
364402                # end handle quotations 
365-                 cursect [optname ] +=  string_decode (line )
403+                 optval  =  cursect .getlast (optname )
404+                 cursect .setlast (optname , optval  +  string_decode (line ))
366405            # END parse section or option 
367406        # END while reading 
368407
@@ -442,9 +481,17 @@ def _write(self, fp):
442481        git compatible format""" 
443482        def  write_section (name , section_dict ):
444483            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 ))
448495                # END if key is not __name__ 
449496        # END section writing 
450497
@@ -457,6 +504,27 @@ def items(self, section_name):
457504        """:return: list((option, value), ...) pairs of all items in the given section""" 
458505        return  [(k , v ) for  k , v  in  super (GitConfigParser , self ).items (section_name ) if  k  !=  '__name__' ]
459506
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+ 
460528    @needs_values  
461529    def  write (self ):
462530        """Write changes to our file, if there are changes at all 
@@ -508,7 +576,11 @@ def read_only(self):
508576        return  self ._read_only 
509577
510578    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+ 
512584        :param default: 
513585            If not None, the given default value will be returned in case 
514586            the option did not exist 
@@ -523,6 +595,31 @@ def get_value(self, section, option, default=None):
523595                return  default 
524596            raise 
525597
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 ):
526623        types  =  (int , float )
527624        for  numtype  in  types :
528625            try :
@@ -545,7 +642,9 @@ def get_value(self, section, option, default=None):
545642            return  True 
546643
547644        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 )
549648
550649        return  valuestr 
551650
@@ -572,6 +671,25 @@ def set_value(self, section, option, value):
572671        self .set (section , option , self ._value_to_string (value ))
573672        return  self 
574673
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+ 
575693    def  rename_section (self , section , new_name ):
576694        """rename the given section to new_name 
577695        :raise ValueError: if section doesn't exit 
@@ -584,8 +702,9 @@ def rename_section(self, section, new_name):
584702            raise  ValueError ("Destination section '%s' already exists"  %  new_name )
585703
586704        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 )
589708        # end for each value to copy 
590709
591710        # This call writes back the changes, which is why we don't have the respective decorator 
0 commit comments