PEP 3101: Completed string.Formatter class. Reimplemented field_name to object transformation.

This commit is contained in:
Eric Smith 2007-08-26 22:27:13 +00:00
parent 2bf4d5ba28
commit 7ade6485ab
7 changed files with 506 additions and 206 deletions

View File

@ -1440,8 +1440,7 @@ PyAPI_FUNC(Py_UNICODE*) Py_UNICODE_strchr(
PyObject * PyObject *
_unicodeformatter_iterator(PyObject *str); _unicodeformatter_iterator(PyObject *str);
PyObject * PyObject *
_unicodeformatter_lookup(PyObject *field_name, PyObject *args, _unicodeformatter_field_name_split(PyObject *field_name);
PyObject *kwargs);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -200,31 +200,59 @@ class Template(metaclass=_TemplateMetaclass):
# exposed here via the sys module. sys was chosen because it's always # exposed here via the sys module. sys was chosen because it's always
# available and doesn't have to be dynamically loaded. # available and doesn't have to be dynamically loaded.
# The parser is implemented in sys._formatter_parser. # The overall parser is implemented in sys._formatter_parser.
# The "object lookup" is implemented in sys._formatter_lookup # The field name parser is implemented in sys._formatter_field_name_split
from sys import _formatter_parser, _formatter_lookup from sys import _formatter_parser, _formatter_field_name_split
class Formatter: class Formatter:
def format(self, format_string, *args, **kwargs): def format(self, format_string, *args, **kwargs):
return self.vformat(format_string, args, kwargs) return self.vformat(format_string, args, kwargs)
def vformat(self, format_string, args, kwargs): def vformat(self, format_string, args, kwargs):
used_args = set()
result = [] result = []
for (is_markup, literal, field_name, format_spec, conversion) in \ for (is_markup, literal, field_name, format_spec, conversion) in \
_formatter_parser(format_string): _formatter_parser(format_string):
if is_markup: if is_markup:
# find the object # given the field_name, find the object it references
index, name, obj = _formatter_lookup(field_name, args, kwargs)
# split it into the first part, and and iterator that
# looks over the rest
first, rest = _formatter_field_name_split(field_name)
used_args.add(first)
obj = self.get_value(first, args, kwargs)
# loop through the rest of the field_name, doing
# getattr or getitem as needed
for is_attr, i in rest:
if is_attr:
obj = getattr(obj, i)
else:
obj = obj[i]
# do any conversion on the resulting object
if conversion == 'r':
obj = repr(obj)
elif conversion == 's':
obj = str(obj)
# format the object and append to the result
result.append(self.format_field(obj, format_spec))
else: else:
result.append(literal) result.append(literal)
self.check_unused_args(used_args, args, kwargs)
return ''.join(result) return ''.join(result)
def get_value(self, key, args, kwargs): def get_value(self, key, args, kwargs):
pass if isinstance(key, int):
return args[key]
else:
return kwargs[key]
def check_unused_args(self, used_args, args, kwargs): def check_unused_args(self, used_args, args, kwargs):
pass pass
def format_field(self, value, format_spec): def format_field(self, value, format_spec):
pass return format(value, format_spec)

View File

@ -19,8 +19,27 @@ class ModuleTest(unittest.TestCase):
fmt = string.Formatter() fmt = string.Formatter()
self.assertEqual(fmt.format("foo"), "foo") self.assertEqual(fmt.format("foo"), "foo")
# Formatter not working you for lookups self.assertEqual(fmt.format("foo{0}", "bar"), "foobar")
#self.assertEqual(fmt.format("foo{0}", "bar"), "foobar") self.assertEqual(fmt.format("foo{1}{0}-{1}", "bar", 6), "foo6bar-6")
self.assertEqual(fmt.format("-{arg!r}-", arg='test'), "-'test'-")
class NamespaceFormatter(string.Formatter):
def __init__(self, namespace={}):
string.Formatter.__init__(self)
self.namespace = namespace
def get_value(self, key, args, kwds):
if isinstance(key, str):
try:
# Check explicitly passed arguments first
return kwds[key]
except KeyError:
return self.namespace[key]
else:
string.Formatter.get_value(key, args, kwds)
fmt = NamespaceFormatter({'greeting':'hello'})
self.assertEqual(fmt.format("{greeting}, world!"), 'hello, world!')
def test_maketrans(self): def test_maketrans(self):

View File

@ -449,6 +449,10 @@ class UnicodeTest(
self.assertEqual('}}{{'.format(), '}{') self.assertEqual('}}{{'.format(), '}{')
self.assertEqual('}}x{{'.format(), '}x{') self.assertEqual('}}x{{'.format(), '}x{')
# weird field names
self.assertEqual("{0[foo-bar]}".format({'foo-bar':'baz'}), 'baz')
self.assertEqual("{0[foo bar]}".format({'foo bar':'baz'}), 'baz')
self.assertEqual('{foo._x}'.format(foo=C(20)), '20') self.assertEqual('{foo._x}'.format(foo=C(20)), '20')
self.assertEqual('{1}{0}'.format(D(10), D(20)), '2010') self.assertEqual('{1}{0}'.format(D(10), D(20)), '2010')
self.assertEqual('{0._x.x}'.format(C(D('abc'))), 'abc') self.assertEqual('{0._x.x}'.format(C(D('abc'))), 'abc')
@ -539,7 +543,11 @@ class UnicodeTest(
self.assertRaises(ValueError, "}".format) self.assertRaises(ValueError, "}".format)
self.assertRaises(ValueError, "abc{0:{}".format) self.assertRaises(ValueError, "abc{0:{}".format)
self.assertRaises(ValueError, "{0".format) self.assertRaises(ValueError, "{0".format)
self.assertRaises(ValueError, "{0.}".format)
self.assertRaises(ValueError, "{0[}".format)
self.assertRaises(ValueError, "{0]}".format)
self.assertRaises(ValueError, "{0.[]}".format) self.assertRaises(ValueError, "{0.[]}".format)
self.assertRaises(ValueError, "{0..foo}".format, 0)
self.assertRaises(ValueError, "{0[0}".format) self.assertRaises(ValueError, "{0[0}".format)
self.assertRaises(ValueError, "{0[0:foo}".format) self.assertRaises(ValueError, "{0[0:foo}".format)
self.assertRaises(ValueError, "{c]}".format) self.assertRaises(ValueError, "{c]}".format)
@ -551,6 +559,7 @@ class UnicodeTest(
self.assertRaises(ValueError, "{0!rs}".format) self.assertRaises(ValueError, "{0!rs}".format)
self.assertRaises(ValueError, "{!}".format) self.assertRaises(ValueError, "{!}".format)
self.assertRaises(ValueError, "{:}".format) self.assertRaises(ValueError, "{:}".format)
self.assertRaises(ValueError, "{:s}".format)
self.assertRaises(ValueError, "{}".format) self.assertRaises(ValueError, "{}".format)
# can't have a replacement on the field name portion # can't have a replacement on the field name portion

View File

@ -72,23 +72,6 @@ SetError(const char *s)
return PyErr_Format(PyExc_ValueError, "%s in format string", s); return PyErr_Format(PyExc_ValueError, "%s in format string", s);
} }
/*
check_input returns True if we still have characters
left in the input string.
XXX: make this function go away when better error handling is
implemented.
*/
Py_LOCAL_INLINE(int)
check_input(SubString *input)
{
if (input->ptr < input->end)
return 1;
PyErr_SetString(PyExc_ValueError,
"unterminated replacement field");
return 0;
}
/************************************************************************/ /************************************************************************/
/*********** Output string management functions ****************/ /*********** Output string management functions ****************/
/************************************************************************/ /************************************************************************/
@ -161,46 +144,22 @@ output_data(OutputString *output, const STRINGLIB_CHAR *s, Py_ssize_t count)
/*********** Format string parsing -- integers and identifiers *********/ /*********** Format string parsing -- integers and identifiers *********/
/************************************************************************/ /************************************************************************/
/* static Py_ssize_t
end_identifier returns true if a character marks get_integer(const SubString *str)
the end of an identifier string.
Although the PEP specifies that identifiers are
numbers or valid Python identifiers, we just let
getattr/getitem handle that, so the implementation
is more flexible than the PEP would indicate.
*/
Py_LOCAL_INLINE(int)
end_identifier(STRINGLIB_CHAR c)
{ {
switch (c) { Py_ssize_t accumulator = 0;
case '.': case '[': case ']': Py_ssize_t digitval;
return 1; Py_ssize_t oldaccumulator;
default: STRINGLIB_CHAR *p;
return 0;
}
}
/* /* empty string is an error */
get_integer consumes 0 or more decimal digit characters from an if (str->ptr >= str->end)
input string, updates *result with the corresponding positive return -1;
integer, and returns the number of digits consumed.
returns -1 on error. for (p = str->ptr; p < str->end; p++) {
*/ digitval = STRINGLIB_TODECIMAL(*p);
static int
get_integer(STRINGLIB_CHAR **ptr, STRINGLIB_CHAR *end,
Py_ssize_t *result)
{
Py_ssize_t accumulator, digitval, oldaccumulator;
int numdigits;
accumulator = numdigits = 0;
for (;;(*ptr)++, numdigits++) {
if (*ptr >= end)
break;
digitval = STRINGLIB_TODECIMAL(**ptr);
if (digitval < 0) if (digitval < 0)
break; return -1;
/* /*
This trick was copied from old Unicode format code. It's cute, This trick was copied from old Unicode format code. It's cute,
but would really suck on an old machine with a slow divide but would really suck on an old machine with a slow divide
@ -216,70 +175,215 @@ get_integer(STRINGLIB_CHAR **ptr, STRINGLIB_CHAR *end,
} }
accumulator += digitval; accumulator += digitval;
} }
*result = accumulator; return accumulator;
return numdigits;
}
/*
get_identifier is a bit of a misnomer. It returns a value for use
with getattr or getindex. This value will a string/unicode
object. The input cannot be zero length. Continues until end of
input, or end_identifier() returns true.
*/
static PyObject *
get_identifier(SubString *input)
{
STRINGLIB_CHAR *start;
for (start = input->ptr;
input->ptr < input->end && !end_identifier(*input->ptr);
input->ptr++)
;
return STRINGLIB_NEW(start, input->ptr - start);
/*
We might want to add code here to check for invalid Python
identifiers. All identifiers are eventually passed to getattr
or getitem, so there is a check when used. However, we might
want to remove (or not) the ability to have strings like
"a/b" or " ab" or "-1" (which is not parsed as a number).
For now, this is left as an exercise for the first disgruntled
user...
if (XXX -- need check function) {
Py_DECREF(result);
PyErr_SetString(PyExc_ValueError,
"Invalid embedded Python identifier");
return NULL;
}
*/
} }
/************************************************************************/ /************************************************************************/
/******** Functions to get field objects and specification strings ******/ /******** Functions to get field objects and specification strings ******/
/************************************************************************/ /************************************************************************/
/* get_field_and_spec is the main function in this section. It parses /* do the equivalent of obj.name */
the format string well enough to return a field object to render along
with a field specification string.
*/
/*
look up key in our keyword arguments
*/
static PyObject * static PyObject *
key_lookup(PyObject *kwargs, PyObject *key) getattr(PyObject *obj, SubString *name)
{ {
PyObject *result; PyObject *newobj;
PyObject *str = STRINGLIB_NEW(name->ptr, name->end - name->ptr);
if (kwargs && (result = PyDict_GetItem(kwargs, key)) != NULL) { if (str == NULL)
Py_INCREF(result); return NULL;
return result; newobj = PyObject_GetAttr(obj, str);
} Py_DECREF(str);
return NULL; return newobj;
} }
/* do the equivalent of obj[idx], where obj is a sequence */
static PyObject *
getitem_sequence(PyObject *obj, Py_ssize_t idx)
{
return PySequence_GetItem(obj, idx);
}
/* do the equivalent of obj[idx], where obj is not a sequence */
static PyObject *
getitem_idx(PyObject *obj, Py_ssize_t idx)
{
PyObject *newobj;
PyObject *idx_obj = PyInt_FromSsize_t(idx);
if (idx_obj == NULL)
return NULL;
newobj = PyObject_GetItem(obj, idx_obj);
Py_DECREF(idx_obj);
return newobj;
}
/* do the equivalent of obj[name] */
static PyObject *
getitem_str(PyObject *obj, SubString *name)
{
PyObject *newobj;
PyObject *str = STRINGLIB_NEW(name->ptr, name->end - name->ptr);
if (str == NULL)
return NULL;
newobj = PyObject_GetItem(obj, str);
Py_DECREF(str);
return newobj;
}
typedef struct {
/* the entire string we're parsing. we assume that someone else
is managing its lifetime, and that it will exist for the
lifetime of the iterator. can be empty */
SubString str;
/* pointer to where we are inside field_name */
STRINGLIB_CHAR *ptr;
} FieldNameIterator;
static int
FieldNameIterator_init(FieldNameIterator *self, STRINGLIB_CHAR *ptr,
Py_ssize_t len)
{
SubString_init(&self->str, ptr, len);
self->ptr = self->str.ptr;
return 1;
}
static int
_FieldNameIterator_attr(FieldNameIterator *self, SubString *name)
{
STRINGLIB_CHAR c;
name->ptr = self->ptr;
/* return everything until '.' or '[' */
while (self->ptr < self->str.end) {
switch (c = *self->ptr++) {
case '[':
case '.':
/* backup so that we this character will be seen next time */
self->ptr--;
break;
default:
continue;
}
break;
}
/* end of string is okay */
name->end = self->ptr;
return 1;
}
static int
_FieldNameIterator_item(FieldNameIterator *self, SubString *name)
{
STRINGLIB_CHAR c;
name->ptr = self->ptr;
/* return everything until ']' */
while (self->ptr < self->str.end) {
switch (c = *self->ptr++) {
case ']':
break;
default:
continue;
}
break;
}
/* end of string is okay */
/* don't include the ']' */
name->end = self->ptr-1;
return 1;
}
/* returns 0 on error, 1 on non-error termination, and 2 if it returns a value */
static int
FieldNameIterator_next(FieldNameIterator *self, int *is_attribute,
Py_ssize_t *name_idx, SubString *name)
{
/* check at end of input */
if (self->ptr >= self->str.end)
return 1;
switch (*self->ptr++) {
case '.':
*is_attribute = 1;
if (_FieldNameIterator_attr(self, name) == 0) {
return 0;
}
*name_idx = -1;
break;
case '[':
*is_attribute = 0;
if (_FieldNameIterator_item(self, name) == 0) {
return 0;
}
*name_idx = get_integer(name);
break;
default:
/* interal error, can't get here */
assert(0);
return 0;
}
/* empty string is an error */
if (name->ptr == name->end) {
PyErr_SetString(PyExc_ValueError, "Empty attribute in format string");
return 0;
}
return 2;
}
/* input: field_name
output: 'first' points to the part before the first '[' or '.'
'first_idx' is -1 if 'first' is not an integer, otherwise
it's the value of first converted to an integer
'rest' is an iterator to return the rest
*/
static int
field_name_split(STRINGLIB_CHAR *ptr, Py_ssize_t len, SubString *first,
Py_ssize_t *first_idx, FieldNameIterator *rest)
{
STRINGLIB_CHAR c;
STRINGLIB_CHAR *p = ptr;
STRINGLIB_CHAR *end = ptr + len;
/* find the part up until the first '.' or '[' */
while (p < end) {
switch (c = *p++) {
case '[':
case '.':
/* backup so that we this character is available to the
"rest" iterator */
p--;
break;
default:
continue;
}
break;
}
/* set up the return values */
SubString_init(first, ptr, p - ptr);
FieldNameIterator_init(rest, p, end - p);
/* see if "first" is an integer, in which case it's used as an index */
*first_idx = get_integer(first);
/* zero length string is an error */
if (first->ptr >= first->end) {
PyErr_SetString(PyExc_ValueError, "empty field name");
goto error;
}
return 1;
error:
return 0;
}
/* /*
get_field_object returns the object inside {}, before the get_field_object returns the object inside {}, before the
format_spec. It handles getindex and getattr lookups and consumes format_spec. It handles getindex and getattr lookups and consumes
@ -288,80 +392,71 @@ key_lookup(PyObject *kwargs, PyObject *key)
static PyObject * static PyObject *
get_field_object(SubString *input, PyObject *args, PyObject *kwargs) get_field_object(SubString *input, PyObject *args, PyObject *kwargs)
{ {
PyObject *myobj, *subobj, *newobj; PyObject *obj = NULL;
STRINGLIB_CHAR c; int ok;
int is_attribute;
SubString name;
SubString first;
Py_ssize_t index; Py_ssize_t index;
int isindex, isnumeric, isargument; FieldNameIterator rest;
index = isnumeric = 0; /* Just to shut up the compiler warnings */ if (!field_name_split(input->ptr, input->end - input->ptr, &first,
&index, &rest)) {
goto error;
}
myobj = args; if (index == -1) {
Py_INCREF(myobj); /* look up in kwargs */
PyObject *key = STRINGLIB_NEW(first.ptr, first.end - first.ptr);
for (isindex=1, isargument=1;;) { if (key == NULL)
if (!check_input(input)) goto error;
break; if ((kwargs == NULL) || (obj = PyDict_GetItem(kwargs, key)) == NULL) {
if (!isindex) { PyErr_SetString(PyExc_ValueError, "Keyword argument not found "
if ((subobj = get_identifier(input)) == NULL) "in format string");
break; Py_DECREF(key);
newobj = PyObject_GetAttr(myobj, subobj); goto error;
Py_DECREF(subobj);
} else {
isnumeric = (STRINGLIB_ISDECIMAL(*input->ptr));
if (isnumeric)
/* XXX: add error checking */
get_integer(&input->ptr, input->end, &index);
if (isnumeric && PySequence_Check(myobj))
newobj = PySequence_GetItem(myobj, index);
else {
/* XXX -- do we need PyLong_FromLongLong?
Using ssizet, not int... */
subobj = isnumeric ?
PyInt_FromLong(index) :
get_identifier(input);
if (subobj == NULL)
break;
if (isargument) {
newobj = key_lookup(kwargs, subobj);
} else {
newobj = PyObject_GetItem(myobj, subobj);
}
Py_DECREF(subobj);
}
} }
Py_DECREF(myobj); } else {
myobj = newobj; /* look up in args */
if (myobj == NULL) obj = PySequence_GetItem(args, index);
break; if (obj == NULL) {
if (!isargument && isindex) /* translate IndexError to a ValueError */
if ((!check_input(input)) || (*(input->ptr++) != ']')) { PyErr_SetString(PyExc_ValueError, "Not enough positional arguments "
SetError("Expected ]"); "in format string");
break; goto error;
}
/* if at the end of input, return with myobj */
if (input->ptr >= input->end)
return myobj;
c = *input->ptr;
input->ptr++;
isargument = 0;
isindex = (c == '[');
if (!isindex && (c != '.')) {
SetError("Expected ., [, :, !, or }");
break;
} }
} }
if ((myobj == NULL) && isargument) {
/* XXX: include more useful error information, like which /* iterate over the rest of the field_name */
* keyword not found or which index missing */ while ((ok = FieldNameIterator_next(&rest, &is_attribute, &index,
PyErr_Clear(); &name)) == 2) {
return SetError(isnumeric PyObject *tmp;
? "Not enough positional arguments"
: "Keyword argument not found"); if (is_attribute)
/* getattr lookup "." */
tmp = getattr(obj, &name);
else
/* getitem lookup "[]" */
if (index == -1)
tmp = getitem_str(obj, &name);
else
if (PySequence_Check(obj))
tmp = getitem_sequence(obj, index);
else
/* not a sequence */
tmp = getitem_idx(obj, index);
if (tmp == NULL)
goto error;
/* assign to obj */
Py_DECREF(obj);
obj = tmp;
} }
Py_XDECREF(myobj); /* end of iterator, this is the non-error case */
if (ok == 1)
return obj;
error:
Py_XDECREF(obj);
return NULL; return NULL;
} }

View File

@ -9161,9 +9161,8 @@ typedef struct {
static void static void
formatteriter_dealloc(formatteriterobject *it) formatteriter_dealloc(formatteriterobject *it)
{ {
_PyObject_GC_UNTRACK(it); Py_XDECREF(it->str);
Py_XDECREF(it->str); PyObject_FREE(it);
PyObject_GC_Del(it);
} }
/* returns a tuple: /* returns a tuple:
@ -9313,7 +9312,7 @@ _unicodeformatter_iterator(PyObject *str)
{ {
formatteriterobject *it; formatteriterobject *it;
it = PyObject_GC_New(formatteriterobject, &PyFormatterIter_Type); it = PyObject_New(formatteriterobject, &PyFormatterIter_Type);
if (it == NULL) if (it == NULL)
return NULL; return NULL;
@ -9326,17 +9325,167 @@ _unicodeformatter_iterator(PyObject *str)
PyUnicode_AS_UNICODE(str), PyUnicode_AS_UNICODE(str),
PyUnicode_GET_SIZE(str)); PyUnicode_GET_SIZE(str));
_PyObject_GC_TRACK(it);
return (PyObject *)it; return (PyObject *)it;
} }
PyObject * /********************* FieldName Iterator ************************/
_unicodeformatter_lookup(PyObject *field_name, PyObject *args,
PyObject *kwargs) /* this is used to implement string.Formatter.vparse(). it parses
the field name into attribute and item values. */
typedef struct {
PyObject_HEAD
/* we know this to be a unicode object, but since we just keep
it around to keep the object alive, having it as PyObject
is okay */
PyObject *str;
FieldNameIterator it_field;
} fieldnameiterobject;
static void
fieldnameiter_dealloc(fieldnameiterobject *it)
{ {
Py_XDECREF(it->str);
PyObject_FREE(it);
}
/* returns a tuple:
(is_attr, value)
is_attr is true if we used attribute syntax (e.g., '.foo')
false if we used index syntax (e.g., '[foo]')
value is an integer or string
*/
static PyObject *
fieldnameiter_next(fieldnameiterobject *it)
{
int result;
int is_attr;
Py_ssize_t idx;
SubString name;
result = FieldNameIterator_next(&it->it_field, &is_attr,
&idx, &name);
if (result == 0 || result == 1) {
/* if 0, error has already been set, if 1, iterator is empty */
return NULL;
} else {
PyObject* result = NULL;
PyObject* is_attr_obj = NULL;
PyObject* obj = NULL;
is_attr_obj = PyBool_FromLong(is_attr);
if (is_attr_obj == NULL)
goto error;
/* either an integer or a string */
if (idx != -1)
obj = PyInt_FromSsize_t(idx);
else
obj = STRINGLIB_NEW(name.ptr, name.end - name.ptr);
if (obj == NULL)
goto error;
/* return a tuple of values */
result = PyTuple_Pack(2, is_attr_obj, obj);
if (result == NULL)
goto error;
return result;
error:
Py_XDECREF(result);
Py_XDECREF(is_attr_obj);
Py_XDECREF(obj);
return NULL;
}
return NULL; return NULL;
} }
static PyMethodDef fieldnameiter_methods[] = {
{NULL, NULL} /* sentinel */
};
static PyTypeObject PyFieldNameIter_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"fieldnameiterator", /* tp_name */
sizeof(fieldnameiterobject), /* tp_basicsize */
0, /* tp_itemsize */
/* methods */
(destructor)fieldnameiter_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_compare */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
PyObject_GenericGetAttr, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
0, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
PyObject_SelfIter, /* tp_iter */
(iternextfunc)fieldnameiter_next, /* tp_iternext */
fieldnameiter_methods, /* tp_methods */
0};
PyObject *
_unicodeformatter_field_name_split(PyObject *field_name)
{
SubString first;
Py_ssize_t first_idx;
fieldnameiterobject *it;
PyObject *first_obj = NULL;
PyObject *it_obj = NULL;
PyObject *result;
it = PyObject_New(fieldnameiterobject, &PyFieldNameIter_Type);
if (it == NULL)
goto error;
it->str = NULL;
it_obj = (PyObject *)it;
if (!field_name_split(STRINGLIB_STR(field_name),
STRINGLIB_LEN(field_name),
&first, &first_idx, &it->it_field))
goto error;
/* first becomes an integer, if possible, else a string */
if (first_idx != -1)
first_obj = PyInt_FromSsize_t(first_idx);
else
/* convert "first" into a string object */
first_obj = STRINGLIB_NEW(first.ptr, first.end - first.ptr);
if (first_obj == NULL)
goto error;
/* take ownership, give the object to the iterator. this is
just to keep the field_name alive */
Py_INCREF(field_name);
it->str = field_name;
/* return a tuple of values */
result = PyTuple_Pack(2, first_obj, it_obj);
if (result == NULL)
goto error;
return result;
error:
Py_XDECREF(it_obj);
Py_XDECREF(first_obj);
return NULL;
}
/********************* Unicode Iterator **************************/ /********************* Unicode Iterator **************************/

View File

@ -683,28 +683,28 @@ sys_formatter_iterator(PyObject *self, PyObject *args)
return _unicodeformatter_iterator(str); return _unicodeformatter_iterator(str);
} }
/* sys_formatter_lookup is used to implement string.Formatter.vformat. /* sys_formatter_field_name_split is used to implement
it takes an PEP 3101 "field name", args, and kwargs, and returns a string.Formatter.vformat. it takes an PEP 3101 "field name", and
tuple (index, name, object). see unicodeobject.c's returns a tuple of (first, rest): "first", the part before the
_unicodeformatter_lookup for details */ first '.' or '['; and "rest", an iterator for the rest of the field
name. see unicodeobjects' _unicode_formatter_field_name_split for
details */
static PyObject * static PyObject *
sys_formatter_lookup(PyObject *self, PyObject *args) sys_formatter_field_name_split(PyObject *self, PyObject *args)
{ {
PyObject *field_name; PyObject *field_name;
PyObject *arg_args;
PyObject *kwargs;
if (!PyArg_ParseTuple(args, "OOO:_formatter_lookup", &field_name, if (!PyArg_ParseTuple(args, "O:_formatter_field_name_split",
&arg_args, &kwargs)) &field_name))
return NULL; return NULL;
if (!PyUnicode_Check(field_name)) { if (!PyUnicode_Check(field_name)) {
PyErr_SetString(PyExc_TypeError, PyErr_SetString(PyExc_TypeError, "_formatter_field_name_split "
"_formatter_lookup expects unicode object"); "expects unicode object");
return NULL; return NULL;
} }
return _unicodeformatter_lookup(field_name, arg_args, kwargs); return _unicodeformatter_field_name_split(field_name);
} }
@ -773,7 +773,8 @@ static PyMethodDef sys_methods[] = {
{"_current_frames", sys_current_frames, METH_NOARGS, {"_current_frames", sys_current_frames, METH_NOARGS,
current_frames_doc}, current_frames_doc},
{"_formatter_parser", sys_formatter_iterator, METH_VARARGS}, {"_formatter_parser", sys_formatter_iterator, METH_VARARGS},
{"_formatter_lookup", sys_formatter_lookup, METH_VARARGS}, {"_formatter_field_name_split", sys_formatter_field_name_split,
METH_VARARGS},
{"displayhook", sys_displayhook, METH_O, displayhook_doc}, {"displayhook", sys_displayhook, METH_O, displayhook_doc},
{"exc_info", sys_exc_info, METH_NOARGS, exc_info_doc}, {"exc_info", sys_exc_info, METH_NOARGS, exc_info_doc},
{"excepthook", sys_excepthook, METH_VARARGS, excepthook_doc}, {"excepthook", sys_excepthook, METH_VARARGS, excepthook_doc},