Skip to content

mode.utils.text

Text and string manipulation utilities.

FuzzyMatch

Bases: NamedTuple

Fuzzy match result.

Source code in mode/utils/text.py
25
26
27
28
29
class FuzzyMatch(NamedTuple):
    """Fuzzy match result."""

    ratio: float
    value: str

abbr(s, max, suffix='...', words=False)

Abbreviate word.

Source code in mode/utils/text.py
191
192
193
194
195
def abbr(s: str, max: int, suffix: str = "...", words: bool = False) -> str:
    """Abbreviate word."""
    if words:
        return _abbr_word_boundary(s, max, suffix)
    return _abbr_abrupt(s, max, suffix)

abbr_fqdn(origin, name, *, prefix='')

Abbreviate fully-qualified Python name, by removing origin.

app.origin is the package where the app is defined, so if this is examples.simple:

>>> app.origin
'examples.simple'
>>> abbr_fqdn(app.origin, 'examples.simple.Withdrawal', prefix='[...]')
'[...]Withdrawal'

>>> abbr_fqdn(app.origin, 'examples.other.Foo', prefix='[...]')
'examples.other.foo'

shorten_fqdn is similar, but will always shorten a too long name, abbr_fqdn will only remove the origin portion of the name.

Source code in mode/utils/text.py
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
def abbr_fqdn(origin: str, name: str, *, prefix: str = "") -> str:
    """Abbreviate fully-qualified Python name, by removing origin.

    `app.origin` is the package where the app is defined,
    so if this is `examples.simple`:

    ```sh
    >>> app.origin
    'examples.simple'
    >>> abbr_fqdn(app.origin, 'examples.simple.Withdrawal', prefix='[...]')
    '[...]Withdrawal'

    >>> abbr_fqdn(app.origin, 'examples.other.Foo', prefix='[...]')
    'examples.other.foo'
    ```

    `shorten_fqdn` is similar, but will always shorten a too long name,
    abbr_fqdn will only remove the origin portion of the name.
    """
    if name.startswith(origin):
        name = name[len(origin) + 1 :]
        return f"{prefix}{name}"
    return name

didyoumean(haystack, needle, *, fmt_many='Did you mean one of {alt}?', fmt_one='Did you mean {alt}?', fmt_none='', min_ratio=0.6)

Generate message with helpful list of alternatives.

Examples:

>>> raise Exception(f'Unknown mode: {mode}! {didyoumean(modes, mode)}')

>>> didyoumean(['foo', 'bar', 'baz'], 'boo')
'Did you mean foo?'

>>> didyoumean(['foo', 'moo', 'bar'], 'boo')
'Did you mean one of foo, moo?'

>>> didyoumean(['foo', 'moo', 'bar'], 'xxx')
''

Parameters:

Name Type Description Default
haystack Iterable[str]

List of all available choices.

required
needle str

What the user provided.

required
fmt_many str

String format returned when there are more than one alternative. Default is: "Did you mean one of {alt}?".

'Did you mean one of {alt}?'
fmt_one str

String format returned when there's a single fuzzy match. Default is: "Did you mean {alt}?".

'Did you mean {alt}?'
fmt_none str

String format returned when there are no fuzzy matches. Default is: "" (empty string, error message is usually printed before the alternatives so user has context).

''
min_ratio float

Minimum fuzzy ratio before word is considered a match. Default is 0.6.

0.6
Source code in mode/utils/text.py
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
def didyoumean(
    haystack: Iterable[str],
    needle: str,
    *,
    fmt_many: str = "Did you mean one of {alt}?",
    fmt_one: str = "Did you mean {alt}?",
    fmt_none: str = "",
    min_ratio: float = 0.6,
) -> str:
    """Generate message with helpful list of alternatives.

    Examples:

    ```sh
    >>> raise Exception(f'Unknown mode: {mode}! {didyoumean(modes, mode)}')

    >>> didyoumean(['foo', 'bar', 'baz'], 'boo')
    'Did you mean foo?'

    >>> didyoumean(['foo', 'moo', 'bar'], 'boo')
    'Did you mean one of foo, moo?'

    >>> didyoumean(['foo', 'moo', 'bar'], 'xxx')
    ''
    ```

    Arguments:
        haystack: List of all available choices.
        needle: What the user provided.
        fmt_many: String format returned when there are more than one
            alternative.  Default is: ``"Did you mean one of {alt}?"``.
        fmt_one: String format returned when there's a single fuzzy match.
            Default is: ``"Did you mean {alt}?"``.
        fmt_none: String format returned when there are no fuzzy matches.
            Default is: ``""`` (empty string, error message is usually printed
            before the alternatives so user has context).
        min_ratio: Minimum fuzzy ratio before word is considered a match.
            Default is 0.6.
    """
    return fuzzymatch_choices(
        list(haystack),
        needle,
        fmt_many=fmt_many,
        fmt_one=fmt_one,
        fmt_none=fmt_none,
        min_ratio=min_ratio,
    )

enumeration(items, *, start=1, sep='\n', template='{index}) {item}')

Enumerate list of strings.

Example:

>>> enumeration(['x', 'y', '...'])
"1) x\n2) y\n3) ..."
Source code in mode/utils/text.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
def enumeration(
    items: Iterable[str],
    *,
    start: int = 1,
    sep: str = "\n",
    template: str = "{index}) {item}",
) -> str:
    """Enumerate list of strings.

    Example:

    ```sh
    >>> enumeration(['x', 'y', '...'])
    "1) x\\n2) y\\n3) ..."
    ```
    """
    return sep.join(
        template.format(index=index, item=item)
        for index, item in enumerate(items, start=start)
    )

