Skip to content

Commit aa66d10

Browse files
committed
Merge pull request gitpython-developers#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

Diff for: git/config.py

+52-42
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
return optionstr
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
if line.split(None, 1)[0].lower() == 'rem' and line[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
if mo:
247249
optname, vi, optval = mo.group('option', 'vi', 'value')
248-
if vi in ('=', ':') and ';' in optval:
249-
pos = optval.find(';')
250-
if pos != -1 and optval[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-
for c in optval:
259-
if not escaped and c == '"':
260-
in_quote = not in_quote
261-
else:
262-
escaped = (c == '\\') and not escaped
263-
unquoted_optval += c
264-
265-
if in_quote:
266-
if not e:
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+
if optval is not None:
252+
if vi in ('=', ':') and ';' in optval:
253+
# ';' is a comment delimiter only if it follows
254+
# a spacing character
255+
pos = optval.find(';')
256+
if pos != -1 and optval[pos-1].isspace():
257+
optval = optval[:pos]
258+
optval = optval.strip()
259+
# allow empty values
260+
if optval == '""':
261+
optval = ''
262+
# Remove paired unescaped-quotes
263+
unquoted_optval = ''
264+
escaped = False
265+
in_quote = False
266+
for c in optval:
267+
if not escaped and c == '"':
268+
in_quote = not in_quote
269+
else:
270+
escaped = (c == '\\') and not escaped
271+
unquoted_optval += c
272+
if in_quote:
273+
if not e:
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
if not e:
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
if e:
287294
raise e
@@ -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, float or 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
return default
410417
raise
411418

419+
if valuestr is None:
420+
return valuestr
421+
412422
types = (long, float)
413423
for numtype in types:
414424
try:

Diff for: git/test/fixtures/git_config

+5
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

Diff for: git/test/test_config.py

+16-12
Original file line numberDiff line numberDiff line change
@@ -77,18 +77,22 @@ def test_base(self):
7777
assert r_config._is_initialized == False
7878
for section in r_config.sections():
7979
num_sections += 1
80-
for option in r_config.options(section):
81-
num_options += 1
82-
val = r_config.get(section, option)
83-
val_typed = r_config.get_value(section, option)
84-
assert isinstance(val_typed, (bool, long, float, basestring))
85-
assert val
86-
assert "\n" not in option
87-
assert "\n" not in val
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+
if section != 'filter "indent"':
81+
for option in r_config.options(section):
82+
num_options += 1
83+
val = r_config.get(section, option)
84+
val_typed = r_config.get_value(section, option)
85+
assert isinstance(val_typed, (bool, long, float, basestring))
86+
assert val
87+
assert "\n" not in option
88+
assert "\n" not in val
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+
assert val is None
9296
# END for each option
9397
self.failUnlessRaises(IOError, r_config.remove_section, section)
9498
# END for each section

0 commit comments

Comments
 (0)