summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexandre Vassalotti <alexandre@peadrop.com>2008-06-11 22:58:36 (GMT)
committerAlexandre Vassalotti <alexandre@peadrop.com>2008-06-11 22:58:36 (GMT)
commit794652dd064590d7188d93d9bf524ae9e1558386 (patch)
treea0011f0cfa8fef8fcda771eceadad50e1f95b42d
parent502d89ed1518687861563293cb761d268321fa4a (diff)
downloadcpython-794652dd064590d7188d93d9bf524ae9e1558386.zip
cpython-794652dd064590d7188d93d9bf524ae9e1558386.tar.gz
cpython-794652dd064590d7188d93d9bf524ae9e1558386.tar.bz2
Issue 2918: Merge StringIO and cStringIO.
-rw-r--r--Lib/io.py288
-rw-r--r--Lib/test/test_memoryio.py10
-rw-r--r--Lib/test/test_minidom.py3
-rw-r--r--Lib/test/test_uu.py34
-rw-r--r--Lib/xml/dom/minidom.py7
-rw-r--r--Misc/NEWS2
-rw-r--r--Modules/_stringio.c379
-rw-r--r--setup.py1
8 files changed, 702 insertions, 22 deletions
diff --git a/Lib/io.py b/Lib/io.py
index 0c993b1..f543e20 100644
--- a/Lib/io.py
+++ b/Lib/io.py
@@ -1769,20 +1769,20 @@ class TextIOWrapper(TextIOBase):
def newlines(self):
return self._decoder.newlines if self._decoder else None
-class StringIO(TextIOWrapper):
- """An in-memory stream for text. The initial_value argument sets the
- value of object. The other arguments are like those of TextIOWrapper's
- constructor.
+class _StringIO(TextIOWrapper):
+ """Text I/O implementation using an in-memory buffer.
+
+ The initial_value argument sets the value of object. The newline
+ argument is like the one of TextIOWrapper's constructor.
"""
# XXX This is really slow, but fully functional
- def __init__(self, initial_value="", encoding="utf-8",
- errors="strict", newline="\n"):
- super(StringIO, self).__init__(BytesIO(),
- encoding=encoding,
- errors=errors,
- newline=newline)
+ def __init__(self, initial_value="", newline="\n"):
+ super(_StringIO, self).__init__(BytesIO(),
+ encoding="utf-8",
+ errors="strict",
+ newline=newline)
if initial_value:
if not isinstance(initial_value, str):
initial_value = str(initial_value)
@@ -1792,3 +1792,271 @@ class StringIO(TextIOWrapper):
def getvalue(self):
self.flush()
return self.buffer.getvalue().decode(self._encoding, self._errors)
+
+try:
+ import _stringio
+
+ # This subclass is a reimplementation of the TextIOWrapper
+ # interface without any of its text decoding facilities. All the
+ # stored data is manipulated with the efficient
+ # _stringio._StringIO extension type. Also, the newline decoding
+ # mechanism of IncrementalNewlineDecoder is reimplemented here for
+ # efficiency. Doing otherwise, would require us to implement a
+ # fake decoder which would add an additional and unnecessary layer
+ # on top of the _StringIO methods.
+
+ class StringIO(_stringio._StringIO, TextIOBase):
+ """Text I/O implementation using an in-memory buffer.
+
+ The initial_value argument sets the value of object. The newline
+ argument is like the one of TextIOWrapper's constructor.
+ """
+
+ _CHUNK_SIZE = 4096
+
+ def __init__(self, initial_value="", newline="\n"):
+ if newline not in (None, "", "\n", "\r", "\r\n"):
+ raise ValueError("illegal newline value: %r" % (newline,))
+
+ self._readuniversal = not newline
+ self._readtranslate = newline is None
+ self._readnl = newline
+ self._writetranslate = newline != ""
+ self._writenl = newline or os.linesep
+ self._pending = ""
+ self._seennl = 0
+
+ # Reset the buffer first, in case __init__ is called
+ # multiple times.
+ self.truncate(0)
+ if initial_value is None:
+ initial_value = ""
+ self.write(initial_value)
+ self.seek(0)
+
+ @property
+ def buffer(self):
+ raise UnsupportedOperation("%s.buffer attribute is unsupported" %
+ self.__class__.__name__)
+
+ def _decode_newlines(self, input, final=False):
+ # decode input (with the eventual \r from a previous pass)
+ if self._pending:
+ input = self._pending + input
+
+ # retain last \r even when not translating data:
+ # then readline() is sure to get \r\n in one pass
+ if input.endswith("\r") and not final:
+ input = input[:-1]
+ self._pending = "\r"
+ else:
+ self._pending = ""
+
+ # Record which newlines are read
+ crlf = input.count('\r\n')
+ cr = input.count('\r') - crlf
+ lf = input.count('\n') - crlf
+ self._seennl |= (lf and self._LF) | (cr and self._CR) \
+ | (crlf and self._CRLF)
+
+ if self._readtranslate:
+ if crlf:
+ output = input.replace("\r\n", "\n")
+ if cr:
+ output = input.replace("\r", "\n")
+ else:
+ output = input
+
+ return output
+
+ def writable(self):
+ return True
+
+ def readable(self):
+ return True
+
+ def seekable(self):
+ return True
+
+ _read = _stringio._StringIO.read
+ _write = _stringio._StringIO.write
+ _tell = _stringio._StringIO.tell
+ _seek = _stringio._StringIO.seek
+ _truncate = _stringio._StringIO.truncate
+ _getvalue = _stringio._StringIO.getvalue
+
+ def getvalue(self) -> str:
+ """Retrieve the entire contents of the object."""
+ if self.closed:
+ raise ValueError("read on closed file")
+ return self._getvalue()
+
+ def write(self, s: str) -> int:
+ """Write string s to file.
+
+ Returns the number of characters written.
+ """
+ if self.closed:
+ raise ValueError("write to closed file")
+ if not isinstance(s, str):
+ raise TypeError("can't write %s to text stream" %
+ s.__class__.__name__)
+ length = len(s)
+ if self._writetranslate and self._writenl != "\n":
+ s = s.replace("\n", self._writenl)
+ self._pending = ""
+ self._write(s)
+ return length
+
+ def read(self, n: int = None) -> str:
+ """Read at most n characters, returned as a string.
+
+ If the argument is negative or omitted, read until EOF
+ is reached. Return an empty string at EOF.
+ """
+ if self.closed:
+ raise ValueError("read to closed file")
+ if n is None:
+ n = -1
+ res = self._pending
+ if n < 0:
+ res += self._decode_newlines(self._read(), True)
+ self._pending = ""
+ return res
+ else:
+ res = self._decode_newlines(self._read(n), True)
+ self._pending = res[n:]
+ return res[:n]
+
+ def tell(self) -> int:
+ """Tell the current file position."""
+ if self.closed:
+ raise ValueError("tell from closed file")
+ if self._pending:
+ return self._tell() - len(self._pending)
+ else:
+ return self._tell()
+
+ def seek(self, pos: int = None, whence: int = 0) -> int:
+ """Change stream position.
+
+ Seek to character offset pos relative to position indicated by whence:
+ 0 Start of stream (the default). pos should be >= 0;
+ 1 Current position - pos must be 0;
+ 2 End of stream - pos must be 0.
+ Returns the new absolute position.
+ """
+ if self.closed:
+ raise ValueError("seek from closed file")
+ self._pending = ""
+ return self._seek(pos, whence)
+
+ def truncate(self, pos: int = None) -> int:
+ """Truncate size to pos.
+
+ The pos argument defaults to the current file position, as
+ returned by tell(). Imply an absolute seek to pos.
+ Returns the new absolute position.
+ """
+ if self.closed:
+ raise ValueError("truncate from closed file")
+ self._pending = ""
+ return self._truncate(pos)
+
+ def readline(self, limit: int = None) -> str:
+ if self.closed:
+ raise ValueError("read from closed file")
+ if limit is None:
+ limit = -1
+ if limit >= 0:
+ # XXX: Hack to support limit argument, for backwards
+ # XXX compatibility
+ line = self.readline()
+ if len(line) <= limit:
+ return line
+ line, self._pending = line[:limit], line[limit:] + self._pending
+ return line
+
+ line = self._pending
+ self._pending = ""
+
+ start = 0
+ pos = endpos = None
+ while True:
+ if self._readtranslate:
+ # Newlines are already translated, only search for \n
+ pos = line.find('\n', start)
+ if pos >= 0:
+ endpos = pos + 1
+ break
+ else:
+ start = len(line)
+
+ elif self._readuniversal:
+ # Universal newline search. Find any of \r, \r\n, \n
+ # The decoder ensures that \r\n are not split in two pieces
+
+ # In C we'd look for these in parallel of course.
+ nlpos = line.find("\n", start)
+ crpos = line.find("\r", start)
+ if crpos == -1:
+ if nlpos == -1:
+ # Nothing found
+ start = len(line)
+ else:
+ # Found \n
+ endpos = nlpos + 1
+ break
+ elif nlpos == -1:
+ # Found lone \r
+ endpos = crpos + 1
+ break
+ elif nlpos < crpos:
+ # Found \n
+ endpos = nlpos + 1
+ break
+ elif nlpos == crpos + 1:
+ # Found \r\n
+ endpos = crpos + 2
+ break
+ else:
+ # Found \r
+ endpos = crpos + 1
+ break
+ else:
+ # non-universal
+ pos = line.find(self._readnl)
+ if pos >= 0:
+ endpos = pos + len(self._readnl)
+ break
+
+ # No line ending seen yet - get more data
+ more_line = self.read(self._CHUNK_SIZE)
+ if more_line:
+ line += more_line
+ else:
+ # end of file
+ return line
+
+ self._pending = line[endpos:]
+ return line[:endpos]
+
+ _LF = 1
+ _CR = 2
+ _CRLF = 4
+
+ @property
+ def newlines(self):
+ return (None,
+ "\n",
+ "\r",
+ ("\r", "\n"),
+ "\r\n",
+ ("\n", "\r\n"),
+ ("\r", "\r\n"),
+ ("\r", "\n", "\r\n")
+ )[self._seennl]
+
+
+except ImportError:
+ StringIO = _StringIO
diff --git a/Lib/test/test_memoryio.py b/Lib/test/test_memoryio.py
index 2d91cbd..d1745bc 100644
--- a/Lib/test/test_memoryio.py
+++ b/Lib/test/test_memoryio.py
@@ -10,7 +10,7 @@ import io
import sys
try:
- import _bytesio
+ import _bytesio, _stringio
has_c_implementation = True
except ImportError:
has_c_implementation = False
@@ -373,7 +373,7 @@ class PyBytesIOTest(MemoryTestMixin, unittest.TestCase):
class PyStringIOTest(MemoryTestMixin, unittest.TestCase):
buftype = str
- ioclass = io.StringIO
+ ioclass = io._StringIO
EOF = ""
def test_relative_seek(self):
@@ -404,10 +404,14 @@ if has_c_implementation:
class CBytesIOTest(PyBytesIOTest):
ioclass = io.BytesIO
+ class CStringIOTest(PyStringIOTest):
+ ioclass = io.StringIO
+
+
def test_main():
tests = [PyBytesIOTest, PyStringIOTest]
if has_c_implementation:
- tests.extend([CBytesIOTest])
+ tests.extend([CBytesIOTest, CStringIOTest])
support.run_unittest(*tests)
if __name__ == '__main__':
diff --git a/Lib/test/test_minidom.py b/Lib/test/test_minidom.py
index ca1f836..c4c568f 100644
--- a/Lib/test/test_minidom.py
+++ b/Lib/test/test_minidom.py
@@ -3,7 +3,6 @@
import os
import sys
import pickle
-from io import StringIO
from test.support import verbose, run_unittest, TestSkipped
import unittest
@@ -80,7 +79,7 @@ class MinidomTest(unittest.TestCase):
self.confirm(t == s, "looking for %s, found %s" % (repr(s), repr(t)))
def testParseFromFile(self):
- dom = parse(StringIO(open(tstfile).read()))
+ dom = parse(open(tstfile))
dom.unlink()
self.confirm(isinstance(dom, Document))
diff --git a/Lib/test/test_uu.py b/Lib/test/test_uu.py
index 02d0171..d2b6e73 100644
--- a/Lib/test/test_uu.py
+++ b/Lib/test/test_uu.py
@@ -17,6 +17,32 @@ encodedtext = b"""\
M5&AE('-M;V]T:\"US8V%L960@<'ET:&]N(&-R97!T(&]V97(@=&AE('-L965P
(:6YG(&1O9PH """
+# Stolen from io.py
+class FakeIO(io.TextIOWrapper):
+ """Text I/O implementation using an in-memory buffer.
+
+ Can be a used as a drop-in replacement for sys.stdin and sys.stdout.
+ """
+
+ # XXX This is really slow, but fully functional
+
+ def __init__(self, initial_value="", encoding="utf-8",
+ errors="strict", newline="\n"):
+ super(FakeIO, self).__init__(io.BytesIO(),
+ encoding=encoding,
+ errors=errors,
+ newline=newline)
+ if initial_value:
+ if not isinstance(initial_value, str):
+ initial_value = str(initial_value)
+ self.write(initial_value)
+ self.seek(0)
+
+ def getvalue(self):
+ self.flush()
+ return self.buffer.getvalue().decode(self._encoding, self._errors)
+
+
def encodedtextwrapped(mode, filename):
return (bytes("begin %03o %s\n" % (mode, filename), "ascii") +
encodedtext + b"\n \nend\n")
@@ -76,15 +102,15 @@ class UUStdIOTest(unittest.TestCase):
sys.stdout = self.stdout
def test_encode(self):
- sys.stdin = io.StringIO(plaintext.decode("ascii"))
- sys.stdout = io.StringIO()
+ sys.stdin = FakeIO(plaintext.decode("ascii"))
+ sys.stdout = FakeIO()
uu.encode("-", "-", "t1", 0o666)
self.assertEqual(sys.stdout.getvalue(),
encodedtextwrapped(0o666, "t1").decode("ascii"))
def test_decode(self):
- sys.stdin = io.StringIO(encodedtextwrapped(0o666, "t1").decode("ascii"))
- sys.stdout = io.StringIO()
+ sys.stdin = FakeIO(encodedtextwrapped(0o666, "t1").decode("ascii"))
+ sys.stdout = FakeIO()
uu.decode("-", "-")
stdout = sys.stdout
sys.stdout = self.stdout
diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py
index f229369..3025ed7 100644
--- a/Lib/xml/dom/minidom.py
+++ b/Lib/xml/dom/minidom.py
@@ -14,6 +14,7 @@ Todo:
* SAX 2 namespaces
"""
+import codecs
import io
import xml.dom
@@ -49,16 +50,16 @@ class Node(xml.dom.Node):
# indent = the indentation string to prepend, per level
# newl = the newline string to append
use_encoding = "utf-8" if encoding is None else encoding
- writer = io.StringIO(encoding=use_encoding)
+ writer = codecs.getwriter(use_encoding)(io.BytesIO())
if self.nodeType == Node.DOCUMENT_NODE:
# Can pass encoding only to document, to put it into XML header
self.writexml(writer, "", indent, newl, encoding)
else:
self.writexml(writer, "", indent, newl)
if encoding is None:
- return writer.getvalue()
+ return writer.stream.getvalue().decode(use_encoding)
else:
- return writer.buffer.getvalue()
+ return writer.stream.getvalue()
def hasChildNodes(self):
if self.childNodes:
diff --git a/Misc/NEWS b/Misc/NEWS
index 6bf1441..c6f91ef 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -78,6 +78,8 @@ Extension Modules
Library
-------
+- Added C optimized implementation of io.StringIO.
+
- The ``pickle`` module is now automatically use an optimized C
implementation of Pickler and Unpickler when available. The
``cPickle`` module is no longer needed.
diff --git a/Modules/_stringio.c b/Modules/_stringio.c
new file mode 100644
index 0000000..83fc79e
--- /dev/null
+++ b/Modules/_stringio.c
@@ -0,0 +1,379 @@
+#include "Python.h"
+
+/* This module is a stripped down version of _bytesio.c with a Py_UNICODE
+ buffer. Most of the functionality is provided by subclassing _StringIO. */
+
+
+typedef struct {
+ PyObject_HEAD
+ Py_UNICODE *buf;
+ Py_ssize_t pos;
+ Py_ssize_t string_size;
+ size_t buf_size;
+} StringIOObject;
+
+
+/* Internal routine for changing the size, in terms of characters, of the
+ buffer of StringIO objects. The caller should ensure that the 'size'
+ argument is non-negative. Returns 0 on success, -1 otherwise. */
+static int
+resize_buffer(StringIOObject *self, size_t size)
+{
+ /* Here, unsigned types are used to avoid dealing with signed integer
+ overflow, which is undefined in C. */
+ size_t alloc = self->buf_size;
+ Py_UNICODE *new_buf = NULL;
+
+ assert(self->buf != NULL);
+
+ /* For simplicity, stay in the range of the signed type. Anyway, Python
+ doesn't allow strings to be longer than this. */
+ if (size > PY_SSIZE_T_MAX)
+ goto overflow;
+
+ if (size < alloc / 2) {
+ /* Major downsize; resize down to exact size. */
+ alloc = size + 1;
+ }
+ else if (size < alloc) {
+ /* Within allocated size; quick exit */
+ return 0;
+ }
+ else if (size <= alloc * 1.125) {
+ /* Moderate upsize; overallocate similar to list_resize() */
+ alloc = size + (size >> 3) + (size < 9 ? 3 : 6);
+ }
+ else {
+ /* Major upsize; resize up to exact size */
+ alloc = size + 1;
+ }
+
+ if (alloc > ((size_t)-1) / sizeof(Py_UNICODE))
+ goto overflow;
+ new_buf = (Py_UNICODE *)PyMem_Realloc(self->buf,
+ alloc * sizeof(Py_UNICODE));
+ if (new_buf == NULL) {
+ PyErr_NoMemory();
+ return -1;
+ }
+ self->buf_size = alloc;
+ self->buf = new_buf;
+
+ return 0;
+
+ overflow:
+ PyErr_SetString(PyExc_OverflowError,
+ "new buffer size too large");
+ return -1;
+}
+
+/* Internal routine for writing a string of characters to the buffer of a
+ StringIO object. Returns the number of bytes wrote, or -1 on error. */
+static Py_ssize_t
+write_str(StringIOObject *self, const Py_UNICODE *str, Py_ssize_t len)
+{
+ assert(self->buf != NULL);
+ assert(self->pos >= 0);
+ assert(len >= 0);
+
+ /* This overflow check is not strictly necessary. However, it avoids us to
+ deal with funky things like comparing an unsigned and a signed
+ integer. */
+ if (self->pos > PY_SSIZE_T_MAX - len) {
+ PyErr_SetString(PyExc_OverflowError,
+ "new position too large");
+ return -1;
+ }
+ if (self->pos + len > self->string_size) {
+ if (resize_buffer(self, self->pos + len) < 0)
+ return -1;
+ }
+
+ if (self->pos > self->string_size) {
+ /* In case of overseek, pad with null bytes the buffer region between
+ the end of stream and the current position.
+
+ 0 lo string_size hi
+ | |<---used--->|<----------available----------->|
+ | | <--to pad-->|<---to write---> |
+ 0 buf positon
+
+ */
+ memset(self->buf + self->string_size, '\0',
+ (self->pos - self->string_size) * sizeof(Py_UNICODE));
+ }
+
+ /* Copy the data to the internal buffer, overwriting some of the
+ existing data if self->pos < self->string_size. */
+ memcpy(self->buf + self->pos, str, len * sizeof(Py_UNICODE));
+ self->pos += len;
+
+ /* Set the new length of the internal string if it has changed */
+ if (self->string_size < self->pos) {
+ self->string_size = self->pos;
+ }
+
+ return len;
+}
+
+static PyObject *
+stringio_getvalue(StringIOObject *self)
+{
+ return PyUnicode_FromUnicode(self->buf, self->string_size);
+}
+
+static PyObject *
+stringio_tell(StringIOObject *self)
+{
+ return PyLong_FromSsize_t(self->pos);
+}
+
+static PyObject *
+stringio_read(StringIOObject *self, PyObject *args)
+{
+ Py_ssize_t size, n;
+ Py_UNICODE *output;
+ PyObject *arg = Py_None;
+
+ if (!PyArg_ParseTuple(args, "|O:read", &arg))
+ return NULL;
+
+ if (PyLong_Check(arg)) {
+ size = PyLong_AsSsize_t(arg);
+ }
+ else if (arg == Py_None) {
+ /* Read until EOF is reached, by default. */
+ size = -1;
+ }
+ else {
+ PyErr_Format(PyExc_TypeError, "integer argument expected, got '%s'",
+ Py_TYPE(arg)->tp_name);
+ return NULL;
+ }
+
+ /* adjust invalid sizes */
+ n = self->string_size - self->pos;
+ if (size < 0 || size > n) {
+ size = n;
+ if (size < 0)
+ size = 0;
+ }
+
+ assert(self->buf != NULL);
+ output = self->buf + self->pos;
+ self->pos += size;
+
+ return PyUnicode_FromUnicode(output, size);
+}
+
+static PyObject *
+stringio_truncate(StringIOObject *self, PyObject *args)
+{
+ Py_ssize_t size;
+ PyObject *arg = Py_None;
+
+ if (!PyArg_ParseTuple(args, "|O:truncate", &arg))
+ return NULL;
+
+ if (PyLong_Check(arg)) {
+ size = PyLong_AsSsize_t(arg);
+ }
+ else if (arg == Py_None) {
+ /* Truncate to current position if no argument is passed. */
+ size = self->pos;
+ }
+ else {
+ PyErr_Format(PyExc_TypeError, "integer argument expected, got '%s'",
+ Py_TYPE(arg)->tp_name);
+ return NULL;
+ }
+
+ if (size < 0) {
+ PyErr_Format(PyExc_ValueError,
+ "Negative size value %zd", size);
+ return NULL;
+ }
+
+ if (size < self->string_size) {
+ self->string_size = size;
+ if (resize_buffer(self, size) < 0)
+ return NULL;
+ }
+ self->pos = size;
+
+ return PyLong_FromSsize_t(size);
+}
+
+static PyObject *
+stringio_seek(StringIOObject *self, PyObject *args)
+{
+ Py_ssize_t pos;
+ int mode = 0;
+
+ if (!PyArg_ParseTuple(args, "n|i:seek", &pos, &mode))
+ return NULL;
+
+ if (mode != 0 && mode != 1 && mode != 2) {
+ PyErr_Format(PyExc_ValueError,
+ "Invalid whence (%i, should be 0, 1 or 2)", mode);
+ return NULL;
+ }
+ else if (pos < 0 && mode == 0) {
+ PyErr_Format(PyExc_ValueError,
+ "Negative seek position %zd", pos);
+ return NULL;
+ }
+ else if (mode != 0 && pos != 0) {
+ PyErr_SetString(PyExc_IOError,
+ "Can't do nonzero cur-relative seeks");
+ return NULL;
+ }
+
+ /* mode 0: offset relative to beginning of the string.
+ mode 1: no change to current position.
+ mode 2: change position to end of file. */
+ if (mode == 1) {
+ pos = self->pos;
+ }
+ else if (mode == 2) {
+ pos = self->string_size;
+ }
+
+ self->pos = pos;
+
+ return PyLong_FromSsize_t(self->pos);
+}
+
+static PyObject *
+stringio_write(StringIOObject *self, PyObject *obj)
+{
+ const Py_UNICODE *str;
+ Py_ssize_t size;
+ Py_ssize_t n = 0;
+
+ if (PyUnicode_Check(obj)) {
+ str = PyUnicode_AsUnicode(obj);
+ size = PyUnicode_GetSize(obj);
+ }
+ else {
+ PyErr_Format(PyExc_TypeError, "string argument expected, got '%s'",
+ Py_TYPE(obj)->tp_name);
+ return NULL;
+ }
+
+ if (size != 0) {
+ n = write_str(self, str, size);
+ if (n < 0)
+ return NULL;
+ }
+
+ return PyLong_FromSsize_t(n);
+}
+
+static void
+stringio_dealloc(StringIOObject *self)
+{
+ PyMem_Free(self->buf);
+ Py_TYPE(self)->tp_free(self);
+}
+
+static PyObject *
+stringio_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+ StringIOObject *self;
+
+ assert(type != NULL && type->tp_alloc != NULL);
+ self = (StringIOObject *)type->tp_alloc(type, 0);
+ if (self == NULL)
+ return NULL;
+
+ self->string_size = 0;
+ self->pos = 0;
+ self->buf_size = 0;
+ self->buf = (Py_UNICODE *)PyMem_Malloc(0);
+ if (self->buf == NULL) {
+ Py_DECREF(self);
+ return PyErr_NoMemory();
+ }
+
+ return (PyObject *)self;
+}
+
+static struct PyMethodDef stringio_methods[] = {
+ {"getvalue", (PyCFunction)stringio_getvalue, METH_VARARGS, NULL},
+ {"read", (PyCFunction)stringio_read, METH_VARARGS, NULL},
+ {"tell", (PyCFunction)stringio_tell, METH_NOARGS, NULL},
+ {"truncate", (PyCFunction)stringio_truncate, METH_VARARGS, NULL},
+ {"seek", (PyCFunction)stringio_seek, METH_VARARGS, NULL},
+ {"write", (PyCFunction)stringio_write, METH_O, NULL},
+ {NULL, NULL} /* sentinel */
+};
+
+static PyTypeObject StringIO_Type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "_stringio._StringIO", /*tp_name*/
+ sizeof(StringIOObject), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ (destructor)stringio_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*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+ 0, /*tp_doc*/
+ 0, /*tp_traverse*/
+ 0, /*tp_clear*/
+ 0, /*tp_richcompare*/
+ 0, /*tp_weaklistoffset*/
+ 0, /*tp_iter*/
+ 0, /*tp_iternext*/
+ stringio_methods, /*tp_methods*/
+ 0, /*tp_members*/
+ 0, /*tp_getset*/
+ 0, /*tp_base*/
+ 0, /*tp_dict*/
+ 0, /*tp_descr_get*/
+ 0, /*tp_descr_set*/
+ 0, /*tp_dictoffset*/
+ 0, /*tp_init*/
+ 0, /*tp_alloc*/
+ stringio_new, /*tp_new*/
+};
+
+static struct PyModuleDef _stringiomodule = {
+ PyModuleDef_HEAD_INIT,
+ "_stringio",
+ NULL,
+ -1,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+PyMODINIT_FUNC
+PyInit__stringio(void)
+{
+ PyObject *m;
+
+ if (PyType_Ready(&StringIO_Type) < 0)
+ return NULL;
+ m = PyModule_Create(&_stringiomodule);
+ if (m == NULL)
+ return NULL;
+ Py_INCREF(&StringIO_Type);
+ if (PyModule_AddObject(m, "_StringIO", (PyObject *)&StringIO_Type) < 0)
+ return NULL;
+ return m;
+}
diff --git a/setup.py b/setup.py
index 54868fe..8ca5808 100644
--- a/setup.py
+++ b/setup.py
@@ -422,6 +422,7 @@ class PyBuildExt(build_ext):
exts.append( Extension("_functools", ["_functoolsmodule.c"]) )
# Memory-based IO accelerator modules
exts.append( Extension("_bytesio", ["_bytesio.c"]) )
+ exts.append( Extension("_stringio", ["_stringio.c"]) )
# C-optimized pickle replacement
exts.append( Extension("_pickle", ["_pickle.c"]) )
# atexit