fuzzymatch_best(haystack, needle, *, min_ratio=0.6)

Fuzzy Match - Return best match only (single scalar value).

Source code in mode/utils/text.py
178
179
180
181
182
183
184
185
186
187
188
def fuzzymatch_best(
    haystack: Iterable[str], needle: str, *, min_ratio: float = 0.6
) -> Optional[str]:
    """Fuzzy Match - Return best match only (single scalar value)."""
    try:
        return sorted(
            fuzzymatch_iter(haystack, needle, min_ratio=min_ratio),
            reverse=True,
        )[0].value
    except IndexError:
        return None

fuzzymatch_choices(haystack, needle, *, fmt_many='one of {alt}', fmt_one='{alt}', fmt_none='', min_ratio=0.6)

Fuzzy match reducing to error message suggesting an alternative.

Source code in mode/utils/text.py
141
142
143
144
145
146
147
148
149
150
151
152
153
154
def fuzzymatch_choices(
    haystack: Iterable[str],
    needle: str,
    *,
    fmt_many: str = "one of {alt}",
    fmt_one: str = "{alt}",
    fmt_none: str = "",
    min_ratio: float = 0.6,
) -> str:
    """Fuzzy match reducing to error message suggesting an alternative."""
    alt = list(fuzzymatch(haystack, needle, min_ratio=min_ratio))
    if not alt:
        return fmt_none
    return (fmt_many if len(alt) > 1 else fmt_one).format(alt=", ".join(alt))

fuzzymatch_iter(haystack, needle, *, min_ratio=0.6)

Fuzzy Match: Including actual ratio.

Yields:

Name Type Description
FuzzyMatch FuzzyMatch

tuples of (ratio, value).

Source code in mode/utils/text.py
164
165
166
167
168
169
170
171
172
173
174
175
def fuzzymatch_iter(
    haystack: Iterable[str], needle: str, *, min_ratio: float = 0.6
) -> Iterator[FuzzyMatch]:
    """Fuzzy Match: Including actual ratio.

    Yields:
        FuzzyMatch: tuples of ``(ratio, value)``.
    """
    for key in iter(haystack):
        ratio = SequenceMatcher(None, needle, key).ratio()
        if ratio >= min_ratio:
            yield FuzzyMatch(ratio, key)

isatty(fh)

Return True if fh has a controlling terminal.

Notes

Use with e.g. sys.stdin.

Source code in mode/utils/text.py
46
47
48
49
50
51
52
53
54
55
def isatty(fh: IO) -> bool:
    """Return True if fh has a controlling terminal.

    Notes:
        Use with e.g. `sys.stdin`.
    """
    try:
        return fh.isatty()
    except AttributeError:
        return False

maybecat(s, suffix='', *, prefix='')

Concatenate string only if existing string s' is defined.

Other Parameters:

Name Type Description
s Union[AnyStr, None]

Main string

suffix str

add suffix if string s' is defined.

prefix str

add prefix is string s' is defined.

Source code in mode/utils/text.py
255
256
257
258
259
260
261
262
263
264
265
266
267
def maybecat(
    s: Union[AnyStr, None], suffix: str = "", *, prefix: str = ""
) -> Optional[str]:
    """Concatenate string only if existing string s' is defined.

    Keyword Arguments:
        s: Main string
        suffix: add suffix if string s' is defined.
        prefix: add prefix is string s' is defined.
    """
    if s is not None:
        return prefix + want_str(s) + suffix
    return s

pluralize(n, text, suffix='s')

Pluralize term when n is greater than one.

Source code in mode/utils/text.py
248
249
250
251
252
def pluralize(n: int, text: str, suffix: str = "s") -> str:
    """Pluralize term when n is greater than one."""
    if n != 1:
        return text + suffix
    return text

shorten_fqdn(s, max=32)

Shorten fully-qualified Python name (like "os.path.isdir").

Source code in mode/utils/text.py
238
239
240
241
242
243
244
245
def shorten_fqdn(s: str, max: int = 32) -> str:
    """Shorten fully-qualified Python name (like "os.path.isdir")."""
    if len(s) > max:
        module, sep, cls = s.rpartition(".")
        if sep:
            module = abbr(module, max - len(cls) - 3, "", words=True)
            return module + "[.]" + cls
    return s

title(s)

Capitalize sentence.

"foo bar" -> "Foo Bar"

"foo-bar" -> "Foo Bar"

Source code in mode/utils/text.py
58
59
60
61
62
63
64
65
66
67
def title(s: str) -> str:
    """Capitalize sentence.

    ``"foo bar" -> "Foo Bar"``

    ``"foo-bar" -> "Foo Bar"``
    """
    return " ".join(
        p.capitalize() for p in s.replace("-", " ").replace("_", " ").split()
    )

want_bytes(s)

Convert string to bytes.

Source code in mode/utils/text.py
32
33
34
35
36
def want_bytes(s: AnyStr) -> bytes:
    """Convert string to bytes."""
    if isinstance(s, str):
        return s.encode()
    return s

want_str(s)

Convert bytes to string.

Source code in mode/utils/text.py
39
40
41
42
43
def want_str(s: AnyStr) -> str:
    """Convert bytes to string."""
    if isinstance(s, bytes):
        return s.decode()
    return s