=== modified file 'Lib/locale.py' --- Lib/locale.py 2009-03-18 17:10:04 +0000 +++ Lib/locale.py 2009-03-29 17:05:35 +0000 @@ -13,6 +13,7 @@ import sys, encodings, encodings.aliases import functools +from re import compile # Try importing the _locale module. # @@ -166,6 +167,10 @@ amount -= 1 return s[lpos:rpos+1] +import re, operator +_percent_re = re.compile(r'%(?:\((?P.*?)\))?' + r'(?P[-#0-9 +*.hlL]*?)[eEfFgGdiouxXcrs%]') + def format(percent, value, grouping=False, monetary=False, *additional): """Returns the locale-aware substitution of a %? specifier (percent). @@ -173,9 +178,13 @@ additional is for format strings which contain one or more '*' modifiers.""" # this is only for one-percent-specifier strings and this should be checked - if percent[0] != '%': - raise ValueError("format() must be given exactly one %char " - "format specifier") + match = _percent_re.match(percent) + if not match or len(match.group())!= len(percent): + raise ValueError(("format() must be given exactly one %%char " + "format specifier, %s not valid") % repr(percent)) + return _format(percent, value, grouping, monetary, *additional) + +def _format(percent, value, grouping=False, monetary=False, *additional): if additional: formatted = percent % ((value,) + additional) else: @@ -199,10 +208,6 @@ formatted = _strip_padding(formatted, seps) return formatted -import re, operator -_percent_re = re.compile(r'%(?:\((?P.*?)\))?' - r'(?P[-#0-9 +*.hlL]*?)[eEfFgGdiouxXcrs%]') - def format_string(f, val, grouping=False): """Formats a string in the same way that the % formatting would use, but takes the current locale into account. === modified file 'Lib/test/test_locale.py' --- Lib/test/test_locale.py 2009-03-26 21:44:43 +0000 +++ Lib/test/test_locale.py 2009-03-29 16:56:07 +0000 @@ -221,6 +221,23 @@ (self.sep, self.sep)) +class TestFormatPatternArg(unittest.TestCase): + # From issue2522: if format doesn't enforce the contract that only + # one format string is passed for the first arg, then from the user + # point of view mysterious errors can result where decimal + # substitution does not take place but no error is generated. + # These test makes sure a ValueError gets generated for things + # that are not valid isolated format strings. + + def test_onlyOnePattern(self): + self.assertRaises(ValueError, locale.format, "%f\n", 'foo') + self.assertRaises(ValueError, locale.format, "%f\r", 'foo') + self.assertRaises(ValueError, locale.format, "%f\r\n", 'foo') + self.assertRaises(ValueError, locale.format, " %f", 'foo') + self.assertRaises(ValueError, locale.format, "%fg", 'foo') + self.assertRaises(ValueError, locale.format, "%^g", 'foo') + + class TestNumberFormatting(BaseLocalizedTest, EnUSNumberFormatting): # Test number formatting with a real English locale. @@ -351,6 +368,7 @@ def test_main(): tests = [ TestMiscellaneous, + TestFormatPatternArg, TestEnUSNumberFormatting, TestCNumberFormatting, TestFrFRNumberFormatting,