Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitf7bbe6b

Browse files
committed
Support multiple git config values per option
Solves#717
1 parent7a6ca8c commitf7bbe6b

File tree

4 files changed

+234
-10
lines changed

4 files changed

+234
-10
lines changed

‎AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,6 @@ Contributors are:
2828
-Yaroslav Halchenko <debian _at_ onerussian.com>
2929
-Tim Swast <swast _at_ google.com>
3030
-William Luc Ritchie
31+
-A. Jesse Jiryu Davis <jesse _at_ emptysquare.net>
3132

3233
Portions derived from other open source works and are clearly marked.

‎git/config.py

Lines changed: 129 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
defadd(self,key,value):
156+
ifkeynotinself:
157+
super(_OMD,self).__setitem__(key, [value])
158+
return
159+
160+
super(_OMD,self).__getitem__(key).append(value)
161+
162+
defsetall(self,key,values):
163+
super(_OMD,self).__setitem__(key,values)
164+
165+
def__getitem__(self,key):
166+
returnsuper(_OMD,self).__getitem__(key)[-1]
167+
168+
defgetlast(self,key):
169+
returnsuper(_OMD,self).__getitem__(key)[-1]
170+
171+
defsetlast(self,key,value):
172+
ifkeynotinself:
173+
super(_OMD,self).__setitem__(key, [value])
174+
return
175+
176+
prior=super(_OMD,self).__getitem__(key)
177+
prior[-1]=value
178+
179+
defgetall(self,key):
180+
returnsuper(_OMD,self).__getitem__(key)
181+
182+
defitems_all(self):
183+
return [(k,self.get(k))forkinself]
184+
185+
149186
classGitConfigParser(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
ifnothasattr(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
ifnotself.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
defwrite_section(name,section_dict):
444483
fp.write(("[%s]\n"%name).encode(defenc))
445-
for (key,value)insection_dict.items():
446-
ifkey!="__name__":
447-
fp.write(("\t%s = %s\n"% (key,self._value_to_string(value).replace('\n','\n\t'))).encode(defenc))
484+
for (key,value)insection_dict.items_all():
485+
ifkey=="__name__":
486+
continue
487+
elifisinstance(value,list):
488+
values=value
489+
else:
490+
# self._defaults isn't a multidict
491+
values= [value]
492+
493+
forvinvalues:
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)fork,vinsuper(GitConfigParser,self).items(section_name)ifk!='__name__']
459506

