Index: Objects/unicodeobject.c =================================================================== --- Objects/unicodeobject.c (revision 88712) +++ Objects/unicodeobject.c (working copy) @@ -688,7 +688,7 @@ *fmt++ = '0'; fmt += sprintf(fmt, "%d", width); } - if (precision) + if (precision != -1) fmt += sprintf(fmt, ".%d", precision); if (longflag) *fmt++ = 'l'; @@ -728,9 +728,11 @@ width = 0; while (Py_ISDIGIT((unsigned)*f)) width = (width*10) + *f++ - '0'; - precision = 0; + precision = -1; if (*f == '.') { f++; + if Py_ISDIGIT((unsigned)*f) + precision = 0; while (Py_ISDIGIT((unsigned)*f)) precision = (precision*10) + *f++ - '0'; if (*f == '%') { @@ -791,6 +793,44 @@ plus 1 for the sign. 53/22 is an upper bound for log10(256). */ #define MAX_LONG_LONG_CHARS (2 + (SIZEOF_LONG_LONG*53-1) / 22) +static PyObject * +unicode_format_align(PyObject *unicode, int width, int precision) +{ + PyObject *result; + Py_UNICODE *u; + Py_ssize_t i; + + assert(PyUnicode_Check(unicode)); + + if (precision < 0 || precision > PyUnicode_GET_SIZE(unicode)) { + if (width <= PyUnicode_GET_SIZE(unicode)) { + Py_INCREF(unicode); + return unicode; + } + else + precision = PyUnicode_GET_SIZE(unicode); + } + + if (precision < PyUnicode_GET_SIZE(unicode) && width <= precision) + return PySequence_GetSlice((PyObject*)unicode, 0, precision); + + result = PyUnicode_FromUnicode(NULL, width); + if (!result) + return NULL; + + // Add left-pad spaces + u = PyUnicode_AS_UNICODE(result); + for (i = 0; i < width - precision; i++) { + *u = (Py_UNICODE)' '; + u++; + } + + Py_UNICODE_COPY(PyUnicode_AS_UNICODE(result) + width - precision, + PyUnicode_AS_UNICODE(unicode), precision); + + return result; +} + PyObject * PyUnicode_FromFormatV(const char *format, va_list vargs) { @@ -824,7 +864,7 @@ if (*f == '%') { /* skip width or width.precision (eg. "1.2" of "%1.2f") */ f = parse_format_flags(f, NULL, NULL, NULL, NULL, NULL); - if (*f == 's' || *f=='S' || *f=='R' || *f=='A' || *f=='V') + if (*f == 's' || *f=='S' || *f=='R' || *f=='A' || *f=='V' || *f=='U') ++callcount; } else if (128 <= (unsigned char)*f) { @@ -854,7 +894,7 @@ const char* p; p = f; - f = parse_format_flags(f, &width, NULL, + f = parse_format_flags(f, &width, &precision, NULL, &longlongflag, NULL); switch (*f) { @@ -899,78 +939,100 @@ { /* UTF-8 */ const char *s = va_arg(count, const char*); - PyObject *str = PyUnicode_DecodeUTF8(s, strlen(s), "replace"); + PyObject *str, *formatted; + str = PyUnicode_DecodeUTF8(s, strlen(s), "replace"); if (!str) goto fail; - n += PyUnicode_GET_SIZE(str); + formatted = unicode_format_align(str, width, precision); + Py_DECREF(str); + if (!formatted) + goto fail; + n += PyUnicode_GET_SIZE(formatted); /* Remember the str and switch to the next slot */ - *callresult++ = str; + *callresult++ = formatted; break; } case 'U': { PyObject *obj = va_arg(count, PyObject *); + PyObject *formatted; assert(obj && PyUnicode_Check(obj)); - n += PyUnicode_GET_SIZE(obj); + formatted = unicode_format_align(obj, width, precision); + if (!formatted) + goto fail; + n += PyUnicode_GET_SIZE(formatted); + *callresult++ = formatted; break; } case 'V': { PyObject *obj = va_arg(count, PyObject *); const char *str = va_arg(count, const char *); - PyObject *str_obj; + PyObject *str_obj, *formatted; assert(obj || str); assert(!obj || PyUnicode_Check(obj)); - if (obj) { - n += PyUnicode_GET_SIZE(obj); - *callresult++ = NULL; - } + if (obj) + formatted = unicode_format_align(obj, width, precision); else { str_obj = PyUnicode_DecodeUTF8(str, strlen(str), "replace"); if (!str_obj) goto fail; - n += PyUnicode_GET_SIZE(str_obj); - *callresult++ = str_obj; + formatted = unicode_format_align(str_obj, width, precision); + Py_DECREF(str_obj); } + if (!formatted) + goto fail; + n += PyUnicode_GET_SIZE(formatted); + *callresult++ = formatted; break; } case 'S': { PyObject *obj = va_arg(count, PyObject *); - PyObject *str; + PyObject *str, *formatted; assert(obj); str = PyObject_Str(obj); if (!str) goto fail; - n += PyUnicode_GET_SIZE(str); + formatted = unicode_format_align(str, width, precision); + Py_DECREF(str); + if (!formatted) + goto fail; + n += PyUnicode_GET_SIZE(formatted); /* Remember the str and switch to the next slot */ - *callresult++ = str; + *callresult++ = formatted; break; } case 'R': { PyObject *obj = va_arg(count, PyObject *); - PyObject *repr; + PyObject *repr, *formatted; assert(obj); repr = PyObject_Repr(obj); if (!repr) goto fail; - n += PyUnicode_GET_SIZE(repr); + formatted = unicode_format_align(repr, width, precision); + Py_DECREF(repr); + if (!formatted) + goto fail; + n += PyUnicode_GET_SIZE(formatted); /* Remember the repr and switch to the next slot */ - *callresult++ = repr; + *callresult++ = formatted; break; } case 'A': { PyObject *obj = va_arg(count, PyObject *); - PyObject *ascii; + PyObject *ascii, *formatted; assert(obj); ascii = PyObject_ASCII(obj); if (!ascii) goto fail; - n += PyUnicode_GET_SIZE(ascii); + formatted = unicode_format_align(ascii, width, precision); + Py_DECREF(ascii); + n += PyUnicode_GET_SIZE(formatted); /* Remember the repr and switch to the next slot */ - *callresult++ = ascii; + *callresult++ = formatted; break; } case 'p': @@ -1082,56 +1144,17 @@ appendstring(realbuffer); break; case 's': - { - /* unused, since we already have the result */ - (void) va_arg(vargs, char *); - Py_UNICODE_COPY(s, PyUnicode_AS_UNICODE(*callresult), - PyUnicode_GET_SIZE(*callresult)); - s += PyUnicode_GET_SIZE(*callresult); - /* We're done with the unicode()/repr() => forget it */ - Py_DECREF(*callresult); - /* switch to next unicode()/repr() result */ - ++callresult; - break; - } case 'U': - { - PyObject *obj = va_arg(vargs, PyObject *); - Py_ssize_t size = PyUnicode_GET_SIZE(obj); - Py_UNICODE_COPY(s, PyUnicode_AS_UNICODE(obj), size); - s += size; - break; - } case 'V': - { - PyObject *obj = va_arg(vargs, PyObject *); - va_arg(vargs, const char *); - if (obj) { - Py_ssize_t size = PyUnicode_GET_SIZE(obj); - Py_UNICODE_COPY(s, PyUnicode_AS_UNICODE(obj), size); - s += size; - } else { - Py_UNICODE_COPY(s, PyUnicode_AS_UNICODE(*callresult), - PyUnicode_GET_SIZE(*callresult)); - s += PyUnicode_GET_SIZE(*callresult); - Py_DECREF(*callresult); - } - ++callresult; - break; - } case 'S': case 'R': case 'A': { - Py_UNICODE *ucopy; - Py_ssize_t usize; - Py_ssize_t upos; /* unused, since we already have the result */ - (void) va_arg(vargs, PyObject *); - ucopy = PyUnicode_AS_UNICODE(*callresult); - usize = PyUnicode_GET_SIZE(*callresult); - for (upos = 0; upos forget it */ Py_DECREF(*callresult); /* switch to next unicode()/repr() result */ Index: Lib/test/test_unicode.py =================================================================== --- Lib/test/test_unicode.py (revision 88712) +++ Lib/test/test_unicode.py (working copy) @@ -1511,6 +1511,61 @@ self.assertEqual(PyUnicode_FromFormat(b'%+i', c_int(10)), '%+i') self.assertEqual(PyUnicode_FromFormat(b'%.%s', b'abc'), '%.%s') + # following tests comes from #7330 + # test width modifier and precision modifier with %S + text = PyUnicode_FromFormat(b'repr=%5S', 'xx') + self.assertEqual(text, "repr= xx") + text = PyUnicode_FromFormat(b'repr=%.2S', 'xxx') + self.assertEqual(text, "repr=xx") + text = PyUnicode_FromFormat(b'repr=%5.2S', 'xxx') + self.assertEqual(text, "repr= xx") + + # test width modifier and precision modifier with %R + text = PyUnicode_FromFormat(b'repr=%5R', 'xx') + self.assertEqual(text, "repr= 'xx'") + text = PyUnicode_FromFormat(b'repr=%.2R', 'xxx') + self.assertEqual(text, "repr='x") + text = PyUnicode_FromFormat(b'repr=%5.2R', 'xxx') + self.assertEqual(text, "repr= 'x") + + # test width modifier and precision modifier with %A + text = PyUnicode_FromFormat(b'repr=%5A', 'xx') + self.assertEqual(text, "repr= 'xx'") + text = PyUnicode_FromFormat(b'repr=%.2A', 'xxx') + self.assertEqual(text, "repr='x") + text = PyUnicode_FromFormat(b'repr=%5.2A', 'xxx') + self.assertEqual(text, "repr= 'x") + + # test width modifier and precision modifier with %s + text = PyUnicode_FromFormat(b'repr=%5s', b'xx') + self.assertEqual(text, "repr= xx") + text = PyUnicode_FromFormat(b'repr=%.2s', b'xxx') + self.assertEqual(text, "repr=xx") + text = PyUnicode_FromFormat(b'repr=%5.2s', b'xxx') + self.assertEqual(text, "repr= xx") + + # test width modifier and precision modifier with %U + text = PyUnicode_FromFormat(b'repr=%5U', 'xx') + self.assertEqual(text, "repr= xx") + text = PyUnicode_FromFormat(b'repr=%.2U', 'xxx') + self.assertEqual(text, "repr=xx") + text = PyUnicode_FromFormat(b'repr=%5.2U', 'xxx') + self.assertEqual(text, "repr= xx") + + # test width modifier and precision modifier with %V + text = PyUnicode_FromFormat(b'repr=%5V', 'xx', b'yy') + self.assertEqual(text, "repr= xx") + text = PyUnicode_FromFormat(b'repr=%.2V', 'xxx', b'yyy') + self.assertEqual(text, "repr=xx") + text = PyUnicode_FromFormat(b'repr=%5.2V', 'xxx', b'yyy') + self.assertEqual(text, "repr= xx") + text = PyUnicode_FromFormat(b'repr=%5V', None, b'yy') + self.assertEqual(text, "repr= yy") + text = PyUnicode_FromFormat(b'repr=%.2V', None, b'yyy') + self.assertEqual(text, "repr=yy") + text = PyUnicode_FromFormat(b'repr=%5.2V', None, b'yyy') + self.assertEqual(text, "repr= yy") + # Test PyUnicode_AsWideChar() def test_aswidechar(self): from _testcapi import unicode_aswidechar