Skip to content

Commit aa66d10

Browse files
committed
Merge pull request #183 from kmosher/master
Update the config parser using code from python2.7
2 parents 6d3d94c + f72477a commit aa66d10

File tree

3 files changed

+73
-54
lines changed

3 files changed

+73
-54
lines changed

‎git/config.py‎

Lines changed: 52 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -131,19 +131,20 @@ class GitConfigParser(cp.RawConfigParser, object):
131131

132132
OPTCRE=re.compile(
133133
r'\s*(?P<option>[^:=\s][^:=]*)'# very permissive, incuding leading whitespace
134-
r'\s*(?P<vi>[:=])\s*'# any number of space/tab,
135-
# followed by separator
136-
# (either : or =), followed
137-
# by any # space/tab
138-
r'(?P<value>.*)$'# everything up to eol
139-
)
134+
r'\s*(?:'# any number of space/tab,
135+
r'(?P<vi>[:=])\s*'# optionally followed by
136+
# separator (either : or
137+
# =), followed by any #
138+
# space/tab
139+
r'(?P<value>.*))?$'# everything up to eol
140+
)
140141

141142
# list of RawConfigParser methods able to change the instance
142143
_mutating_methods_= ("add_section", "remove_section", "remove_option", "set")
143144
__slots__= ("_sections", "_defaults", "_file_or_files", "_read_only", "_is_initialized", '_lock')
144145

145146
def__init__(self, file_or_files, read_only=True):
146-
"""Initialize a configuration reader to read the given file_or_files and to
147+
"""Initialize a configuration reader to read the given file_or_files and to
147148
possibly allow changes to it by setting read_only False
148149
149150
:param file_or_files:
@@ -198,10 +199,10 @@ def optionxform(self, optionstr):
198199
returnoptionstr
199200

