pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/python/cpython/pull/138620/files

"https://github.githubassets.com/assets/primer-primitives-0f570a01cbe448fb.css" /> gh-103997: Fix and test `_PyUnicode_Dedent` by StanFromIreland · Pull Request #138620 · python/cpython · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Include/internal/pycore_unicodeobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -327,9 +327,11 @@ extern PyObject* _PyUnicode_XStrip(

/* Dedent a string.
Behaviour is expected to be an exact match of `textwrap.dedent`.
Return a new reference on success, NULL with exception set on error.
Return a new reference on success, NULL with an exception set on error.

Export for '_testinternalcapi' shared extension.
*/
extern PyObject* _PyUnicode_Dedent(PyObject *unicode);
PyAPI_FUNC(PyObject*) _PyUnicode_Dedent(PyObject *unicode);
Comment thread
StanFromIreland marked this conversation as resolved.

/* --- Misc functions ----------------------------------------------------- */

Expand Down
88 changes: 88 additions & 0 deletions Lib/test/test_capi/test_unicode.py
Original file line number Diff line number Diff line change
Expand Up @@ -1074,6 +1074,94 @@ def test_transform_decimal_and_space(self):
self.assertRaises(SystemError, transform_decimal, [])
# CRASHES transform_decimal(NULL)

@support.cpython_only
@unittest.skipIf(_testinternalcapi is None,'need _testinternalcapi module')
def test_dedent(self):
from _testinternalcapi import _PyUnicode_Dedent as dedent
self.assertEqual('hello\nworld', dedent(' hello\n world'))
self.assertEqual('hello\nmy\n friend', dedent(' hello\n my\n friend'))

# Only spaces.
text = " "
expect = ""
self.assertEqual(expect, dedent(text))

# Only tabs.
text = "\t\t\t\t"
expect = ""
self.assertEqual(expect, dedent(text))

# A mixture.
text = " \t \t\t \t "
expect = ""
self.assertEqual(expect, dedent(text))

# ASCII whitespace.
text = "\f\n\r\t\v "
expect = "\n"
self.assertEqual(expect, dedent(text))

# One newline.
text = "\n"
expect = "\n"
self.assertEqual(expect, dedent(text))

# Windows-style newlines.
text = "\r\n" * 5
expect = "\n" * 5
self.assertEqual(expect, dedent(text))

# Whitespace mixture.
text = " \n\t\n \n\t\t\n\n\n "
expect = "\n\n\n\n\n\n"
self.assertEqual(expect, dedent(text))

# Lines consisting only of whitespace are always normalised
text = "a\n \n\t\n"
expect = "a\n\n\n"
self.assertEqual(expect, dedent(text))

# Whitespace characters on non-empty lines are retained
text = "a\r\n\r\n\r\n"
expect = "a\r\n\n\n"
self.assertEqual(expect, dedent(text))

# Uneven indentation with declining indent level.
text = " Foo\n Bar\n" # 5 spaces, then 4
expect = " Foo\nBar\n"
self.assertEqual(expect, dedent(text))

# Declining indent level with blank line.
text = " Foo\n\n Bar\n" # 5 spaces, blank, then 4
expect = " Foo\n\nBar\n"
self.assertEqual(expect, dedent(text))

# Declining indent level with whitespace only line.
text = " Foo\n \n Bar\n" # 5 spaces, then 4, then 4
expect = " Foo\n\nBar\n"
self.assertEqual(expect, dedent(text))

text = " hello\tthere\n how are\tyou?"
expect = "hello\tthere\nhow are\tyou?"
self.assertEqual(expect, dedent(text))

# dedent() only removes whitespace that can be uniformly removed!
text = "\thello there\n\thow are you?"
expect = "hello there\nhow are you?"
self.assertEqual(expect, dedent(text))

text = '''\
def foo():
while 1:
return foo
'''
expect = '''\
def foo():
while 1:
return foo
'''
self.assertEqual(expect, dedent(text))

