Skip to content

Update the config parser using code from python2.7 #183

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 17, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 52 additions & 42 deletions git/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,19 +131,20 @@ class GitConfigParser(cp.RawConfigParser, object):

OPTCRE = re.compile(
r'\s*(?P<option>[^:=\s][^:=]*)' # very permissive, incuding leading whitespace
r'\s*(?P<vi>[:=])\s*' # any number of space/tab,
# followed by separator
# (either : or =), followed
# by any # space/tab
r'(?P<value>.*)$' # everything up to eol
)
r'\s*(?:' # any number of space/tab,
r'(?P<vi>[:=])\s*' # optionally followed by
# separator (either : or
# =), followed by any #
# space/tab
r'(?P<value>.*))?$' # everything up to eol
)

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

def __init__(self, file_or_files, read_only=True):
"""Initialize a configuration reader to read the given file_or_files and to
"""Initialize a configuration reader to read the given file_or_files and to
possibly allow changes to it by setting read_only False
:param file_or_files:
Expand Down Expand Up @@ -198,10 +199,10 @@ def optionxform(self, optionstr):
return optionstr

def _read(self, fp, fpname):
"""A direct copy of the py2.4 version of the super class's _read method
"""A direct copy of the py2.7 version of the super class's _read method
to assure it uses ordered dicts. Had to change one line to make it work.
Future versions have this fixed, but in fact its quite embarassing for the
Future versions have this fixed, but in fact its quite embarassing for the
guys not to have done it right in the first place !
Removed big comments to make it more compact.
Expand All @@ -222,6 +223,7 @@ def _read(self, fp, fpname):
if line.split(None, 1)[0].lower() == 'rem' and line[0] in "rR":
# no leading whitespace
continue
# a section header or option header?
else:
# is it a section header?
mo = self.SECTCRE.match(line.strip())
Expand All @@ -245,43 +247,48 @@ def _read(self, fp, fpname):
mo = self.OPTCRE.match(line)
if mo:
optname, vi, optval = mo.group('option', 'vi', 'value')
if vi in ('=', ':') and ';' in optval:
pos = optval.find(';')
if pos != -1 and optval[pos - 1].isspace():
optval = optval[:pos]
optval = optval.strip()

# Remove paired unescaped-quotes
unquoted_optval = ''
escaped = False
in_quote = False
for c in optval:
if not escaped and c == '"':
in_quote = not in_quote
else:
escaped = (c == '\\') and not escaped
unquoted_optval += c

if in_quote:
if not e:
e = cp.ParsingError(fpname)
e.append(lineno, repr(line))

optval = unquoted_optval

optval = optval.replace('\\\\', '\\') # Unescape backslashes
optval = optval.replace(r'\"', '"') # Unescape quotes

optname = self.optionxform(optname.rstrip())
cursect[optname] = optval
if optval is not None:
if vi in ('=', ':') and ';' in optval:
# ';' is a comment delimiter only if it follows
# a spacing character
pos = optval.find(';')
if pos != -1 and optval[pos-1].isspace():
optval = optval[:pos]
optval = optval.strip()
# allow empty values
if optval == '""':
optval = ''
# Remove paired unescaped-quotes
unquoted_optval = ''
escaped = False
in_quote = False
for c in optval:
if not escaped and c == '"':
in_quote = not in_quote
else:
escaped = (c == '\\') and not escaped
unquoted_optval += c
if in_quote:
if not e:
e = cp.ParsingError(fpname)
e.append(lineno, repr(line))

optval = unquoted_optval
optval = optval.replace('\\\\', '\\') # Unescape backslashes
optval = optval.replace(r'\"', '"') # Unescape quotes
cursect[optname] = optval
else:
# valueless option handling
cursect[optname] = optval
else:
# a non-fatal parsing error occurred. set up the
# exception but keep going. the exception will be
# raised at the end of the file and will contain a
# list of all bogus lines
if not e:
e = cp.ParsingError(fpname)
e.append(lineno, repr(line))
# END
# END ?
# END ?
# END while reading
# if any parsing errors occurred, raise an exception
if e:
raise e
Expand Down Expand Up @@ -398,7 +405,7 @@ def get_value(self, section, option, default=None):
:param default:
If not None, the given default value will be returned in case
the option did not exist
:return: a properly typed value, either int, float or string
:return: a properly typed value, either int, bool, float, string or None
:raise TypeError: in case the value could not be understood
Otherwise the exceptions known to the ConfigParser will be raised."""
Expand All @@ -409,6 +416,9 @@ def get_value(self, section, option, default=None):
return default
raise

if valuestr is None:
return valuestr

types = (long, float)
for numtype in types:
try:
Expand Down
5 changes: 5 additions & 0 deletions git/test/fixtures/git_config
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,8 @@
[branch "mainline_performance"]
remote = mainline
merge = refs/heads/master
[filter "indent"]
clean = indent
smudge = cat
# A vauleless option
required
28 changes: 16 additions & 12 deletions git/test/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,18 +77,22 @@ def test_base(self):
assert r_config._is_initialized == False
for section in r_config.sections():
num_sections += 1
for option in r_config.options(section):
num_options += 1
val = r_config.get(section, option)
val_typed = r_config.get_value(section, option)
assert isinstance(val_typed, (bool, long, float, basestring))
assert val
assert "\n" not in option
assert "\n" not in val

# writing must fail
self.failUnlessRaises(IOError, r_config.set, section, option, None)
self.failUnlessRaises(IOError, r_config.remove_option, section, option)
if section != 'filter "indent"':
for option in r_config.options(section):
num_options += 1
val = r_config.get(section, option)
val_typed = r_config.get_value(section, option)
assert isinstance(val_typed, (bool, long, float, basestring))
assert val
assert "\n" not in option
assert "\n" not in val

# writing must fail
self.failUnlessRaises(IOError, r_config.set, section, option, None)
self.failUnlessRaises(IOError, r_config.remove_option, section, option)
else:
val = r_config.get(section, 'required')
assert val is None
# END for each option
self.failUnlessRaises(IOError, r_config.remove_section, section)
# END for each section
Expand Down