Index: Objects/unicodeobject.c =================================================================== --- Objects/unicodeobject.c (revision 67529) +++ Objects/unicodeobject.c (working copy) @@ -7948,7 +7948,111 @@ return Py_BuildValue("(u#)", v->str, v->length); } +/* Unicode ops must return a unicode. */ +/* If the object is subclass of unicode, create a copy */ +Py_LOCAL(PyUnicodeObject *) +return_self(PyUnicodeObject *self) +{ + if (PyUnicode_CheckExact(self)) { + Py_INCREF(self); + return self; + } + return (PyUnicodeObject *)PyUnicode_FromUnicode( + PyUnicode_AS_UNICODE(self), + PyUnicode_GET_SIZE(self)); +} +PyDoc_STRVAR(lstrips__doc__, +"S.lstrips(sub) -> string\n\ +\n\ +Return a copy of the string S with leading substring removed."); + +static PyObject * +unicode_lstrips(PyUnicodeObject *self, PyObject *sub) +{ + Py_ssize_t result; + + /* If sub is None, then return self */ + if (sub == Py_None) { + return (PyObject *)return_self(self); + } + + result = PyUnicode_Tailmatch((PyObject *)self, sub, 0, PY_SSIZE_T_MAX, 0); + if (result == -1) { + /* Error */ + return NULL; + } + else if (result) { + /* Match: return a copy of self with sub removed from the beginning */ + Py_UNICODE *raw_self; + Py_ssize_t raw_self_len, raw_sub_len; + + raw_self = PyUnicode_AS_UNICODE(self); + raw_self_len = PyUnicode_GET_SIZE(self); + raw_sub_len = PyUnicode_GET_SIZE(sub); + + return PyUnicode_FromUnicode(raw_self + raw_sub_len, + raw_self_len - raw_sub_len); + } + else { + /* No match: return self */ + return (PyObject *)return_self(self); + } +} + +PyDoc_STRVAR(rstrips__doc__, +"S.rstrips(sub) -> string\n\ +\n\ +Return a copy of the string S with trailing substring removed."); + +static PyObject * +unicode_rstrips(PyUnicodeObject *self, PyObject *sub) +{ + Py_ssize_t result; + + /* If sub is None, then return self */ + if (sub == Py_None) { + return (PyObject *)return_self(self); + } + + result = PyUnicode_Tailmatch((PyObject *)self, sub, 0, PY_SSIZE_T_MAX, 1); + if (result == -1) { + /* Error */ + return NULL; + } + else if (result) { + /* Match: return a copy of self with sub removed from the end */ + Py_UNICODE *raw_self; + Py_ssize_t raw_self_len, raw_sub_len; + + raw_self = PyUnicode_AS_UNICODE(self); + raw_self_len = PyUnicode_GET_SIZE(self); + raw_sub_len = PyUnicode_GET_SIZE(sub); + + return PyUnicode_FromUnicode(raw_self, raw_self_len - raw_sub_len); + } + else { + /* No match: return self */ + return (PyObject *)return_self(self); + } +} + +PyDoc_STRVAR(strips__doc__, +"S.strips(sub) -> string\n\ +\n\ +Return a copy of the string S with leading and trailing\ +substring removed."); + +static PyObject * +unicode_strips(PyUnicodeObject *self, PyObject *sub) +{ + PyObject *lstripped = unicode_lstrips(self, sub); + if (!lstripped) { + return NULL; + } + return unicode_rstrips((PyUnicodeObject *)lstripped, sub); +} + static PyMethodDef unicode_methods[] = { /* Order is according to common usage: often used methods should @@ -7970,15 +8074,18 @@ {"ljust", (PyCFunction) unicode_ljust, METH_VARARGS, ljust__doc__}, {"lower", (PyCFunction) unicode_lower, METH_NOARGS, lower__doc__}, {"lstrip", (PyCFunction) unicode_lstrip, METH_VARARGS, lstrip__doc__}, + {"lstrips", (PyCFunction) unicode_lstrips, METH_O, lstrips__doc__}, {"decode", (PyCFunction) unicode_decode, METH_VARARGS, decode__doc__}, /* {"maketrans", (PyCFunction) unicode_maketrans, METH_VARARGS, maketrans__doc__}, */ {"rfind", (PyCFunction) unicode_rfind, METH_VARARGS, rfind__doc__}, {"rindex", (PyCFunction) unicode_rindex, METH_VARARGS, rindex__doc__}, {"rjust", (PyCFunction) unicode_rjust, METH_VARARGS, rjust__doc__}, {"rstrip", (PyCFunction) unicode_rstrip, METH_VARARGS, rstrip__doc__}, + {"rstrips", (PyCFunction) unicode_rstrips, METH_O, rstrips__doc__}, {"rpartition", (PyCFunction) unicode_rpartition, METH_O, rpartition__doc__}, {"splitlines", (PyCFunction) unicode_splitlines, METH_VARARGS, splitlines__doc__}, {"strip", (PyCFunction) unicode_strip, METH_VARARGS, strip__doc__}, + {"strips", (PyCFunction) unicode_strips, METH_O, strips__doc__}, {"swapcase", (PyCFunction) unicode_swapcase, METH_NOARGS, swapcase__doc__}, {"translate", (PyCFunction) unicode_translate, METH_O, translate__doc__}, {"upper", (PyCFunction) unicode_upper, METH_NOARGS, upper__doc__}, Index: Objects/stringobject.c =================================================================== --- Objects/stringobject.c (revision 67529) +++ Objects/stringobject.c (working copy) @@ -3997,7 +3997,97 @@ \n\ "); +PyDoc_STRVAR(lstrips__doc__, +"S.lstrips(sub) -> string\n\ +\n\ +Return a copy of the string S with leading substring removed."); +static PyObject * +string_lstrips(PyStringObject *self, PyObject *sub) +{ + int result; + + /* If sub is None, then return self */ + if (sub == Py_None) { + return (PyObject *)return_self(self); + } + + result = _string_tailmatch(self, sub, 0, PY_SSIZE_T_MAX, -1); + if (result == -1) { + /* Error */ + return NULL; + } + else if (result) { + /* Match: return a copy of self with sub removed from the beginning */ + char *raw_self; + Py_ssize_t raw_self_len, raw_sub_len; + + raw_self = PyString_AS_STRING(self); + raw_self_len = PyString_GET_SIZE(self); + raw_sub_len = PyString_GET_SIZE(sub); + + return PyString_FromStringAndSize(raw_self + raw_sub_len, + raw_self_len - raw_sub_len); + } + else { + /* No match: return self */ + return (PyObject *)return_self(self); + } +} + +PyDoc_STRVAR(rstrips__doc__, +"S.rstrips(sub) -> string\n\ +\n\ +Return a copy of the string S with trailing substring removed."); + +static PyObject * +string_rstrips(PyStringObject *self, PyObject *sub) +{ + int result; + + /* If sub is None, then return self */ + if (sub == Py_None) { + return (PyObject *)return_self(self); + } + + result = _string_tailmatch(self, sub, 0, PY_SSIZE_T_MAX, 0); + if (result == -1) { + /* Error */ + return NULL; + } + else if (result) { + /* Match: return a copy of self with sub removed from the beginning */ + char *raw_self; + Py_ssize_t raw_self_len, raw_sub_len; + + raw_self = PyString_AS_STRING(self); + raw_self_len = PyString_GET_SIZE(self); + raw_sub_len = PyString_GET_SIZE(sub); + + return PyString_FromStringAndSize(raw_self, raw_self_len - raw_sub_len); + } + else { + /* No match: return self */ + return (PyObject *)return_self(self); + } +} + +PyDoc_STRVAR(strips__doc__, +"S.strips(sub) -> string\n\ +\n\ +Return a copy of the string S with leading and trailing\ +substring removed."); + +static PyObject * +string_strips(PyStringObject *self, PyObject *sub) +{ + PyObject *lstripped = string_lstrips(self, sub); + if (!lstripped) { + return NULL; + } + return string_rstrips((PyStringObject *)lstripped, sub); +} + static PyMethodDef string_methods[] = { /* Counterparts of the obsolete stropmodule functions; except @@ -4054,6 +4144,9 @@ {"__sizeof__", (PyCFunction)string_sizeof, METH_NOARGS, sizeof__doc__}, {"__getnewargs__", (PyCFunction)string_getnewargs, METH_NOARGS}, + {"lstrips", (PyCFunction)string_lstrips, METH_O, lstrips__doc__}, + {"rstrips", (PyCFunction)string_rstrips, METH_O, rstrips__doc__}, + {"strips", (PyCFunction)string_strips, METH_O, strips__doc__}, {NULL, NULL} /* sentinel */ }; Index: Lib/UserString.py =================================================================== --- Lib/UserString.py (revision 67529) +++ Lib/UserString.py (working copy) @@ -102,6 +102,7 @@ return self.__class__(self.data.ljust(width, *args)) def lower(self): return self.__class__(self.data.lower()) def lstrip(self, chars=None): return self.__class__(self.data.lstrip(chars)) + def lstrips(self, sub): return self.__class__(self.data.lstrips(sub)) def partition(self, sep): return self.data.partition(sep) def replace(self, old, new, maxsplit=-1): @@ -115,6 +116,7 @@ def rpartition(self, sep): return self.data.rpartition(sep) def rstrip(self, chars=None): return self.__class__(self.data.rstrip(chars)) + def rstrips(self, sub): return self.__class__(self.data.rstrips(sub)) def split(self, sep=None, maxsplit=-1): return self.data.split(sep, maxsplit) def rsplit(self, sep=None, maxsplit=-1): @@ -123,6 +125,7 @@ def startswith(self, prefix, start=0, end=sys.maxint): return self.data.startswith(prefix, start, end) def strip(self, chars=None): return self.__class__(self.data.strip(chars)) + def strips(self, sub): return self.__class__(self.data.strips(sub)) def swapcase(self): return self.__class__(self.data.swapcase()) def title(self): return self.__class__(self.data.title()) def translate(self, *args): Index: Lib/string.py =================================================================== --- Lib/string.py (revision 67529) +++ Lib/string.py (working copy) @@ -516,7 +516,35 @@ """ return s.replace(old, new, maxsplit) +# Strip leading and trailing substring +def strips(s, sub): + """strips(s, sub) -> string + Return a copy of the string s with leading and trailing + substring removed. + + """ + return s.strips(sub) + +# Strip leading substring +def lstrips(s, sub): + """lstrips(s, sub) -> string + + Return a copy of the string s with leading substring removed. + + """ + return s.lstrips(sub) + +# Strip trailing substring +def rstrips(s, sub): + """rstrips(s, sub) -> string + + Return a copy of the string s with trailing substring removed. + + """ + return s.rstrips(sub) + + # Try importing optional built-in module "strop" -- if it exists, # it redefines some string operations that are 100-1000 times faster. # It also defines values for whitespace, lowercase and uppercase Index: Lib/test/string_tests.py =================================================================== --- Lib/test/string_tests.py (revision 67529) +++ Lib/test/string_tests.py (working copy) @@ -744,6 +744,26 @@ self.checkraises(TypeError, '123', 'zfill') + def test_strips(self): + self.checkequal('barfoo', 'foobarfoo', 'lstrips', 'foo') + self.checkequal('foobarfoo', 'foobarfoo', 'lstrips', 'foop') + self.checkequal('foobarfoo', 'foobarfoo', 'lstrips', '') + self.checkequal('foobarfoo', 'foobarfoo', 'lstrips', None) + + self.checkequal('foobar', 'foobarfoo', 'rstrips', 'foo') + self.checkequal('foobarfoo', 'foobarfoo', 'rstrips', 'foop') + self.checkequal('foobarfoo', 'foobarfoo', 'rstrips', '') + self.checkequal('foobarfoo', 'foobarfoo', 'rstrips', None) + + self.checkequal('bar', 'foobarfoo', 'strips', 'foo') + self.checkequal('foobarfoo', 'foobarfoo', 'strips', 'foop') + self.checkequal('foobarfoo', 'foobarfoo', 'strips', '') + self.checkequal('foobarfoo', 'foobarfoo', 'strips', None) + + self.checkraises(TypeError, 'foobarfoo', 'lstrips', 42) + self.checkraises(TypeError, 'foobarfoo', 'rstrips', 42) + self.checkraises(TypeError, 'foobarfoo', 'strips', 42) + # XXX alias for py3k forward compatibility BaseTest = CommonTest Index: Lib/test/test_bytes.py =================================================================== --- Lib/test/test_bytes.py (revision 67529) +++ Lib/test/test_bytes.py (working copy) @@ -905,6 +905,8 @@ def test_hash(self): # XXX check this out pass + def test_strips(self): + pass class ByteArrayAsStringTest(FixedStringTest):