Commit c8eabf3e authored by Eric Smith's avatar Eric Smith

Merged revisions 70364 via svnmerge from

svn+ssh://pythondev@svn.python.org/python/trunk

........
  r70364 | eric.smith | 2009-03-14 07:57:26 -0400 (Sat, 14 Mar 2009) | 17 lines

  Issue 5237, Allow auto-numbered replacement fields in str.format() strings.

  For simple uses for str.format(), this makes the typing easier. Hopfully this
  will help in the adoption of str.format().

  For example:
  'The {} is {}'.format('sky', 'blue')

  You can mix and matcth auto-numbering and named replacement fields:
  'The {} is {color}'.format('sky', color='blue')

  But you can't mix and match auto-numbering and specified numbering:
  'The {0} is {}'.format('sky', 'blue')
  ValueError: cannot switch from manual field specification to automatic field numbering

  Will port to 3.1.
........
parent b7a7c3de
...@@ -683,9 +683,9 @@ class UnicodeTest( ...@@ -683,9 +683,9 @@ class UnicodeTest(
self.assertRaises(ValueError, "{0!}".format, 0) self.assertRaises(ValueError, "{0!}".format, 0)
self.assertRaises(ValueError, "{0!rs}".format, 0) self.assertRaises(ValueError, "{0!rs}".format, 0)
self.assertRaises(ValueError, "{!}".format) self.assertRaises(ValueError, "{!}".format)
self.assertRaises(ValueError, "{:}".format) self.assertRaises(IndexError, "{:}".format)
self.assertRaises(ValueError, "{:s}".format) self.assertRaises(IndexError, "{:s}".format)
self.assertRaises(ValueError, "{}".format) self.assertRaises(IndexError, "{}".format)
# can't have a replacement on the field name portion # can't have a replacement on the field name portion
self.assertRaises(TypeError, '{0[{1}]}'.format, 'abcdefg', 4) self.assertRaises(TypeError, '{0[{1}]}'.format, 'abcdefg', 4)
...@@ -704,6 +704,36 @@ class UnicodeTest( ...@@ -704,6 +704,36 @@ class UnicodeTest(
self.assertRaises(ValueError, format, '', '#') self.assertRaises(ValueError, format, '', '#')
self.assertRaises(ValueError, format, '', '#20') self.assertRaises(ValueError, format, '', '#20')
def test_format_auto_numbering(self):
class C:
def __init__(self, x=100):
self._x = x
def __format__(self, spec):
return spec
self.assertEqual('{}'.format(10), '10')
self.assertEqual('{:5}'.format('s'), 's ')
self.assertEqual('{!r}'.format('s'), "'s'")
self.assertEqual('{._x}'.format(C(10)), '10')
self.assertEqual('{[1]}'.format([1, 2]), '2')
self.assertEqual('{[a]}'.format({'a':4, 'b':2}), '4')
self.assertEqual('a{}b{}c'.format(0, 1), 'a0b1c')
self.assertEqual('a{:{}}b'.format('x', '^10'), 'a x b')
self.assertEqual('a{:{}x}b'.format(20, '#'), 'a0x14b')
# can't mix and match numbering and auto-numbering
self.assertRaises(ValueError, '{}{1}'.format, 1, 2)
self.assertRaises(ValueError, '{1}{}'.format, 1, 2)
self.assertRaises(ValueError, '{:{1}}'.format, 1, 2)
self.assertRaises(ValueError, '{0:{}}'.format, 1, 2)
# can mix and match auto-numbering and named
self.assertEqual('{f}{}'.format(4, f='test'), 'test4')
self.assertEqual('{}{f}'.format(4, f='test'), '4test')
self.assertEqual('{:{f}}{g}{}'.format(1, 3, g='g', f=2), ' 1g3')
self.assertEqual('{f:{}}{}{g}'.format(2, 4, f=1, g='g'), ' 14g')
def test_formatting(self): def test_formatting(self):
string_tests.MixinStrUnicodeUserStringTest.test_formatting(self) string_tests.MixinStrUnicodeUserStringTest.test_formatting(self)
# Testing Unicode formatting strings... # Testing Unicode formatting strings...
......
...@@ -12,6 +12,9 @@ What's New in Python 3.1 alpha 2? ...@@ -12,6 +12,9 @@ What's New in Python 3.1 alpha 2?
Core and Builtins Core and Builtins
----------------- -----------------
- Issue #5237: Allow auto-numbered fields in str.format(). For
example: '{} {}'.format(1, 2) == '1 2'.
- Issue #5392: when a very low recursion limit was set, the interpreter would - Issue #5392: when a very low recursion limit was set, the interpreter would
abort with a fatal error after the recursion limit was hit twice. abort with a fatal error after the recursion limit was hit twice.
...@@ -33,6 +36,7 @@ What's New in Python 3.1 alpha 1 ...@@ -33,6 +36,7 @@ What's New in Python 3.1 alpha 1
Core and Builtins Core and Builtins
----------------- -----------------
=======
- The io module has been reimplemented in C for speed. - The io module has been reimplemented in C for speed.
- Give dict views an informative __repr__. - Give dict views an informative __repr__.
......
...@@ -31,10 +31,23 @@ typedef struct { ...@@ -31,10 +31,23 @@ typedef struct {
} SubString; } SubString;
typedef enum {
ANS_INIT,
ANS_AUTO,
ANS_MANUAL,
} AutoNumberState; /* Keep track if we're auto-numbering fields */
/* Keeps track of our auto-numbering state, and which number field we're on */
typedef struct {
AutoNumberState an_state;
int an_field_number;
} AutoNumber;
/* forward declaration for recursion */ /* forward declaration for recursion */
static PyObject * static PyObject *
build_string(SubString *input, PyObject *args, PyObject *kwargs, build_string(SubString *input, PyObject *args, PyObject *kwargs,
int recursion_depth); int recursion_depth, AutoNumber *auto_number);
...@@ -42,6 +55,13 @@ build_string(SubString *input, PyObject *args, PyObject *kwargs, ...@@ -42,6 +55,13 @@ build_string(SubString *input, PyObject *args, PyObject *kwargs,
/************************** Utility functions ************************/ /************************** Utility functions ************************/
/************************************************************************/ /************************************************************************/
static void
AutoNumber_Init(AutoNumber *auto_number)
{
auto_number->an_state = ANS_INIT;
auto_number->an_field_number = 0;
}
/* fill in a SubString from a pointer and length */ /* fill in a SubString from a pointer and length */
Py_LOCAL_INLINE(void) Py_LOCAL_INLINE(void)
SubString_init(SubString *str, STRINGLIB_CHAR *p, Py_ssize_t len) SubString_init(SubString *str, STRINGLIB_CHAR *p, Py_ssize_t len)
...@@ -74,6 +94,32 @@ SubString_new_object_or_empty(SubString *str) ...@@ -74,6 +94,32 @@ SubString_new_object_or_empty(SubString *str)
return STRINGLIB_NEW(str->ptr, str->end - str->ptr); return STRINGLIB_NEW(str->ptr, str->end - str->ptr);
} }
/* Return 1 if an error has been detected switching between automatic
field numbering and manual field specification, else return 0. Set
ValueError on error. */
static int
autonumber_state_error(AutoNumberState state, int field_name_is_empty)
{
if (state == ANS_MANUAL) {
if (field_name_is_empty) {
PyErr_SetString(PyExc_ValueError, "cannot switch from "
"manual field specification to "
"automatic field numbering");
return 1;
}
}
else {
if (!field_name_is_empty) {
PyErr_SetString(PyExc_ValueError, "cannot switch from "
"automatic field numbering to "
"manual field specification");
return 1;
}
}
return 0;
}
/************************************************************************/ /************************************************************************/
/*********** Output string management functions ****************/ /*********** Output string management functions ****************/
/************************************************************************/ /************************************************************************/
...@@ -352,11 +398,14 @@ FieldNameIterator_next(FieldNameIterator *self, int *is_attribute, ...@@ -352,11 +398,14 @@ FieldNameIterator_next(FieldNameIterator *self, int *is_attribute,
*/ */
static int static int
field_name_split(STRINGLIB_CHAR *ptr, Py_ssize_t len, SubString *first, field_name_split(STRINGLIB_CHAR *ptr, Py_ssize_t len, SubString *first,
Py_ssize_t *first_idx, FieldNameIterator *rest) Py_ssize_t *first_idx, FieldNameIterator *rest,
AutoNumber *auto_number)
{ {
STRINGLIB_CHAR c; STRINGLIB_CHAR c;
STRINGLIB_CHAR *p = ptr; STRINGLIB_CHAR *p = ptr;
STRINGLIB_CHAR *end = ptr + len; STRINGLIB_CHAR *end = ptr + len;
int field_name_is_empty;
int using_numeric_index;
/* find the part up until the first '.' or '[' */ /* find the part up until the first '.' or '[' */
while (p < end) { while (p < end) {
...@@ -380,15 +429,41 @@ field_name_split(STRINGLIB_CHAR *ptr, Py_ssize_t len, SubString *first, ...@@ -380,15 +429,41 @@ field_name_split(STRINGLIB_CHAR *ptr, Py_ssize_t len, SubString *first,
/* see if "first" is an integer, in which case it's used as an index */ /* see if "first" is an integer, in which case it's used as an index */
*first_idx = get_integer(first); *first_idx = get_integer(first);
/* zero length string is an error */ field_name_is_empty = first->ptr >= first->end;
if (first->ptr >= first->end) {
PyErr_SetString(PyExc_ValueError, "empty field name"); /* If the field name is omitted or if we have a numeric index
goto error; specified, then we're doing numeric indexing into args. */
using_numeric_index = field_name_is_empty || *first_idx != -1;
/* We always get here exactly one time for each field we're
processing. And we get here in field order (counting by left
braces). So this is the perfect place to handle automatic field
numbering if the field name is omitted. */
/* Check if we need to do the auto-numbering. It's not needed if
we're called from string.Format routines, because it's handled
in that class by itself. */
if (auto_number) {
/* Initialize our auto numbering state if this is the first
time we're either auto-numbering or manually numbering. */
if (auto_number->an_state == ANS_INIT && using_numeric_index)
auto_number->an_state = field_name_is_empty ?
ANS_AUTO : ANS_MANUAL;
/* Make sure our state is consistent with what we're doing
this time through. Only check if we're using a numeric
index. */
if (using_numeric_index)
if (autonumber_state_error(auto_number->an_state,
field_name_is_empty))
return 0;
/* Zero length field means we want to do auto-numbering of the
fields. */
if (field_name_is_empty)
*first_idx = (auto_number->an_field_number)++;
} }
return 1; return 1;
error:
return 0;
} }
...@@ -398,7 +473,8 @@ error: ...@@ -398,7 +473,8 @@ error:
the entire input string. the entire input string.
*/ */
static PyObject * static PyObject *
get_field_object(SubString *input, PyObject *args, PyObject *kwargs) get_field_object(SubString *input, PyObject *args, PyObject *kwargs,
AutoNumber *auto_number)
{ {
PyObject *obj = NULL; PyObject *obj = NULL;
int ok; int ok;
...@@ -409,7 +485,7 @@ get_field_object(SubString *input, PyObject *args, PyObject *kwargs) ...@@ -409,7 +485,7 @@ get_field_object(SubString *input, PyObject *args, PyObject *kwargs)
FieldNameIterator rest; FieldNameIterator rest;
if (!field_name_split(input->ptr, input->end - input->ptr, &first, if (!field_name_split(input->ptr, input->end - input->ptr, &first,
&index, &rest)) { &index, &rest, auto_number)) {
goto error; goto error;
} }
...@@ -548,14 +624,18 @@ static int ...@@ -548,14 +624,18 @@ static int
parse_field(SubString *str, SubString *field_name, SubString *format_spec, parse_field(SubString *str, SubString *field_name, SubString *format_spec,
STRINGLIB_CHAR *conversion) STRINGLIB_CHAR *conversion)
{ {
/* Note this function works if the field name is zero length,
which is good. Zero length field names are handled later, in
field_name_split. */
STRINGLIB_CHAR c = 0; STRINGLIB_CHAR c = 0;
/* initialize these, as they may be empty */ /* initialize these, as they may be empty */
*conversion = '\0'; *conversion = '\0';
SubString_init(format_spec, NULL, 0); SubString_init(format_spec, NULL, 0);
/* search for the field name. it's terminated by the end of the /* Search for the field name. it's terminated by the end of
string, or a ':' or '!' */ the string, or a ':' or '!' */
field_name->ptr = str->ptr; field_name->ptr = str->ptr;
while (str->ptr < str->end) { while (str->ptr < str->end) {
switch (c = *(str->ptr++)) { switch (c = *(str->ptr++)) {
...@@ -598,15 +678,12 @@ parse_field(SubString *str, SubString *field_name, SubString *format_spec, ...@@ -598,15 +678,12 @@ parse_field(SubString *str, SubString *field_name, SubString *format_spec,
} }
} }
} }
return 1;
} }
else { else
/* end of string, there's no format_spec or conversion */ /* end of string, there's no format_spec or conversion */
field_name->end = str->ptr; field_name->end = str->ptr;
return 1; return 1;
}
} }
/************************************************************************/ /************************************************************************/
...@@ -633,8 +710,8 @@ MarkupIterator_init(MarkupIterator *self, STRINGLIB_CHAR *ptr, Py_ssize_t len) ...@@ -633,8 +710,8 @@ MarkupIterator_init(MarkupIterator *self, STRINGLIB_CHAR *ptr, Py_ssize_t len)
string (or something to be expanded) */ string (or something to be expanded) */
static int static int
MarkupIterator_next(MarkupIterator *self, SubString *literal, MarkupIterator_next(MarkupIterator *self, SubString *literal,
SubString *field_name, SubString *format_spec, int *field_present, SubString *field_name,
STRINGLIB_CHAR *conversion, SubString *format_spec, STRINGLIB_CHAR *conversion,
int *format_spec_needs_expanding) int *format_spec_needs_expanding)
{ {
int at_end; int at_end;
...@@ -650,6 +727,7 @@ MarkupIterator_next(MarkupIterator *self, SubString *literal, ...@@ -650,6 +727,7 @@ MarkupIterator_next(MarkupIterator *self, SubString *literal,
SubString_init(format_spec, NULL, 0); SubString_init(format_spec, NULL, 0);
*conversion = '\0'; *conversion = '\0';
*format_spec_needs_expanding = 0; *format_spec_needs_expanding = 0;
*field_present = 0;
/* No more input, end of iterator. This is the normal exit /* No more input, end of iterator. This is the normal exit
path. */ path. */
...@@ -711,6 +789,7 @@ MarkupIterator_next(MarkupIterator *self, SubString *literal, ...@@ -711,6 +789,7 @@ MarkupIterator_next(MarkupIterator *self, SubString *literal,
/* this is markup, find the end of the string by counting nested /* this is markup, find the end of the string by counting nested
braces. note that this prohibits escaped braces, so that braces. note that this prohibits escaped braces, so that
format_specs cannot have braces in them. */ format_specs cannot have braces in them. */
*field_present = 1;
count = 1; count = 1;
start = self->str.ptr; start = self->str.ptr;
...@@ -735,13 +814,6 @@ MarkupIterator_next(MarkupIterator *self, SubString *literal, ...@@ -735,13 +814,6 @@ MarkupIterator_next(MarkupIterator *self, SubString *literal,
if (parse_field(&s, field_name, format_spec, conversion) == 0) if (parse_field(&s, field_name, format_spec, conversion) == 0)
return 0; return 0;
/* a zero length field_name is an error */
if (field_name->ptr == field_name->end) {
PyErr_SetString(PyExc_ValueError, "zero length field name "
"in format");
return 0;
}
/* success */ /* success */
return 2; return 2;
} }
...@@ -793,13 +865,17 @@ do_conversion(PyObject *obj, STRINGLIB_CHAR conversion) ...@@ -793,13 +865,17 @@ do_conversion(PyObject *obj, STRINGLIB_CHAR conversion)
compute the result and write it to output. compute the result and write it to output.
format_spec_needs_expanding is an optimization. if it's false, format_spec_needs_expanding is an optimization. if it's false,
just output the string directly, otherwise recursively expand the just output the string directly, otherwise recursively expand the
format_spec string. */ format_spec string.
field_name is allowed to be zero length, in which case we
are doing auto field numbering.
*/
static int static int
output_markup(SubString *field_name, SubString *format_spec, output_markup(SubString *field_name, SubString *format_spec,
int format_spec_needs_expanding, STRINGLIB_CHAR conversion, int format_spec_needs_expanding, STRINGLIB_CHAR conversion,
OutputString *output, PyObject *args, PyObject *kwargs, OutputString *output, PyObject *args, PyObject *kwargs,
int recursion_depth) int recursion_depth, AutoNumber *auto_number)
{ {
PyObject *tmp = NULL; PyObject *tmp = NULL;
PyObject *fieldobj = NULL; PyObject *fieldobj = NULL;
...@@ -808,7 +884,7 @@ output_markup(SubString *field_name, SubString *format_spec, ...@@ -808,7 +884,7 @@ output_markup(SubString *field_name, SubString *format_spec,
int result = 0; int result = 0;
/* convert field_name to an object */ /* convert field_name to an object */
fieldobj = get_field_object(field_name, args, kwargs); fieldobj = get_field_object(field_name, args, kwargs, auto_number);
if (fieldobj == NULL) if (fieldobj == NULL)
goto done; goto done;
...@@ -825,7 +901,8 @@ output_markup(SubString *field_name, SubString *format_spec, ...@@ -825,7 +901,8 @@ output_markup(SubString *field_name, SubString *format_spec,
/* if needed, recurively compute the format_spec */ /* if needed, recurively compute the format_spec */
if (format_spec_needs_expanding) { if (format_spec_needs_expanding) {
tmp = build_string(format_spec, args, kwargs, recursion_depth-1); tmp = build_string(format_spec, args, kwargs, recursion_depth-1,
auto_number);
if (tmp == NULL) if (tmp == NULL)
goto done; goto done;
...@@ -859,26 +936,28 @@ done: ...@@ -859,26 +936,28 @@ done:
*/ */
static int static int
do_markup(SubString *input, PyObject *args, PyObject *kwargs, do_markup(SubString *input, PyObject *args, PyObject *kwargs,
OutputString *output, int recursion_depth) OutputString *output, int recursion_depth, AutoNumber *auto_number)
{ {
MarkupIterator iter; MarkupIterator iter;
int format_spec_needs_expanding; int format_spec_needs_expanding;
int result; int result;
int field_present;
SubString literal; SubString literal;
SubString field_name; SubString field_name;
SubString format_spec; SubString format_spec;
STRINGLIB_CHAR conversion; STRINGLIB_CHAR conversion;
MarkupIterator_init(&iter, input->ptr, input->end - input->ptr); MarkupIterator_init(&iter, input->ptr, input->end - input->ptr);
while ((result = MarkupIterator_next(&iter, &literal, &field_name, while ((result = MarkupIterator_next(&iter, &literal, &field_present,
&format_spec, &conversion, &field_name, &format_spec,
&conversion,
&format_spec_needs_expanding)) == 2) { &format_spec_needs_expanding)) == 2) {
if (!output_data(output, literal.ptr, literal.end - literal.ptr)) if (!output_data(output, literal.ptr, literal.end - literal.ptr))
return 0; return 0;
if (field_name.ptr != field_name.end) if (field_present)
if (!output_markup(&field_name, &format_spec, if (!output_markup(&field_name, &format_spec,
format_spec_needs_expanding, conversion, output, format_spec_needs_expanding, conversion, output,
args, kwargs, recursion_depth)) args, kwargs, recursion_depth, auto_number))
return 0; return 0;
} }
return result; return result;
...@@ -891,7 +970,7 @@ do_markup(SubString *input, PyObject *args, PyObject *kwargs, ...@@ -891,7 +970,7 @@ do_markup(SubString *input, PyObject *args, PyObject *kwargs,
*/ */
static PyObject * static PyObject *
build_string(SubString *input, PyObject *args, PyObject *kwargs, build_string(SubString *input, PyObject *args, PyObject *kwargs,
int recursion_depth) int recursion_depth, AutoNumber *auto_number)
{ {
OutputString output; OutputString output;
PyObject *result = NULL; PyObject *result = NULL;
...@@ -913,7 +992,8 @@ build_string(SubString *input, PyObject *args, PyObject *kwargs, ...@@ -913,7 +992,8 @@ build_string(SubString *input, PyObject *args, PyObject *kwargs,
INITIAL_SIZE_INCREMENT)) INITIAL_SIZE_INCREMENT))
goto done; goto done;
if (!do_markup(input, args, kwargs, &output, recursion_depth)) { if (!do_markup(input, args, kwargs, &output, recursion_depth,
auto_number)) {
goto done; goto done;
} }
...@@ -947,8 +1027,11 @@ do_string_format(PyObject *self, PyObject *args, PyObject *kwargs) ...@@ -947,8 +1027,11 @@ do_string_format(PyObject *self, PyObject *args, PyObject *kwargs)
*/ */
int recursion_depth = 2; int recursion_depth = 2;
AutoNumber auto_number;
AutoNumber_Init(&auto_number);
SubString_init(&input, STRINGLIB_STR(self), STRINGLIB_LEN(self)); SubString_init(&input, STRINGLIB_STR(self), STRINGLIB_LEN(self));
return build_string(&input, args, kwargs, recursion_depth); return build_string(&input, args, kwargs, recursion_depth, &auto_number);
} }
...@@ -993,8 +1076,9 @@ formatteriter_next(formatteriterobject *it) ...@@ -993,8 +1076,9 @@ formatteriter_next(formatteriterobject *it)
SubString format_spec; SubString format_spec;
STRINGLIB_CHAR conversion; STRINGLIB_CHAR conversion;
int format_spec_needs_expanding; int format_spec_needs_expanding;
int result = MarkupIterator_next(&it->it_markup, &literal, &field_name, int field_present;
&format_spec, &conversion, int result = MarkupIterator_next(&it->it_markup, &literal, &field_present,
&field_name, &format_spec, &conversion,
&format_spec_needs_expanding); &format_spec_needs_expanding);
/* all of the SubString objects point into it->str, so no /* all of the SubString objects point into it->str, so no
...@@ -1009,7 +1093,6 @@ formatteriter_next(formatteriterobject *it) ...@@ -1009,7 +1093,6 @@ formatteriter_next(formatteriterobject *it)
PyObject *format_spec_str = NULL; PyObject *format_spec_str = NULL;
PyObject *conversion_str = NULL; PyObject *conversion_str = NULL;
PyObject *tuple = NULL; PyObject *tuple = NULL;
int has_field = field_name.ptr != field_name.end;
literal_str = SubString_new_object(&literal); literal_str = SubString_new_object(&literal);
if (literal_str == NULL) if (literal_str == NULL)
...@@ -1021,7 +1104,7 @@ formatteriter_next(formatteriterobject *it) ...@@ -1021,7 +1104,7 @@ formatteriter_next(formatteriterobject *it)
/* if field_name is non-zero length, return a string for /* if field_name is non-zero length, return a string for
format_spec (even if zero length), else return None */ format_spec (even if zero length), else return None */
format_spec_str = (has_field ? format_spec_str = (field_present ?
SubString_new_object_or_empty : SubString_new_object_or_empty :
SubString_new_object)(&format_spec); SubString_new_object)(&format_spec);
if (format_spec_str == NULL) if (format_spec_str == NULL)
...@@ -1245,9 +1328,11 @@ formatter_field_name_split(STRINGLIB_OBJECT *self) ...@@ -1245,9 +1328,11 @@ formatter_field_name_split(STRINGLIB_OBJECT *self)
Py_INCREF(self); Py_INCREF(self);
it->str = self; it->str = self;
/* Pass in auto_number = NULL. We'll return an empty string for
first_obj in that case. */
if (!field_name_split(STRINGLIB_STR(self), if (!field_name_split(STRINGLIB_STR(self),
STRINGLIB_LEN(self), STRINGLIB_LEN(self),
&first, &first_idx, &it->it_field)) &first, &first_idx, &it->it_field, NULL))
goto done; goto done;
/* first becomes an integer, if possible; else a string */ /* first becomes an integer, if possible; else a string */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment