Passed
Push — main ( c9ac86...a4501a )
by Douglas
02:00
created

StringTools.extract_group()   B

Complexity

Conditions 8

Size

Total Lines 34
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 20
nop 6
dl 0
loc 34
rs 7.3333
c 0
b 0
f 0
1
import re
0 ignored issues
show
introduced by
Missing module docstring
Loading history...
2
import warnings
3
from copy import copy
4
from typing import Any, Callable, Iterable, Mapping, Optional, Sequence, Tuple, TypeVar, Union
5
6
import numpy as np
0 ignored issues
show
introduced by
Unable to import 'numpy'
Loading history...
7
import orjson
0 ignored issues
show
introduced by
Unable to import 'orjson'
Loading history...
8
import regex
0 ignored issues
show
introduced by
Unable to import 'regex'
Loading history...
9
10
from pocketutils.core.chars import *
0 ignored issues
show
Coding Style introduced by
The usage of wildcard imports like pocketutils.core.chars should generally be avoided.
Loading history...
11
from pocketutils.core.exceptions import OutOfRangeError
12
from pocketutils.tools.base_tools import BaseTools
13
14
T = TypeVar("T")
0 ignored issues
show
Coding Style Naming introduced by
Class name "T" doesn't conform to PascalCase naming style ('[^\\W\\da-z][^\\W_]+$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
15
V = TypeVar("V")
0 ignored issues
show
Coding Style Naming introduced by
Class name "V" doesn't conform to PascalCase naming style ('[^\\W\\da-z][^\\W_]+$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
16
_control_chars = regex.compile(r"\p{C}", flags=regex.V1)
17
18
19
class StringTools(BaseTools):
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
best-practice introduced by
Too many public methods (34/20)
Loading history...
20
    @classmethod
21
    def pretty_dict(cls, dct: Mapping[Any, Any]) -> str:
22
        """
23
        Returns a pretty-printed dict, complete with indentation. Will fail on non-JSON-serializable datatypes.
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (111/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
24
        """
25
        # return Pretty.condensed(dct)
26
        return orjson.dumps(dct, option=orjson.OPT_INDENT_2).decode(encoding="utf8")
27
28
    @classmethod
29
    def extract_group(
30
        cls,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
31
        pattern: Union[str, re.Pattern, regex.Pattern],
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
32
        value: Optional[str],
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
33
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
34
        group: int = 0,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
35
        ignore_null: bool = False,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
36
    ) -> Optional[str]:
37
        """
38
        Extracts a capture group from a regex full-match.
39
        Returns None if there was no match.
40
        **Always** uses https://pypi.org/project/regex with ``flags=regex.V1``.
41
42
        Args:
43
            pattern: Regex pattern
44
            value: The target string
45
            group: The group number
46
            ignore_null: Return None if ``value`` is None instead of raising a ValueError
47
48
        Returns The capture group, or None
49
        """
50
        if isinstance(pattern, re.Pattern):
51
            pattern = regex.compile(pattern.pattern, flags=regex.V1)
52
        elif isinstance(pattern, str):
53
            pattern = regex.compile(pattern, flags=regex.V1)
54
        elif isinstance(pattern, regex.Pattern) and not pattern.flags & regex.V1:
55
            pattern = regex.compile(pattern.pattern, flags=regex.V1)
56
        if value is None and ignore_null:
57
            return None
58
        match = pattern.fullmatch(value)
59
        if match is None:
60
            return None
61
        return match.group(group)
62
63
    @classmethod
64
    def join_to_str(cls, *items: Any, last: str, sep: str = ", ") -> str:
65
        """
66
        Joins items to something like "cat, dog, and pigeon" or "cat, dog, or pigeon".
67
68
        Args:
69
            *items: Items to join; ``str(item) for item in items`` will be used
70
            last: Probably "and", "or", "and/or", or ""
71
                    Spaces are added/removed as needed if ``suffix`` is alphanumeric
72
                    or "and/or", after stripping whitespace off the ends.
73
            sep: Used to separate all words; include spaces as desired
74
75
        Examples:
76
            - ``join_to_str(["cat", "dog", "elephant"], last="and")  # cat, dog, and elephant``
77
            - ``join_to_str(["cat", "dog"], last="and")  # cat and dog``
78
            - ``join_to_str(["cat", "dog", "elephant"], last="", sep="/")  # cat/dog/elephant``
79
        """
80
        if last.strip().isalpha() or last.strip() == "and/or":
81
            last = last.strip() + " "
82
        items = [str(s).strip("'" + '"' + " ") for s in items]
83
        if len(items) > 2:
0 ignored issues
show
unused-code introduced by
Unnecessary "else" after "return"
Loading history...
84
            return sep.join(items[:-1]) + sep + last + items[-1]
85
        else:
86
            return (" " + last + " ").join(items)
87
88
    @classmethod
89
    def strip_control_chars(cls, s: str) -> str:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
90
        """
91
        Strips all characters under the Unicode 'Cc' category.
92
        """
93
        return _control_chars.sub("", s)
94
95
    @classmethod
96
    def roman_to_arabic(
97
        cls, roman: str, min_val: Optional[int] = None, max_val: Optional[int] = None
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
98
    ) -> int:
99
        """
100
        Converts roman numerals to an integer.
101
102
        Args:
103
            roman: A string like "MCIV"
104
            min_val: Raise a ValueError if the parsed value is less than this
105
            max_val: Raise a ValueError if the parsed value is more than this
106
107
        Returns:
108
            The arabic numeral as a Python int
109
        """
110
        # this order is IMPORTANT!
111
        mp = dict(
0 ignored issues
show
Coding Style Naming introduced by
Variable name "mp" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
112
            IV=4, IX=9, XL=40, XC=90, CD=400, CM=900, I=1, V=5, X=10, L=50, C=100, D=500, M=1000
113
        )
114
        for k, v in mp.items():
0 ignored issues
show
Coding Style Naming introduced by
Variable name "v" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
115
            roman = roman.replace(k, str(v))
116
        # it'll just error if it's empty
117
        try:
118
            value = sum((int(num) for num in roman))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable num does not seem to be defined.
Loading history...
119
        except (ValueError, StopIteration):
120
            raise ValueError(f"Cannot parse roman numerals '{roman}'")
121
        if min_val is not None and value < min_val or min_val is not None and roman > max_val:
122
            raise ValueError(f"Value {roman} (int={value}) is out of range ({min_val}, {max_val})")
123
        return value
124
125
    @classmethod
126
    def retab(cls, s: str, nspaces: int) -> str:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
127
        """
128
        Converts indentation with spaces to tab indentation.
129
130
        Args:
131
            s: The string to convert
132
            nspaces: A tab is this number of spaces
133
        """
134
135
        def fix(m):
0 ignored issues
show
Coding Style Naming introduced by
Argument name "m" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
136
            n = len(m.group(1)) // nspaces
0 ignored issues
show
Coding Style Naming introduced by
Variable name "n" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
137
            return "\t" * n + " " * (len(m.group(1)) % nspaces)
138
139
        return regex.sub("^( +)", fix, s, flags=regex.V1 | regex.MULTILINE)
140
141
    @classmethod
142
    def strip_empty_decimal(cls, num: Union[float, str]) -> str:
143
        """
144
        Replaces prefix . with 0. and strips trailing .0 and trailing .
145
        """
146
        try:
147
            float(num)
148
        except TypeError:
149
            if not isinstance(num, str):
150
                raise TypeError("Must be either str or float-like") from None
151
        t = str(num)
0 ignored issues
show
Coding Style Naming introduced by
Variable name "t" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
152
        if t.startswith("."):
153
            t = "0" + t
0 ignored issues
show
Coding Style Naming introduced by
Variable name "t" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
154
        if "." in t:
0 ignored issues
show
unused-code introduced by
Unnecessary "else" after "return"
Loading history...
155
            return t.rstrip("0").rstrip(".")
156
        else:
157
            return t
158
159
    @classmethod
160
    def tabs_to_list(cls, s: str) -> Sequence[str]:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
161
        """
162
        Splits by tabs, but preserving quoted tabs, stripping quotes.
163
        In other words, will not split within a quoted substring.
164
        Double and single quotes are handled.
165
        """
166
        pat = regex.compile(r"""((?:[^\t"']|"[^"]*"|'[^']*')+)""", flags=regex.V1)
167
        # Don't strip double 2x quotes: ex ""55"" should be "55", not 55
168
        def strip(i: str) -> str:
169
            if i.endswith('"') or i.endswith("'"):
170
                i = i[:-1]
171
            if i.startswith('"') or i.startswith("'"):
172
                i = i[1:]
173
            return i.strip()
174
175
        return [strip(i) for i in pat.findall(s)]
176
177
    @classmethod
178
    def truncate(
0 ignored issues
show
Coding Style Naming introduced by
Argument name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style Naming introduced by
Argument name "n" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
179
        cls,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
180
        s: Optional[str],
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
181
        n: int,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
182
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
183
        null: Optional[str] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
184
    ) -> Optional[str]:
185
        """
186
        Returns a string if it has ``n`` or fewer characters;
187
        otherwise truncates to length ``n-1`` and appends ``…`` (UTF character).
188
        If ``s`` is None and ``always_dots`` is True, returns ``n`` copies of ``.`` (as a string).
189
        If ``s`` is None otherwise, returns None.
190
191
        Args:
192
            s: The string
193
            n: The maximum length, inclusive
194
            null: Replace ``None`` with this string
195
196
        Returns:
197
            A string or None
198
        """
199
        if s is None:
200
            return null
201
        if len(s) > n:
202
            nx = max(0, n - 1)
0 ignored issues
show
Coding Style Naming introduced by
Variable name "nx" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
203
            return s[:nx] + "…"
204
        return s
205
206
    # these are provided to avoid having to call with labdas or functools.partial
207
    @classmethod
208
    def truncating(
0 ignored issues
show
Coding Style Naming introduced by
Argument name "n" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
introduced by
Missing function or method docstring
Loading history...
209
        cls,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
210
        n: int = 40,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
211
        always_dots: bool = False,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
212
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
213
        null: Optional[str] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
214
    ) -> Callable[[str], str]:
215
        # pretty much functools.partial
216
        def trunc(s: str) -> str:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
217
            return cls.truncate(s, n, null=null)
218
219
        trunc.__name__ = f"truncate({n},{'…' if always_dots else ''})"
220
        return trunc
221
222
    @classmethod
223
    def longest(cls, parts: Iterable[T]) -> T:
224
        """
225
        Returns an element with the highest ``len``.
226
        """
227
        mx = ""
0 ignored issues
show
Coding Style Naming introduced by
Variable name "mx" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
228
        for i, x in enumerate(parts):
0 ignored issues
show
Coding Style Naming introduced by
Variable name "x" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Unused Code introduced by
The variable i seems to be unused.
Loading history...
229
            if len(x) > len(mx):
230
                mx = x
0 ignored issues
show
Coding Style Naming introduced by
Variable name "mx" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
231
        return mx
232
233
    @classmethod
234
    def longest_str(cls, parts: Iterable[str]) -> str:
235
        """
236
        Returns the argmax by length.
237
        """
238
        return cls.longest(parts)
239
240
    @classmethod
241
    def strip_off_start(cls, s: str, pre: str) -> str:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
242
        """
243
        Strips the full string ``pre`` from the start of ``str``.
244
        See ``Tools.strip_off`` for more info.
245
        """
246
        if not isinstance(pre, str):
247
            raise TypeError(f"{pre} is not a string")
248
        if s.startswith(pre):
249
            s = s[len(pre) :]
250
        return s
251
252
    @classmethod
253
    def strip_off_end(cls, s: str, suf: str) -> str:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
254
        """
255
        Strips the full string ``suf`` from the end of ``str``.
256
        See `Tools.strip_off` for more info.
257
        """
258
        if not isinstance(suf, str):
259
            raise TypeError(f"{suf} is not a string")
260
        if s.endswith(suf):
261
            s = s[: -len(suf)]
262
        return s
263
264
    @classmethod
265
    def strip_off(cls, s: str, prefix_or_suffix: str) -> str:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
266
        """
267
        Strip a substring from the beginning or end of a string (at most 1 occurrence).
268
        """
269
        return StringTools.strip_off_start(
270
            StringTools.strip_off_end(s, prefix_or_suffix), prefix_or_suffix
271
        )
272
273
    @classmethod
274
    def strip_ends(cls, s: str, prefix: str, suffix: str) -> str:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
275
        """
276
        Strips a substring from the start, and another substring from the end, of a string (at most 1 occurrence each).
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (119/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
277
        """
278
        return StringTools.strip_off_start(StringTools.strip_off_end(s, suffix), prefix)
279
280
    @classmethod
281
    def strip_any_ends(
0 ignored issues
show
Coding Style Naming introduced by
Argument name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
282
        cls,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
283
        s: str,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
284
        prefixes: Union[str, Sequence[str]],
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
285
        suffixes: Union[str, Sequence[str]],
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
286
    ) -> str:
287
        """
288
        Flexible variant that strips any number of prefixes and any number of suffixes.
289
        Also less type-safe than more specific variants.
290
        Note that the order of the prefixes (or suffixes) DOES matter.
291
        """
292
        prefixes = (
293
            [str(z) for z in prefixes]
294
            if StringTools.is_true_iterable(prefixes)
295
            else [str(prefixes)]
296
        )
297
        suffixes = (
298
            [str(z) for z in suffixes]
299
            if StringTools.is_true_iterable(suffixes)
300
            else [str(suffixes)]
301
        )
302
        s = str(s)
303
        for pre in prefixes:
304
            if s.startswith(pre):
305
                s = s[len(pre) :]
306
        for suf in suffixes:
307
            if s.endswith(suf):
308
                s = s[: -len(suf)]
309
        return s
310
311
    @classmethod
312
    def strip_brackets(cls, text: str) -> str:
313
        """
314
        Strips any and all pairs of brackets from start and end of a string, but only if they're paired.
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (104/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
315
        See Also:
316
             strip_paired
317
        """
318
        pieces = [
319
            ("(", ")"),
320
            ("[", "]"),
321
            ("[", "]"),
322
            ("{", "}"),
323
            ("<", ">"),
324
            (Chars.lshell, Chars.rshell),
325
            (Chars.langle, Chars.rangle),
326
            (Chars.ldparen, Chars.rdparen),
327
            (Chars.ldbracket, Chars.rdbracket),
328
            (Chars.ldangle, Chars.rdangle),
329
            (Chars.ldshell, Chars.rdshell),
330
        ]
331
        return StringTools.strip_paired(text, pieces)
332
333
    @classmethod
334
    def strip_quotes(cls, text: str) -> str:
335
        """
336
        Strips any and all pairs of quotes from start and end of a string, but only if they're paired.
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (102/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
337
338
        See Also:
339
            strip_paired
340
        """
341
        pieces = [
342
            ("`", "`"),
343
            (Chars.lsq, Chars.rsq),
344
            (Chars.ldq, Chars.rdq),
345
            ("'", "'"),
346
            ('"', '"'),
347
        ]
348
        return StringTools.strip_paired(text, pieces)
349
350
    @classmethod
351
    def strip_brackets_and_quotes(cls, text: str) -> str:
352
        """
353
        Strips any and all pairs of brackets and quotes from start and end of a string, but only if they're paired.
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (115/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
354
355
        See Also:
356
            strip_paired
357
        """
358
        pieces = [
359
            ("(", ")"),
360
            ("[", "]"),
361
            ("[", "]"),
362
            ("{", "}"),
363
            ("<", ">"),
364
            (Chars.lshell, Chars.rshell),
365
            (Chars.langle, Chars.rangle),
366
            ("`", "`"),
367
            (Chars.lsq, Chars.rsq),
368
            (Chars.ldq, Chars.rdq),
369
            ("'", "'"),
370
            ('"', '"'),
371
            (Chars.ldparen, Chars.rdparen),
372
            (Chars.ldbracket, Chars.rdbracket),
373
            (Chars.ldangle, Chars.rdangle),
374
            (Chars.ldshell, Chars.rdshell),
375
        ]
376
        return StringTools.strip_paired(text, pieces)
377
378
    @classmethod
379
    def strip_paired(cls, text: str, pieces: Iterable[Tuple[str, str]]) -> str:
380
        """
381
        Strips pairs of (start, end) from the ends of strings.
382
383
        Example:
384
            >>> StringTools.strip_paired("[(abc]", [("(", ")"), ("[", "]"))  # returns "(abc"
385
386
        Also see ``strip_brackets``
387
        """
388
        if any([a for a in pieces if len(a) != 2]):
389
            raise ValueError(f"Each item must be a string of length 2: (stard, end); got {pieces}")
390
        text = str(text)
391
        while len(text) > 0:
392
            yes = False
393
            for a, b in pieces:
0 ignored issues
show
Coding Style Naming introduced by
Variable name "a" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style Naming introduced by
Variable name "b" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
394
                while text.startswith(a) and text.endswith(b):
395
                    text = text[1:-1]
396
                    yes = True
397
            if not yes:
398
                break
399
        return text
400
401
    @classmethod
402
    def replace_all(cls, s: str, rep: Mapping[str, str]) -> str:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
403
        """
404
        Simply replace multiple things in a string.
405
        """
406
        warnings.warn("replace_all will be removed", DeprecationWarning)
407
        for k, v in rep.items():
0 ignored issues
show
Coding Style Naming introduced by
Variable name "v" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
408
            s = s.replace(k, v)
409
        return s
410
411
    @classmethod
412
    def superscript(cls, s: Union[str, float]) -> str:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
413
        """
414
        Replaces digits, +, =, (, and ) with equivalent Unicode superscript chars (ex ¹).
415
        """
416
        return "".join(dict(zip("0123456789-+=()", "⁰¹²³⁴⁵⁶⁷⁸⁹⁻⁺⁼⁽⁾")).get(c, c) for c in s)
417
418
    @classmethod
419
    def subscript(cls, s: Union[str, float]) -> str:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
420
        """
421
        Replaces digits, +, =, (, and ) with equivalent Unicode subscript chars (ex ₁).
422
        """
423
        return "".join(dict(zip("0123456789+-=()", "₀₁₂₃₄₅₆₇₈₉₊₋₌₍₎")).get(c, c) for c in s)
424
425
    @classmethod
426
    def unsuperscript(cls, s: Union[str, float]) -> str:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
427
        """
428
        Replaces Unicode superscript digits, +, =, (, and ) with normal chars.
429
        """
430
        return "".join(dict(zip("⁰¹²³⁴⁵⁶⁷⁸⁹⁻⁺⁼⁽⁾", "0123456789-+=()")).get(c, c) for c in s)
431
432
    @classmethod
433
    def unsubscript(cls, s: Union[str, float]) -> str:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
434
        """
435
        Replaces Unicode superscript digits, +, =, (, and ) with normal chars.
436
        """
437
        return "".join(dict(zip("₀₁₂₃₄₅₆₇₈₉₊₋₌₍₎", "0123456789+-=()")).get(c, c) for c in s)
438
439
    @classmethod
440
    def dashes_to_hm(cls, s: str) -> str:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
441
        """
442
        Replaces most Latin-alphabet dash-like and hyphen-like characters with a hyphen-minus.
443
        """
444
        smallem = "﹘"
445
        smallhm = "﹣"
446
        fullhm = "-"
447
        for c in [
0 ignored issues
show
Coding Style Naming introduced by
Variable name "c" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
448
            Chars.em,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
449
            Chars.en,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
450
            Chars.fig,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
451
            Chars.minus,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
452
            Chars.hyphen,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
453
            Chars.nbhyphen,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
454
            smallem,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
455
            smallhm,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
456
            fullhm,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
457
        ]:
458
            s = str(s).replace(c, "-")
459
        return s
460
461
    @classmethod
462
    def pretty_float(cls, v: Union[float, int], n_sigfigs: Optional[int] = 5) -> str:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "v" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
463
        """
464
        Represents a float as a string, with symbols for NaN and infinity.
465
        The returned string always has a minus or + prepended. Strip off the plus with .lstrip('+').
466
        If v is an integer (by isinstance), makes sure to display without a decimal point.
467
        If n_sigfigs < 2, will never have a
468
        For ex:
469
            - StringTools.pretty_float(.2222222)       # '+0.22222'
470
            - StringTools.pretty_float(-.2222222)      # '−0.22222' (Unicode minus)
471
            - StringTools.pretty_float(-float('inf'))  # '−∞'
472
            - StringTools.pretty_float(np.NaN)         # '⌀'
473
        """
474
        # TODO this seems absurdly long for what it does
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
475
        if n_sigfigs is None or n_sigfigs < 1:
476
            raise OutOfRangeError(
477
                f"Sigfigs of {n_sigfigs} is nonpositive",
478
                value=n_sigfigs,
479
                minimum=1,
480
            )
481
        # first, handle NaN and infinities
482
        if np.isneginf(v):
0 ignored issues
show
unused-code introduced by
Unnecessary "elif" after "return"
Loading history...
483
            return Chars.minus + Chars.inf
484
        elif np.isposinf(v):
485
            return "+" + Chars.inf
486
        elif np.isnan(v):
487
            return Chars.null
488
        # sweet. it's a regular float or int.
489
        if n_sigfigs is None:
490
            s = StringTools.strip_empty_decimal(str(v))
0 ignored issues
show
Coding Style Naming introduced by
Variable name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
491
        else:
492
            # yes, this is weird. we need to convert from str to float then back to str
493
            s = str(float(str(("%." + str(n_sigfigs) + "g") % v)))
0 ignored issues
show
Coding Style Naming introduced by
Variable name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
494
        # remove the .0 if the precision doesn't support it
495
        # if v >= 1 and n_sigfigs<2, it couldn't have a decimal
496
        # and if n_sigfigs<1, it definitely can't
497
        # and ... %g does this.
498
        if isinstance(v, int) or n_sigfigs is not None and n_sigfigs < 2:
499
            s = StringTools.strip_empty_decimal(s)
0 ignored issues
show
Coding Style Naming introduced by
Variable name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
500
        # prepend + or - (unless 0)
501
        if float(s) == 0.0:
502
            return s
503
        s = s.replace("-", Chars.minus)
0 ignored issues
show
Coding Style Naming introduced by
Variable name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
504
        if not s.startswith(Chars.minus):
505
            s = "+" + s[1:]
0 ignored issues
show
Coding Style Naming introduced by
Variable name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
506
        if len(s) > 1 and s[1] == ".":
507
            s = s[0] + "0." + s[2:]
0 ignored issues
show
Coding Style Naming introduced by
Variable name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
508
        return s
509
510
    @classmethod
511
    def pretty_function(
0 ignored issues
show
best-practice introduced by
Too many return statements (8/6)
Loading history...
512
        cls, function, *, with_address: bool = False, prefix: str = "⟨", suffix: str = "⟩"
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
513
    ) -> str:
514
        """
515
        Get a better and shorter name for a function than str(function).
516
        Ex: pprint_function(lambda s: s)  == '<λ>'
517
        - Instead of '<bound method ...', you'll get '<name(nargs)>'
518
        - Instead of 'lambda ...', you'll get '<λ(nargs)>'
519
        - etc.
520
        NOTE 1: If function is None, returns '⌀'
521
        NOTE 2: If function does not have __name__, returns prefix + type(function) + <address> + suffix
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (104/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
522
        NOTE 3: If it's a primitive, returns str(function)
523
524
        Args:
525
            function: Can be anything, but especially useful for functions
526
            with_address: Include ``@ hex-mem-addr`` in the name
527
            prefix: Prefix to the whole string
528
            suffix: Suffix to the whole string
529
        """
530
        if function is None:
531
            return Chars.null
532
        n_args = str(function.__code__.co_argcount) if hasattr(function, "__code__") else "?"
533
        pat = regex.compile(r"^<bound method [^ .]+\.([^ ]+) of (.+)>$", flags=regex.V1)
534
        boundmatch = pat.fullmatch(str(function))
535
        pat = regex.compile(r"<([A-Za-z0-9_.<>]+)[ ']*object", flags=regex.V1)
536
        objmatch = pat.search(str(function))  # instance of global or local class
537
        addr = " @ " + hex(id(function)) if with_address else ""
538
        if cls.is_lambda(function):
0 ignored issues
show
unused-code introduced by
Unnecessary "elif" after "return"
Loading history...
539
            # simplify lambda functions!
540
            return prefix + "λ(" + n_args + ")" + addr + suffix
541
        elif boundmatch is not None:
542
            # it's a method (bound function)
543
            # don't show the address of the instance AND its method
544
            pat = regex.compile(r"@ ?0x[0-9a-hA-H]+\)?$", flags=regex.V1)
545
            s = pat.sub("", boundmatch.group(2)).strip()
0 ignored issues
show
Coding Style Naming introduced by
Variable name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
546
            return (
547
                prefix + "`" + s + "`." + boundmatch.group(1) + "(" + n_args + ")" + addr + suffix
548
            )
549
        elif isinstance(function, type):
550
            # it's a class
551
            return prefix + "type:" + function.__name__ + suffix
552
        elif callable(function):
553
            # it's an actual function
554
            return prefix + function.__name__ + addr + suffix
555
        elif hasattr(function, "__dict__") and len(function.__dict__) > 0:
556
            # it's a member with attributes
557
            # it's interesting enough that it may have a good __str__
558
            s = StringTools.strip_off_end(
0 ignored issues
show
Coding Style Naming introduced by
Variable name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
559
                StringTools.strip_off_start(str(function), prefix), suffix
560
            )
561
            return prefix + s + addr + suffix
562
        elif objmatch is not None:
563
            # it's an instance without attributes
564
            s = objmatch.group(1)
0 ignored issues
show
Coding Style Naming introduced by
Variable name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
565
            if "." in s:
566
                s = s[s.rindex(".") + 1 :]
0 ignored issues
show
Coding Style Naming introduced by
Variable name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
567
            return prefix + s + addr + suffix
568
        else:
569
            # it's a primitive, etc
570
            s = StringTools.strip_off_end(
0 ignored issues
show
Coding Style Naming introduced by
Variable name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
571
                StringTools.strip_off_start(str(function), prefix), suffix
572
            )
573
            return s
574
575
    @classmethod
576
    def greek_to_name(cls) -> Mapping[str, str]:
577
        """
578
        Returns a dict from Greek lowercase+uppercase Unicode chars to their full names
579
        @return A defensive copy
580
        """
581
        return copy(StringTools._greek_alphabet)
582
583
    @classmethod
584
    def name_to_greek(cls) -> Mapping[str, str]:
585
        """
586
        Returns a dict from Greek lowercase+uppercase letter names to their Unicode chars
587
        @return A defensive copy
588
        """
589
        return {v: k for k, v in StringTools._greek_alphabet.items()}
590
591
    @classmethod
592
    def fix_greek(cls, s: str, lowercase: bool = False) -> str:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
593
        """
594
        Replaces Greek letter names with their Unicode equivalents.
595
        Does this correctly by replacing superstrings before substrings.
596
        Ex: '1-beta' is '1-β' rather than '1-bη'
597
        If lowercase is True: Replaces Beta, BeTa, and BETA with β
598
        Else: Replaces Beta with a capital Greek Beta and ignores BETA and BeTa.
599
        """
600
        # Clever if I may say so:
601
        # If we just sort from longest to shortest, we can't replace substrings by accident
602
        # For example we'll replace 'beta' before 'eta', so '1-beta' won't become '1-bη'
603
        greek = sorted(
604
            [(v, k) for k, v in StringTools._greek_alphabet.items()],
605
            key=lambda t: -len(t[1]),
606
        )
607
        for k, v in greek:
0 ignored issues
show
Coding Style Naming introduced by
Variable name "v" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
608
            if k[0].isupper() and lowercase:
609
                continue
610
            if lowercase:
611
                s = regex.compile(k, flags=regex.V1 | regex.IGNORECASE).sub(v, s)
612
            else:
613
                s = s.replace(k, v)
614
        return s
615
616
    @classmethod
617
    def join(
618
        cls,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
619
        seq: Iterable[T],
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
620
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
621
        sep: str = "\t",
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
622
        attr: Optional[str] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
623
        prefix: str = "",
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
624
        suffix: str = "",
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
625
    ) -> str:
626
        """
627
        Join elements into a str more easily than ''.join. Just simplifies potentially long expressions.
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (104/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
628
        Won't break with ValueError if the elements aren't strs.
629
        Ex:
630
            - StringTools.join([1,2,3])  # "1    2    3"
631
            - StringTools.join(cars, sep=',', attr='make', prefix="(", suffix=")")`  # "(Ford),(Ford),(BMW)"
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (108/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
632
633
            seq: Sequence of elements
634
            sep: Delimiter
635
            attr: Get this attribute from each element (in `seq`), or use the element itself if None
636
            prefix: Prefix before each item
637
            suffix: Suffix after each item
638
639
        Returns:
640
            A string
641
        """
642
        if attr is None:
0 ignored issues
show
unused-code introduced by
Unnecessary "else" after "return"
Loading history...
643
            return sep.join([prefix + str(s) + suffix for s in seq])
644
        else:
645
            return sep.join([prefix + str(getattr(s, attr)) + suffix for s in seq])
646
647
    @classmethod
648
    def join_kv(
649
        cls,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
650
        seq: Mapping[T, V],
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
651
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
652
        sep: str = "\t",
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
653
        eq: str = "=",
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
Coding Style Naming introduced by
Variable name "eq" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
654
        prefix: str = "",
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
655
        suffix: str = "",
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
656
    ) -> str:
657
        """
658
        Joins dict elements into a str like 'a=1, b=2, c=3`.
659
        Won't break with ValueError if the keys or values aren't strs.
660
661
        Args:
662
            seq: Dict-like, with ``items()``
663
            sep: Delimiter
664
            eq: Separates a key with its value
665
            prefix: Prepend before every key
666
            suffix: Append after every value
667
668
        Returns:
669
            A string
670
        """
671
        return sep.join([prefix + str(k) + eq + str(v) + suffix for k, v in seq.items()])
672
673
    _greek_alphabet = {
674
        "\u0391": "Alpha",
675
        "\u0392": "Beta",
676
        "\u0393": "Gamma",
677
        "\u0394": "Delta",
678
        "\u0395": "Epsilon",
679
        "\u0396": "Zeta",
680
        "\u0397": "Eta",
681
        "\u0398": "Theta",
682
        "\u0399": "Iota",
683
        "\u039A": "Kappa",
684
        "\u039B": "Lambda",
685
        "\u039C": "Mu",
686
        "\u039D": "Nu",
687
        "\u039E": "Xi",
688
        "\u039F": "Omicron",
689
        "\u03A0": "Pi",
690
        "\u03A1": "Rho",
691
        "\u03A3": "Sigma",
692
        "\u03A4": "Tau",
693
        "\u03A5": "Upsilon",
694
        "\u03A6": "Phi",
695
        "\u03A7": "Chi",
696
        "\u03A8": "Psi",
697
        "\u03A9": "Omega",
698
        "\u03B1": "alpha",
699
        "\u03B2": "beta",
700
        "\u03B3": "gamma",
701
        "\u03B4": "delta",
702
        "\u03B5": "epsilon",
703
        "\u03B6": "zeta",
704
        "\u03B7": "eta",
705
        "\u03B8": "theta",
706
        "\u03B9": "iota",
707
        "\u03BA": "kappa",
708
        "\u03BB": "lambda",
709
        "\u03BC": "mu",
710
        "\u03BD": "nu",
711
        "\u03BE": "xi",
712
        "\u03BF": "omicron",
713
        "\u03C0": "pi",
714
        "\u03C1": "rho",
715
        "\u03C3": "sigma",
716
        "\u03C4": "tau",
717
        "\u03C5": "upsilon",
718
        "\u03C6": "phi",
719
        "\u03C7": "chi",
720
        "\u03C8": "psi",
721
        "\u03C9": "omega",
722
    }
723
724
725
__all__ = ["StringTools"]
726