507+
defitems_all(self,section_name):
508+
""":return: list((option, [values...]), ...) pairs of all items in the given section"""
509+
rv=OrderedDict()
510+
fork,vinself._defaults:
511+
rv[k]= [v]
512+
513+
fork,vinself._sections[section_name].items_all():
514+
ifk=='__name__':
515+
continue
516+
517+
ifknotinrv:
518+
rv[k]=v
519+
continue
520+
521+
ifrv[k]==v:
522+
continue
523+
524+
rv[k].extend(v)
525+
526+
returnrv.items()
527+
460528
@needs_values
461529
defwrite(self):
462530
"""Write changes to our file, if there are changes at all
@@ -508,7 +576,11 @@ def read_only(self):
508576
returnself._read_only
509577

510578
defget_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
returndefault
524596
raise
525597

598+
returnself._string_to_value(valuestr)
599+
600+
defget_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+
exceptException:
616+
ifdefaultisnotNone:
617+
return [default]
618+
raise
619+
620+
return [self._string_to_value(valuestr)forvaluestrinlst]
621+
622+
def_string_to_value(self,valuestr):
526623
types= (int,float)
527624
fornumtypeintypes:
528625
try:
@@ -545,7 +642,9 @@ def get_value(self, section, option, default=None):
545642
returnTrue
546643

547644
ifnotisinstance(valuestr,string_types):
548-
raiseTypeError("Invalid value type: only int, long, float and str are allowed",valuestr)
645+
raiseTypeError(
646+
"Invalid value type: only int, long, float and str are allowed",
647+
valuestr)
549648

550649
returnvaluestr
551650

@@ -572,6 +671,25 @@ def set_value(self, section, option, value):
572671
self.set(section,option,self._value_to_string(value))
573672
returnself
574673

674+
@needs_values
675+
@set_dirty_and_flush_changes
676+
defadd_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+
ifnotself.has_section(section):
689+
self.add_section(section)
690+
self._sections[section].add(option,self._value_to_string(value))
691+
returnself
692+
575693
defrename_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
raiseValueError("Destination section '%s' already exists"%new_name)
585703

586704
super(GitConfigParser,self).add_section(new_name)
587-
fork,vinself.items(section):
588-
self.set(new_name,k,self._value_to_string(v))
705+
new_section=self._sections[new_name]
706+
fork,vsinself.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

‎git/test/fixtures/git_config_multiple

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[section0]
2+
option0 = value0
3+
4+
[section1]
5+
option1 = value1a
6+
option1 = value1b
7+
other_option1 = other_value1

‎git/test/test_config.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,3 +265,100 @@ def test_empty_config_value(self):
265265

266266
withself.assertRaises(cp.NoOptionError):
267267
cr.get_value('color','ui')
268+
269+
deftest_multiple_values(self):
270+
file_obj=self._to_memcache(fixture_path('git_config_multiple'))
271+
withGitConfigParser(file_obj,read_only=False)ascw:
272+
self.assertEqual(cw.get('section0','option0'),'value0')
273+
self.assertEqual(cw.get_values('section0','option0'), ['value0'])
274+
self.assertEqual(cw.items('section0'), [('option0','value0')])
275+
276+
# Where there are multiple values, "get" returns the last.
277+
self.assertEqual(cw.get('section1','option1'),'value1b')
278+
self.assertEqual(cw.get_values('section1','option1'),
279+
['value1a','value1b'])
280+
self.assertEqual(cw.items('section1'),
281+
[('option1','value1b'),
282+
('other_option1','other_value1')])
283+
self.assertEqual(cw.items_all('section1'),
284+
[('option1', ['value1a','value1b']),
285+
('other_option1', ['other_value1'])])
286+
withself.assertRaises(KeyError):
287+
cw.get_values('section1','missing')
288+
289+
self.assertEqual(cw.get_values('section1','missing',1), [1])
290+
self.assertEqual(cw.get_values('section1','missing','s'), ['s'])
291+
292+
deftest_multiple_values_rename(self):
293+
file_obj=self._to_memcache(fixture_path('git_config_multiple'))
294+
withGitConfigParser(file_obj,read_only=False)ascw:
295+
cw.rename_section('section1','section2')
296+
cw.write()
297+
file_obj.seek(0)
298+
cr=GitConfigParser(file_obj,read_only=True)
299+
self.assertEqual(cr.get_value('section2','option1'),'value1b')
300+
self.assertEqual(cr.get_values('section2','option1'),
301+
['value1a','value1b'])
302+
self.assertEqual(cr.items('section2'),
303+
[('option1','value1b'),
304+
('other_option1','other_value1')])
305+
self.assertEqual(cr.items_all('section2'),
306+
[('option1', ['value1a','value1b']),
307+
('other_option1', ['other_value1'])])
308+
309+
deftest_multiple_to_single(self):
310+
file_obj=self._to_memcache(fixture_path('git_config_multiple'))
311+
withGitConfigParser(file_obj,read_only=False)ascw:
312+
cw.set_value('section1','option1','value1c')
313+
314+
cw.write()
315+
file_obj.seek(0)
316+
cr=GitConfigParser(file_obj,read_only=True)
317+
self.assertEqual(cr.get_value('section1','option1'),'value1c')
318+
self.assertEqual(cr.get_values('section1','option1'), ['value1c'])
319+
self.assertEqual(cr.items('section1'),
320+
[('option1','value1c'),
321+
('other_option1','other_value1')])
322+
self.assertEqual(cr.items_all('section1'),
323+
[('option1', ['value1c']),
324+
('other_option1', ['other_value1'])])
325+
326+
deftest_single_to_multiple(self):
327+
file_obj=self._to_memcache(fixture_path('git_config_multiple'))
328+
withGitConfigParser(file_obj,read_only=False)ascw:
329+
cw.add_value('section1','other_option1','other_value1a')
330+
331+
cw.write()
332+
file_obj.seek(0)
333+
cr=GitConfigParser(file_obj,read_only=True)
334+
self.assertEqual(cr.get_value('section1','option1'),'value1b')
335+
self.assertEqual(cr.get_values('section1','option1'),
336+
['value1a','value1b'])
337+
self.assertEqual(cr.get_value('section1','other_option1'),
338+
'other_value1a')
339+
self.assertEqual(cr.get_values('section1','other_option1'),
340+
['other_value1','other_value1a'])
341+
self.assertEqual(cr.items('section1'),
342+
[('option1','value1b'),
343+
('other_option1','other_value1a')])
344+
self.assertEqual(
345+
cr.items_all('section1'),
346+
[('option1', ['value1a','value1b']),
347+
('other_option1', ['other_value1','other_value1a'])])
348+
349+
deftest_add_to_multiple(self):
350+
file_obj=self._to_memcache(fixture_path('git_config_multiple'))
351+
withGitConfigParser(file_obj,read_only=False)ascw:
352+
cw.add_value('section1','option1','value1c')
353+
cw.write()
354+
file_obj.seek(0)
355+
cr=GitConfigParser(file_obj,read_only=True)
356+
self.assertEqual(cr.get_value('section1','option1'),'value1c')
357+
self.assertEqual(cr.get_values('section1','option1'),
358+
['value1a','value1b','value1c'])
359+
self.assertEqual(cr.items('section1'),
360+
[('option1','value1c'),
361+
('other_option1','other_value1')])
362+
self.assertEqual(cr.items_all('section1'),
363+
[('option1', ['value1a','value1b','value1c']),
364+
('other_option1', ['other_value1'])])

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp