[PATCH 4/9] Add logical_rslice() function
"Jarret \"Jax\" Renker via Docutils-develop" <[email protected]>
| Newsgroups | gmane.text.docutils.devel |
|---|---|
| Message-ID | <vmE9kGYSi9zf3Mg7wp-OEbx4fBAHMPvY6hUHrRAlrFcP9IUMT-5Df8ML65ASY-_I6jzZbqyItE-4IR4XelPdiPJZs1qzGeHhWa00cyE5FZs=@proton.me> |
Add a function which works like ``string[start:]`` except that tabs are
not counted as a single character but as `tab_width` characters wide.
---
docutils/docutils/statemachine.py | 66 ++++++++++++++++++++++++++++++
docutils/test/test_statemachine.py | 16 ++++++++
2 files changed, 82 insertions(+)
diff --git a/docutils/docutils/statemachine.py b/docutils/docutils/statemachine.py
index 890572564..167cb7744 100644
--- a/docutils/docutils/statemachine.py
+++ b/docutils/docutils/statemachine.py
@@ -1496,6 +1496,72 @@ def string2lines(astring):
return astring.splitlines()
+def logical_rslice(astring, logical_start, tab_width):
+ """
+ Removes characters from the beginning of `astring` such that the logical
+ width of the removed characters is `logical_start`. If `astring` is
+ logically shorter than `logical_start`, then an empty string is returned.
+
+ For all except the tab character, the logical width is 1. If `tab_width`
+ >= 0, then a tab character is considered to be up to `tab_width` spaces
+ wide depending on the current column in `astring` and the next tab
+ position. A tab position is every tab_width columns. For a description of
+ tab positions see str.expandtabs().
+
+ If `logical_start` is such that only parts of a single tab character should
+ be removed from `astring`. Then the tab character is replaced by spaces
+ and only the necessary amount of spaces is stripped. In other words, the
+ tab is "broken apart in the middle" and the left over parts are replaced
+ with spaces.
+
+ Note:
+
+ > logical_rslice(astring, logical_start, tab_width)
+
+ is almost equivalent to
+
+ > astring.expandtabs(tab_width)[logical_start:]
+
+ except that only tabs in the stripped prefix are expanded into spaces,
+ other tab characters are left untouched.
+
+ Examples:
+
+ >>> logical_rslice("foo", 2, 8)
+ "o"
+ >>> logical_rslice(" \tfoo", 2, 8)
+ "\tfoo"
+ >>> logical_rslice("\tfoo", 5, 8)
+ " foo"
+ >>> logical_rslice(" \tfoo", 5, 8)
+ " foo"
+
+ Parameters:
+
+ - `astring`: a string to strip from
+ - `logical_start`: a number of logical character width to strip
+ - `tab_width`: the tab width
+ """
+ logical_pos = 0
+ string_pos = 0 # physical position in string
+ space_prefix = ''
+ try:
+ while logical_pos < logical_start:
+ ch = astring[string_pos]
+ string_pos += 1
+ if ch == '\t':
+ if tab_width > 0:
+ logical_pos += tab_width - (logical_pos % tab_width)
+ else:
+ pass
+ else:
+ logical_pos += 1
+ return (' ' * (logical_pos - logical_start)) + astring[string_pos:]
+ except IndexError:
+ # `astring` was logically shorter than `logical_start`
+ return ''
+
+
def _exception_data():
"""
Return exception information:
diff --git a/docutils/test/test_statemachine.py b/docutils/test/test_statemachine.py
index 8332a5a61..19f2bd874 100755
--- a/docutils/test/test_statemachine.py
+++ b/docutils/test/test_statemachine.py
@@ -290,6 +290,22 @@ def test_string2lines(self):
self.assertEqual(statemachine.string2lines(self.s2l_string),
self.s2l_expected)
+ logical_rslice_test_data = [
+ # (input, logical_start, tab_width, expected)
+ ("foo\tfoo", 3, 8, "\tfoo"),
+ (" \tfoo", 3, 8, "\tfoo"),
+ ("\tfoo", 3, 8, " foo"),
+ (" \tfoo", 8, 8, "foo"),
+ (" \tfoo", 9, 8, "oo"),
+ (" \tfoo", 9, 10, " foo"),
+ ("\tfoo", 5, 8, " foo"),
+ ("\tfoo", 5, 1, ""),
+ ]
+
+ def test_logical_rslice(self):
+ for (i,s,t,e) in self.logical_rslice_test_data:
+ self.assertEqual(statemachine.logical_rslice(i,s,t), e)
+
if __name__ == '__main__':
unittest.main()