Comment thread
StanFromIreland marked this conversation as resolved.
@support.cpython_only
@unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_concat(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:option:`-c` now dedents like :func:`textwrap.dedent`
16 changes: 15 additions & 1 deletion Modules/_testinternalcapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
#include "pycore_pylifecycle.h" // _PyInterpreterConfig_InitFromDict()
#include "pycore_pystate.h" // _PyThreadState_GET()
#include "pycore_runtime_structs.h" // _PY_NSMALLPOSINTS
#include "pycore_unicodeobject.h" // _PyUnicode_TransformDecimalAndSpaceToASCII()
#include "pycore_unicodeobject.h" // _PyUnicode_TransformDecimalAndSpaceToASCII(), _PyUnicode_Dedent()


#include "clinic/_testinternalcapi.c.h"

Expand Down Expand Up @@ -1764,6 +1765,18 @@ unicode_transformdecimalandspacetoascii(PyObject *self, PyObject *arg)
return _PyUnicode_TransformDecimalAndSpaceToASCII(arg);
}


/* Test _PyUnicode_Dedent() */
static PyObject *
Comment thread
StanFromIreland marked this conversation as resolved.
unicode_dedent(PyObject *self, PyObject *arg)
{
if (arg == Py_None) {
arg = NULL;
}
return _PyUnicode_Dedent(arg);
}


static PyObject *
test_pyobject_is_freed(const char *test_name, PyObject *op)
{
Expand Down Expand Up @@ -2896,6 +2909,7 @@ static PyMethodDef module_functions[] = {
{"_PyTraceMalloc_GetTraceback", tracemalloc_get_traceback, METH_VARARGS},
{"test_tstate_capi", test_tstate_capi, METH_NOARGS, NULL},
{"_PyUnicode_TransformDecimalAndSpaceToASCII", unicode_transformdecimalandspacetoascii, METH_O},
{"_PyUnicode_Dedent", unicode_dedent, METH_O},
{"check_pyobject_forbidden_bytes_is_freed",
check_pyobject_forbidden_bytes_is_freed, METH_NOARGS},
{"check_pyobject_freed_is_freed", check_pyobject_freed_is_freed, METH_NOARGS},
Expand Down
18 changes: 8 additions & 10 deletions Objects/unicodeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -13501,7 +13501,7 @@ unicode_getnewargs(PyObject *v, PyObject *Py_UNUSED(ignored))
}

/*
This function searchs the longest common leading whitespace
This function searches the longest common leading whitespace
of all lines in the [src, end).
It returns the length of the common leading whitespace and sets `output` to
point to the beginning of the common leading whitespace if length > 0.
Expand All @@ -13523,7 +13523,7 @@ search_longest_common_leading_whitespace(

// scan the whole line
while (iter < end && *iter != '\n') {
if (!leading_whitespace_end && *iter != ' ' && *iter != '\t') {
if (!leading_whitespace_end && !Py_ISSPACE(Py_CHARMASK(*iter))) {
/* `iter` points to the first non-whitespace character
in this line */
if (iter == line_start) {
Expand Down Expand Up @@ -13582,7 +13582,7 @@ search_longest_common_leading_whitespace(

/* Dedent a string.
Behaviour is expected to be an exact match of `textwrap.dedent`.
Return a new reference on success, NULL with exception set on error.
Return a new reference on success, NULL with an exception set on error.
*/
PyObject *
_PyUnicode_Dedent(PyObject *unicode)
Expand All @@ -13605,10 +13605,6 @@ _PyUnicode_Dedent(PyObject *unicode)
Py_ssize_t whitespace_len = search_longest_common_leading_whitespace(
src, end, &whitespace_start);

if (whitespace_len == 0) {
return Py_NewRef(unicode);
}

// now we should trigger a dedent
char *dest = PyMem_Malloc(src_len);
if (!dest) {
Expand All @@ -13623,7 +13619,7 @@ _PyUnicode_Dedent(PyObject *unicode)

// iterate over a line to find the end of a line
while (iter < end && *iter != '\n') {
if (in_leading_space && *iter != ' ' && *iter != '\t') {
if (in_leading_space && !Py_ISSPACE(Py_CHARMASK(*iter))) {
in_leading_space = false;
}
++iter;
Expand All @@ -13633,8 +13629,10 @@ _PyUnicode_Dedent(PyObject *unicode)
bool append_newline = iter < end;

// if this line has all white space, write '\n' and continue
if (in_leading_space && append_newline) {
*dest_iter++ = '\n';
if (in_leading_space) {
if (append_newline) {
*dest_iter++ = '\n';
}
continue;
}

Expand Down
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy