TextUtils: Text Utilities#

class my.utils.TextUtils.TextUtils#

Methods that clean, search, split, and otherwise interact with strings.

Parts of this class overlap in scope with RegexStore (namely regex_dict()), but ultimately present a much more lightweight interface for simple (or dependency-sensitive…) regex tasks. As with the store, all regex compiled through these methods supports the regex module’s extended regex syntax (see the my.regex docs).

I Manipulation & Regex#

static TextUtils.replace(string: str, *args: tuple[str | Pattern, str | Callable[[Match[str]], str]]) → str#

Apply multiple regex replacements sequentially to a string.

Parameters:
  • string – Input string to transform.

  • *args – Tuples of (pattern, replacement) for sequential application.

Returns:

String with all replacements applied in order.

Examples

Chain several substitutions in one pass:

>>> from my import ut
>>> ut.replace('a1b2', (r'\d', '#'), ('b', 'B'))
'a#B#'
static TextUtils.split_into(text: str, pattern: str | Pattern, n: int = 2, rhs: bool = True) → list[str]#

Split string using regex into exactly n parts, padding as needed.

Parameters:
  • text – String to split.

  • pattern – Regex pattern to split by.

  • n – Exact number of parts to return (must be > 1).

  • rhs – If True, pad on right; if False, pad on left (default: True).

Returns:

List of exactly n strings, padded with empty strings if needed.

Raises:

AssertionError – If n <= 1 or split operation fails.

Examples

Split into exactly n parts, padding when short:

>>> from my import ut
>>> ut.split_into('a:b:c', ':', 2)
['a', 'b:c']
>>> ut.split_into('a', ':', 3)
['a', '', '']
static TextUtils.regex_dict(expressions: ~collections.abc.Mapping[K, V | ~_regex.Pattern] | None = None, compile_function: ~collections.abc.Callable[[V], ~_regex.Pattern] = <function compile>, sep: str = '', **kwargs: V | ~_regex.Pattern) → dict[K, Pattern]#

Compile the expression strings in the given dictionary, mapping names to Patterns.

A string value may reference an earlier entry in the same call with {{.name}}; the reference is substituted with that entry’s (already-compiled) pattern text before compiling. A non-string, non-Pattern value is treated as an iterable of strings and joined with sep before compiling (and before reference substitution).

Parameters:
  • expressions – A mapping of string names to regular expressions (compiled or otherwise).

  • compile_function – Function to compile patterns (default: re.compile).

  • sep – Separator used to join a list-of-strings value before compiling.

  • **kwargs – Additional named patterns to include.

Returns:

The expressions mapping with all values now compiled.

Raises:
  • AssertionError – If a {{.name}} reference names a group not yet defined.

  • ValueError – If a pattern fails to compile; the message reports the offending key and, where possible, the exact column.

Examples

Compile a named batch of patterns:

>>> from my import ut
>>> rgxs = ut.regex_dict(word=r'\w+')
>>> rgxs['word'].findall('a b')
['a', 'b']

Reference an earlier entry, and join a list of alternatives with sep:

>>> rgxs = ut.regex_dict(digit=r'\d', pair=r'{{.digit}}{{.digit}}')
>>> bool(rgxs['pair'].fullmatch('42'))
True
>>> rgxs = ut.regex_dict(sep='|', either=['aa', 'bb'])
>>> bool(rgxs['either'].fullmatch('bb'))
True
classmethod TextUtils.safe_compile(key: str, expr: str, fn: Callable[[str], Pattern[str]]) → Pattern[str]#

Compile a regex pattern, falling back to a matches-nothing-useful pattern on failure.

Unlike regex_dict(), a compilation failure here is reported (printed) rather than raised, so a caller assembling many independent patterns can keep going.

Parameters:
  • key – Name of the regex pattern (used only for the printed diagnostic).

  • expr – Regular expression to compile.

  • fn – Function to compile the pattern (e.g. re.compile).

Returns:

The compiled pattern, or a pattern compiled from the literal text 'ERROR' if expr failed to compile.

Examples

A valid pattern compiles and matches normally:

>>> import regex as re
>>> from my import ut
>>> bool(ut.safe_compile('ok', r'\w+', re.compile).fullmatch('abc'))
True
static TextUtils.regex_array(*args: tuple[str | Pattern, str]) → list[tuple[Pattern, str]]#
static TextUtils.regex_array(*args: tuple[str | Pattern, V], compile_function: Callable[[...], Pattern] = re.compile) → list[tuple[Pattern, V]]

Compile the expressions in a list of two-tuples, mapping Patterns to strings.

Parameters:
  • *args – (pattern, replacement) tuples to compile.

  • compile_function – Function to compile patterns (default: re.compile).

Returns:

List of (compiled_pattern, replacement) tuples.

Examples

Compile substitution pairs ready for replace():

>>> from my import ut
>>> pairs = ut.regex_array((r'\d+', 'N'), (r'\s+', '_'))
>>> [(p.pattern, repl) for p, repl in pairs]
[('\\d+', 'N'), ('\\s+', '_')]
static TextUtils.multi_rgx(*expressions: str | list[str], branching: bool = False, sep: str = ' ?', pre: str = '', suf: str = '') → str#

Combine expression clauses into a single alternating group that matches any of them.

Parameters:
  • *expressions – Regex patterns to be combined.

  • branching – If True, use a branch-reset group (resets group names b/w branches).

  • sep – Separator for joining list patterns (default: ` ?`).

  • pre – Prefix to add before combined pattern (default: empty).

  • suf – Suffix to add after combined pattern (default: empty).

Returns:

Combined regex pattern in group format (?|...) or (?:...).

Examples

Combine alternatives into one non-capturing group:

>>> from my import ut
>>> ut.multi_rgx('cat', 'dog')
'(?:cat|dog)'
>>> ut.multi_rgx(['a', 'b'], 'c')
'(?:a ?b|c)'

II Formatting#

static TextUtils.wrap(line: str, prefix: str = '', char: str = '-', width: int = 2) → str#

Wrap a line of text with decorative borders.

Parameters:
  • line – Text to wrap.

  • prefix – Prefix for each line (default: empty).

  • char – Character for border (default: ‘-‘).

  • width – Padding width on each side (default: 2).

Returns:

Multi-line string with text wrapped in decorative borders.

Examples

Frame a headline:

>>> from my import ut
>>> print(ut.wrap('Stage 1').strip())
-------------
-- Stage 1 --
-------------
static TextUtils.indent(text: str, n: int = 4) → str#

Indent all lines in text by n spaces.

Parameters:
  • text – Text to indent.

  • n – Number of spaces to indent (default: 4).

Returns:

Indented text, or original if n is 0.

Examples

Indent every line:

>>> from my import ut
>>> print(ut.indent('a\nb', 2))
  a
  b
static TextUtils.unindent(text: str, n: int = 4) → str#

Remove up to n*4 leading spaces from each line.

Parameters:
  • text – Text to unindent.

  • n – Number of indent levels to remove (default: 4, removes up to 16 spaces).

Returns:

Unindented text.

Examples

Peel off up to n levels of 4-space indentation:

>>> from my import ut
>>> print(ut.unindent('    a\n        b', 1))
a
    b
static TextUtils.strip_quotes(string: str) → str#

Remove surrounding matched quotes and emphasis markers ('"*_) from the string.

Parameters:

string – The text content to strip.

Returns:

String with surrounding quotes/emphasis removed.

Examples

Strip matched quoting and emphasis pairs:

>>> from my import ut
>>> ut.strip_quotes('"hello"')
'hello'
>>> ut.strip_quotes("*'nested'*")
'nested'
classmethod TextUtils.clean_string(string: str, case: 'lower' | 'none' | 'upper' = 'lower') → str#

Fully clean and normalize a string for use as identifier or slug.

Applies unidecode, strips whitespace, cleans non-words, and applies case conversion.

Parameters:
  • string – String to clean.

  • case – Case conversion - ‘lower’, ‘upper’, or ‘none’ (default: ‘lower’).

Returns:

Cleaned and normalized string suitable for identifiers.

Raises:

ImportError – If the optional unidecode dependency is not installed.

Examples

Slugify arbitrary text:

>>> from my import ut
>>> ut.clean_string('Héllo, World!')
'hello_world'
>>> ut.clean_string("Zoë's Café", case='none')
'Zoes-Cafe'
class my.utils.TextUtils.TextUtils.TextCase(*values)#

Enumeration of common text case styles, each able to apply() itself to a string.

apply(text: str, _from: TextCase | None = None) → str#

Apply this case style to text, first splitting it into words.

Parameters:
  • text – Text to reformat.

  • _from – The text’s current case, when it isn’t plain whitespace-separated – required to split KEBAB/SNAKE/PASCAL/CAMEL input correctly.

Returns:

text reformatted to this case, or '' if text is blank.

classmethod TextUtils.recase(string: str, to: TextCase | str = TextCase.SNAKE, _from: TextCase | str | None = None, clean: bool = True) → str#

Convert a string between case styles (snake_case, camelCase, kebab-case, …).

Parameters:
  • string – String to convert.

  • to – The target case (default: SNAKE); a case name string is also accepted.

  • _from – The string’s current case – required for KEBAB/SNAKE/PASCAL/CAMEL input, since those don’t split on whitespace.

  • clean – Whether to also apply _clean_nonwords() to the result (default: True).

Returns:

string reformatted to the to case.

Examples

Convert between styles, with and without an explicit source case:

>>> from my import ut
>>> ut.recase('hello world', to='kebab')
'hello-world'
>>> ut.recase('helloWorld', to=ut.TextCase.SNAKE, _from=ut.TextCase.CAMEL)
'hello_world'
classmethod TextUtils.from_pascal(string: str) → str#

Convert a PascalCase string to snake_case.

Examples

>>> from my import ut
>>> ut.from_pascal('MyClassName')
'my_class_name'
classmethod TextUtils.to_pascal(string: str) → str#

Convert a snake_case string to PascalCase.

Examples

>>> from my import ut
>>> ut.to_pascal('my_class_name')
'MyClassName'
classmethod TextUtils.to_words(text: str) → list[str]#

Extract all words from text using regex word boundary matching.

Parameters:

text – Text to extract words from.

Returns:

List of word strings.

Examples

Pull out just the words:

>>> from my import ut
>>> ut.to_words('Hello, world - again!')
['Hello', 'world', 'again']
static TextUtils.line_num(article: str, pos: int | str) → int#

Calculate line number from character position or substring.

Parameters:
  • article – Text to search within.

  • pos – Character position (int) or substring to find (str).

Returns:

Line number (1-indexed).

Examples

Locate by offset or by substring:

>>> from my import ut
>>> ut.line_num('ab\ncd\nef', 4)
2
>>> ut.line_num('ab\ncd\nef', 'ef')
3
static TextUtils.parse_domain(url: str, default: str = '') → str#

Extract domain name from URL, removing ‘www.’ prefix.

Parameters:
  • url – URL string to parse.

  • default – Default value if parsing fails (default: ‘’).

Returns:

Domain name without ‘www.’ prefix, or default if parsing fails.

Examples

Extract the bare domain:

>>> from my import ut
>>> ut.parse_domain('https://www.example.com/page?q=1')
'example.com'
>>> ut.parse_domain('not a url', default='n/a')
'n/a'
static TextUtils.wrap_paragraphs(text: str, width: int = 100) → str#

Wrap text to specified width, breaking on whitespace.

Parameters:
  • text – Text to wrap.

  • width – Maximum line width (default: 100).

Returns:

Wrapped text.

Examples

Hard-wrap prose at a fixed width:

>>> from my import ut
>>> ut.wrap_paragraphs('one two three four', width=9)
'one two\nthree\nfour'
classmethod TextUtils.unwrap_paragraphs(text: str) → str#

Unwrap and normalize paragraph text, joining wrapped lines intelligently.

Handles hyphenated line breaks, prose detection, and comment prefixes.

Parameters:

text – Text with potentially wrapped paragraphs.

Returns:

Unwrapped text with proper spacing and line breaks.

Examples

Rejoin hard-wrapped prose while preserving non-prose lines:

>>> from my import ut
>>> ut.unwrap_paragraphs('This line was\nhard-wrapped by an\neditor.\n\n- a bullet')
'This line was hard-wrapped by an editor.\n\n- a bullet'