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

Commit5cd12a6

Browse files
author
Sebastian Thiel
committed
Merge branch 'multi-value' ofhttps://github.com/ajdavis/GitPython into ajdavis-multi-value
2 parents6971a93 +4106f18 commit5cd12a6

File tree

4 files changed

+243
-11
lines changed

4 files changed

+243
-11
lines changed

‎AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,6 @@ Contributors are:
2929
-Tim Swast <swast _at_ google.com>
3030
-William Luc Ritchie
3131
-David Host <hostdm _at_ outlook.com>
32+
-A. Jesse Jiryu Davis <jesse _at_ emptysquare.net>
3233

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

‎git/config.py

Lines changed: 127 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,51 @@ 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+
defget(self,key,default=None):
180+
returnsuper(_OMD,self).get(key, [default])[-1]
181+
182+
defgetall(self,key):
183+
returnsuper(_OMD,self).__getitem__(key)
184+
185+
defitems(self):
186+
"""List of (key, last value for key)."""
187+
return [(k,self[k])forkinself]
188+
189+
defitems_all(self):
190+
"""List of (key, list of values for key)."""
191+
return [(k,self.getall(k))forkinself]
192+
193+
149194
classGitConfigParser(with_metaclass(MetaParserBuilder,cp.RawConfigParser,object)):
150195

151196
"""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):
200245
contents into ours. This makes it impossible to write back an individual configuration file.
201246
Thus, if you want to modify a single configuration file, turn this off to leave the original
202247
dataset unaltered when reading it."""
203-
cp.RawConfigParser.__init__(self,dict_type=OrderedDict)
248+
cp.RawConfigParser.__init__(self,dict_type=_OMD)
204249

205250
# Used in python 3, needs to stay in sync with sections for underlying implementation to work
206251
ifnothasattr(self,'_proxies'):
@@ -348,7 +393,8 @@ def string_decode(v):
348393
is_multi_line=True
349394
optval=string_decode(optval[1:])
350395
# end handle multi-line
351-
cursect[optname]=optval
396+
# preserves multiple values for duplicate optnames
397+
cursect.add(optname,optval)
352398
else:
353399
# check if it's an option with no value - it's just ignored by git
354400
ifnotself.OPTVALUEONLY.match(line):
@@ -362,7 +408,8 @@ def string_decode(v):
362408
is_multi_line=False
363409
line=line[:-1]
364410
# end handle quotations
365-
cursect[optname]+=string_decode(line)
411+
optval=cursect.getlast(optname)
412+
cursect.setlast(optname,optval+string_decode(line))
366413
# END parse section or option
367414
# END while reading
368415

@@ -442,9 +489,12 @@ def _write(self, fp):
442489
git compatible format"""
443490
defwrite_section(name,section_dict):
444491
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))
492+
for (key,values)insection_dict.items_all():
493+
ifkey=="__name__":
494+
continue
495+
496+
forvinvalues:
497+
fp.write(("\t%s = %s\n"% (key,self._value_to_string(v).replace('\n','\n\t'))).encode(defenc))
448498
# END if key is not __name__
449499
# END section writing
450500

@@ -457,6 +507,22 @@ def items(self, section_name):
457507
""":return: list((option, value), ...) pairs of all items in the given section"""
458508
return [(k,v)fork,vinsuper(GitConfigParser,self).items(section_name)ifk!='__name__']
459509

510+
defitems_all(self,section_name):
511+
""":return: list((option, [values...]), ...) pairs of all items in the given section"""
512+
rv=_OMD(self._defaults)
513+
514+
fork,vsinself._sections[section_name].items_all():
515+
ifk=='__name__':
516+
continue
517+
518+
ifkinrvandrv.getall(k)==vs:
519+
continue
520+
521+
forvinvs:
522+
rv.add(k,v)
523+
524+
returnrv.items_all()
525+
460526
@needs_values
461527
defwrite(self):
462528
"""Write changes to our file, if there are changes at all
@@ -508,7 +574,11 @@ def read_only(self):
508574
returnself._read_only
509575

510576
defget_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+
512582
:param default:
513583
If not None, the given default value will be returned in case
514584
the option did not exist
@@ -523,6 +593,31 @@ def get_value(self, section, option, default=None):
523593
returndefault
524594
raise
525595

596+
returnself._string_to_value(valuestr)
597+
598+
defget_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+
exceptException:
614+
ifdefaultisnotNone:
615+
return [default]
616+
raise
617+
618+
return [self._string_to_value(valuestr)forvaluestrinlst]
619+
620+
def_string_to_value(self,valuestr):
526621
types= (int,float)
527622
fornumtypeintypes:
528623
try:
@@ -545,7 +640,9 @@ def get_value(self, section, option, default=None):
545640
returnTrue
546641

547642
ifnotisinstance(valuestr,string_types):
548-
raiseTypeError("Invalid value type: only int, long, float and str are allowed",valuestr)
643+
raiseTypeError(
644+
"Invalid value type: only int, long, float and str are allowed",
645+
valuestr)
549646

550647
returnvaluestr
551648

@@ -572,6 +669,25 @@ def set_value(self, section, option, value):
572669
self.set(section,option,self._value_to_string(value))
573670
returnself
574671

672+
@needs_values
673+
@set_dirty_and_flush_changes
674+
defadd_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+
ifnotself.has_section(section):
687+
self.add_section(section)
688+
self._sections[section].add(option,self._value_to_string(value))
689+
returnself
690+
575691
defrename_section(self,section,new_name):
576692
"""rename the given section to new_name
577693
:raise ValueError: if section doesn't exit
@@ -584,8 +700,9 @@ def rename_section(self, section, new_name):
584700
raiseValueError("Destination section '%s' already exists"%new_name)
585701

586702
super(GitConfigParser,self).add_section(new_name)
587-
fork,vinself.items(section):
588-
self.set(new_name,k,self._value_to_string(v))
703+
new_section=self._sections[new_name]
704+
fork,vsinself.items_all(section):
705+
new_section.setall(k,vs)
589706
# end for each value to copy
590707

591708
# 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: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
GitConfigParser
1212
)
1313
fromgit.compatimportstring_types
14-
fromgit.configimportcp
14+
fromgit.configimport_OMD,cp
1515
fromgit.test.libimport (
1616
TestCase,
1717
fixture_path,
@@ -265,3 +265,110 @@ 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'])])
365+
366+
deftest_setlast(self):
367+
# Test directly, not covered by higher-level tests.
368+
omd=_OMD()
369+
omd.setlast('key','value1')
370+
self.assertEqual(omd['key'],'value1')
371+
self.assertEqual(omd.getall('key'), ['value1'])
372+
omd.setlast('key','value2')
373+
self.assertEqual(omd['key'],'value2')
374+
self.assertEqual(omd.getall('key'), ['value2'])

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp