diff options
author | Keith Mosher <kmosher@yelp.com> | 2014-06-21 02:48:58 -0700 |
---|---|---|
committer | Keith Mosher <kmosher@yelp.com> | 2014-08-12 00:58:48 -0700 |
commit | f72477aa5b2f61b22bd6d9cc7567da3fc197d932 (patch) | |
tree | 1891d80cba5ec1f7fb6247899a293652f86ddce0 | |
parent | a66cfe99c1af3d745e929da6a61e1257e3a376b1 (diff) | |
download | gitpython-f72477aa5b2f61b22bd6d9cc7567da3fc197d932.tar.gz |
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.)
-rw-r--r-- | git/config.py | 94 | ||||
-rw-r--r-- | git/test/fixtures/git_config | 5 | ||||
-rw-r--r-- | git/test/test_config.py | 28 |
3 files changed, 73 insertions, 54 deletions
diff --git a/git/config.py b/git/config.py index c66d0cc4..e2b0356f 100644 --- a/git/config.py +++ b/git/config.py @@ -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: @@ -198,10 +199,10 @@ class GitConfigParser(cp.RawConfigParser, object): 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. @@ -222,6 +223,7 @@ class GitConfigParser(cp.RawConfigParser, object): 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()) @@ -245,43 +247,48 @@ class GitConfigParser(cp.RawConfigParser, object): 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 @@ -398,7 +405,7 @@ class GitConfigParser(cp.RawConfigParser, object): :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.""" @@ -409,6 +416,9 @@ class GitConfigParser(cp.RawConfigParser, object): return default raise + if valuestr is None: + return valuestr + types = (long, float) for numtype in types: try: diff --git a/git/test/fixtures/git_config b/git/test/fixtures/git_config index ff8e7114..34fdfc5a 100644 --- a/git/test/fixtures/git_config +++ b/git/test/fixtures/git_config @@ -27,3 +27,8 @@ [branch "mainline_performance"] remote = mainline merge = refs/heads/master +[filter "indent"] + clean = indent + smudge = cat + # A vauleless option + required diff --git a/git/test/test_config.py b/git/test/test_config.py index b00240b0..dc4b52dc 100644 --- a/git/test/test_config.py +++ b/git/test/test_config.py @@ -77,18 +77,22 @@ class TestConfig(TestBase): 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 |