Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f72477a

Browse files
committed
Update the config parser using code from python2.7
Notably this adds support for valueless options ( e.x. the option "required" which can be added to filters which must succeed.)
1 parent a66cfe9 commit f72477a

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
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:

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
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)