diff options
-rw-r--r-- | Doc/lib/libcfgparser.tex | 10 | ||||
-rw-r--r-- | Lib/ConfigParser.py | 48 | ||||
-rw-r--r-- | Lib/test/test_cfgparser.py | 17 |
3 files changed, 74 insertions, 1 deletions
diff --git a/Doc/lib/libcfgparser.tex b/Doc/lib/libcfgparser.tex index 946590d..0ad9d61 100644 --- a/Doc/lib/libcfgparser.tex +++ b/Doc/lib/libcfgparser.tex @@ -59,6 +59,16 @@ appropriate for the \samp{\%()s} string interpolation. Note that and will override any value provided in \var{defaults}. \end{classdesc} +\begin{classdesc}{SafeConfigParser}{\optional{defaults}} +Derived class of \class{ConfigParser} that implements a more-sane +variant of the magical interpolation feature. This implementation is +more predictable as well. +% XXX Need to explain what's safer/more predictable about it. +New applications should prefer this version if they don't need to be +compatible with older versions of Python. +\versionadded{2.3} +\end{classdesc} + \begin{excdesc}{NoSectionError} Exception raised when a specified section is not found. \end{excdesc} diff --git a/Lib/ConfigParser.py b/Lib/ConfigParser.py index c623ec9..6077022 100644 --- a/Lib/ConfigParser.py +++ b/Lib/ConfigParser.py @@ -538,3 +538,51 @@ class ConfigParser(RawConfigParser): if value.find("%(") != -1: raise InterpolationDepthError(option, section, rawval) return value + + +class SafeConfigParser(ConfigParser): + + def _interpolate(self, section, option, rawval, vars): + # do the string interpolation + L = [] + self._interpolate_some(option, L, rawval, section, vars, 1) + return ''.join(L) + + _interpvar_match = re.compile(r"%\(([^)]+)\)s").match + + def _interpolate_some(self, option, accum, rest, section, map, depth): + if depth > MAX_INTERPOLATION_DEPTH: + raise InterpolationDepthError(option, section, rest) + while rest: + p = rest.find("%") + if p < 0: + accum.append(rest) + return + if p > 0: + accum.append(rest[:p]) + rest = rest[p:] + # p is no longer used + c = rest[1:2] + if c == "%": + accum.append("%") + rest = rest[2:] + elif c == "(": + m = self._interpvar_match(rest) + if m is None: + raise InterpolationSyntaxError( + "bad interpolation variable syntax at: %r" % rest) + var = m.group(1) + rest = rest[m.end():] + try: + v = map[var] + except KeyError: + raise InterpolationError( + "no value found for %r" % var) + if "%" in v: + self._interpolate_some(option, accum, v, + section, map, depth + 1) + else: + accum.append(v) + else: + raise InterpolationSyntaxError( + "'%' must be followed by '%' or '('") diff --git a/Lib/test/test_cfgparser.py b/Lib/test/test_cfgparser.py index 6d93c61..b221598 100644 --- a/Lib/test/test_cfgparser.py +++ b/Lib/test/test_cfgparser.py @@ -289,10 +289,25 @@ class RawConfigParserTestCase(TestCaseBase): ('name', 'value')]) +class SafeConfigParserTestCase(ConfigParserTestCase): + config_class = ConfigParser.SafeConfigParser + + def test_safe_interpolation(self): + # See http://www.python.org/sf/511737 + cf = self.fromstring("[section]\n" + "option1=xxx\n" + "option2=%(option1)s/xxx\n" + "ok=%(option1)s/%%s\n" + "not_ok=%(option2)s/%%s") + self.assertEqual(cf.get("section", "ok"), "xxx/%s") + self.assertEqual(cf.get("section", "not_ok"), "xxx/xxx/%s") + + def test_main(): suite = unittest.TestSuite() suite.addTests([unittest.makeSuite(ConfigParserTestCase), - unittest.makeSuite(RawConfigParserTestCase)]) + unittest.makeSuite(RawConfigParserTestCase), + unittest.makeSuite(SafeConfigParserTestCase)]) test_support.run_suite(suite) if __name__ == "__main__": |