200201
def_read(self, fp, fpname):
201-
"""A direct copy of the py2.4 version of the super class's _read method
202+
"""A direct copy of the py2.7 version of the super class's _read method
202203
to assure it uses ordered dicts. Had to change one line to make it work.
203204
204-
Future versions have this fixed, but in fact its quite embarassing for the
205+
Future versions have this fixed, but in fact its quite embarassing for the
205206
guys not to have done it right in the first place !
206207
207208
Removed big comments to make it more compact.
@@ -222,6 +223,7 @@ def _read(self, fp, fpname):
222223
ifline.split(None, 1)[0].lower() =='rem'andline[0] in"rR":
223224
# no leading whitespace
224225
continue
226+
# a section header or option header?
225227
else:
226228
# is it a section header?
227229
mo=self.SECTCRE.match(line.strip())
@@ -245,43 +247,48 @@ def _read(self, fp, fpname):
245247
mo=self.OPTCRE.match(line)
246248
ifmo:
247249
optname, vi, optval=mo.group('option', 'vi', 'value')
248-
ifviin ('=', ':') and''inoptval:
249-
pos=optval.find('')
250-
ifpos!=-1andoptval[pos-1].isspace():
251-
optval=optval[:pos]
252-
optval=optval.strip()
253-
254-
# Remove paired unescaped-quotes
255-
unquoted_optval=''
256-
escaped=False
257-
in_quote=False
258-
forcinoptval:
259-
ifnotescapedandc=='"':
260-
in_quote=notin_quote
261-
else:
262-
escaped= (c=='\\') andnotescaped
263-
unquoted_optval+=c
264-
265-
ifin_quote:
266-
ifnote:
267-
e=cp.ParsingError(fpname)
268-
e.append(lineno, repr(line))
269-
270-
optval=unquoted_optval
271-
272-
optval=optval.replace('\\\\', '\\') # Unescape backslashes
273-
optval=optval.replace(r'\"', '"') # Unescape quotes
274-
275250
optname=self.optionxform(optname.rstrip())
276-
cursect[optname] =optval
251+
ifoptvalisnotNone:
252+
ifviin ('=', ':') and''inoptval:
253+
# '' is a comment delimiter only if it follows
254+
# a spacing character
255+
pos=optval.find('')
256+
ifpos!=-1andoptval[pos-1].isspace():
257+
optval=optval[:pos]
258+
optval=optval.strip()
259+
# allow empty values
260+
ifoptval=='""':
261+
optval=''
262+
# Remove paired unescaped-quotes
263+
unquoted_optval=''
264+
escaped=False
265+
in_quote=False
266+
forcinoptval:
267+
ifnotescapedandc=='"':
268+
in_quote=notin_quote
269+
else:
270+
escaped= (c=='\\') andnotescaped
271+
unquoted_optval+=c
272+
ifin_quote:
273+
ifnote:
274+
e=cp.ParsingError(fpname)
275+
e.append(lineno, repr(line))
276+
277+
optval=unquoted_optval
278+
optval=optval.replace('\\\\', '\\') # Unescape backslashes
279+
optval=optval.replace(r'\"', '"') # Unescape quotes
280+
cursect[optname] =optval
281+
else:
282+
# valueless option handling
283+
cursect[optname] =optval
277284
else:
285+
# a non-fatal parsing error occurred. set up the
286+
# exception but keep going. the exception will be
287+
# raised at the end of the file and will contain a
288+
# list of all bogus lines
278289
ifnote:
279290
e=cp.ParsingError(fpname)
280291
e.append(lineno, repr(line))
281-
# END
282-
# END ?
283-
# END ?
284-
# END while reading
285292
# if any parsing errors occurred, raise an exception
286293
ife:
287294
raisee
@@ -398,7 +405,7 @@ def get_value(self, section, option, default=None):
398405
:param default:
399406
If not None, the given default value will be returned in case
400407
the option did not exist
401-
:return: a properly typed value, either int, floator string
408+
:return: a properly typed value, either int, bool, float, string or None
402409
403410
:raise TypeError: in case the value could not be understood
404411
Otherwise the exceptions known to the ConfigParser will be raised."""
@@ -409,6 +416,9 @@ def get_value(self, section, option, default=None):
409416
returndefault
410417
raise
411418

419+
ifvaluestrisNone:
420+
returnvaluestr
421+
412422
types= (long, float)
413423
fornumtypeintypes:
414424
try:

‎git/test/fixtures/git_config‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,8 @@
2727
[branch "mainline_performance"]
2828
remote = mainline
2929
merge = refs/heads/master
30+
[filter "indent"]
31+
clean = indent
32+
smudge = cat
33+
# A vauleless option
34+
required

‎git/test/test_config.py‎

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -77,18 +77,22 @@ def test_base(self):
7777
assertr_config._is_initialized==False
7878
forsectioninr_config.sections():
7979
num_sections+=1
80-
foroptioninr_config.options(section):
81-
num_options+=1
82-
val=r_config.get(section, option)
83-
val_typed=r_config.get_value(section, option)
84-
assertisinstance(val_typed, (bool, long, float, basestring))
85-
assertval
86-
assert"\n"notinoption
87-
assert"\n"notinval
88-
89-
# writing must fail
90-
self.failUnlessRaises(IOError, r_config.set, section, option, None)
91-
self.failUnlessRaises(IOError, r_config.remove_option, section, option)
80+
ifsection!='filter "indent"':
81+
foroptioninr_config.options(section):
82+
num_options+=1
83+
val=r_config.get(section, option)
84+
val_typed=r_config.get_value(section, option)
85+
assertisinstance(val_typed, (bool, long, float, basestring))
86+
assertval
87+
assert"\n"notinoption
88+
assert"\n"notinval
89+
90+
# writing must fail
91+
self.failUnlessRaises(IOError, r_config.set, section, option, None)
92+
self.failUnlessRaises(IOError, r_config.remove_option, section, option)
93+
else:
94+
val=r_config.get(section, 'required')
95+
assertvalisNone
9296
# END for each option
9397
self.failUnlessRaises(IOError, r_config.remove_section, section)
9498
# END for each section

0 commit comments

Comments
(0)