Passed
Push — main ( 60119b...e5c7f7 )
by Douglas
02:02
created

StringTools.roman_to_arabic()   B

Complexity

Conditions 7

Size

Total Lines 29
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 14
nop 4
dl 0
loc 29
rs 8
c 0
b 0
f 0
1
import json
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 regex
0 ignored issues
show
introduced by
Unable to import 'regex'
Loading history...
8
9
from pocketutils.core import JsonEncoder
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 json.dumps(
27
            dct,
28
            default=JsonEncoder().default,
29
            sort_keys=True,
30
            indent=2,
31
            ensure_ascii=False,
32
        )
33
34
    @classmethod
35
    def extract_group_1(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
36
        cls, pattern: Union[str, regex.Pattern], value: Optional[str], ignore_null: bool = False
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
37
    ) -> Optional[str]:
38
        warnings.warn("extract_group_1 will be removed", DeprecationWarning)
39
        """
40
        Performs a ``fullmatch`` on a target string and returns capture group 1, or None if there was no match.
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...
41
42
        Args:
43
            pattern: Regex pattern
44
            value: The target string
45
            ignore_null: If True, returns None if ``value`` is None; otherwise raises a ValueError if ``value`` is None
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...
46
                         (Useful for *map*-like operations.)
47
48
        Returns The first capture group, or None
49
        """
0 ignored issues
show
Unused Code introduced by
This string statement has no effect and could be removed.
Loading history...
50
        pattern = (
51
            pattern
52
            if isinstance(pattern, regex.Pattern)
53
            else regex.compile(pattern, flags=regex.V1)
54
        )
55
        if pattern.groups != 1:
56
            raise ValueError(f"Pattern {pattern} has {pattern.groups} groups, not 1")
57
        if value is None and ignore_null:
58
            return None
59
        match = pattern.fullmatch(value)
60
        if match is None:
61
            return None
62
        return match.group(1)
63
64
    @classmethod
65
    def join_to_str(cls, *items: Any, last: str, sep: str = ", ") -> str:
66
        """
67
        Joins items to something like "cat, dog, and pigeon" or "cat, dog, or pigeon".
68
69
        Args:
70
            *items: Items to join; ``str(item) for item in items`` will be used
71
            last: Probably "and", "or", "and/or", or ""
72
                    Spaces are added/removed as needed if ``suffix`` is alphanumeric
73
                    or "and/or", after stripping whitespace off the ends.
74
            sep: Used to separate all words; include spaces as desired
75
76
        Examples:
77
            - ``join_to_str(["cat", "dog", "elephant"], last="and")  # cat, dog, and elephant``
78
            - ``join_to_str(["cat", "dog"], last="and")  # cat and dog``
79
            - ``join_to_str(["cat", "dog", "elephant"], last="", sep="/")  # cat/dog/elephant``
80
        """
81
        if last.strip().isalpha() or last.strip() == "and/or":
82
            last = last.strip() + " "
83
        items = [str(s).strip("'" + '"' + " ") for s in items]
84
        if len(items) > 2:
0 ignored issues
show
unused-code introduced by
Unnecessary "else" after "return"
Loading history...
85
            return sep.join(items[:-1]) + sep + last + items[-1]
86
        else:
87
            return (" " + last + " ").join(items)
88
89
    @classmethod
90
    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...
91
        """
92
        Strips all characters under the Unicode 'Cc' category.
93
        """
94
        return _control_chars.sub("", s)
95
96
    @classmethod
97
    def roman_to_arabic(
98
        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...
99
    ) -> int:
100
        """
101
        Converts roman numerals to an integer.
102
103
        Args:
104
            roman: A string like "MCIV"
105
            min_val: Raise a ValueError if the parsed value is less than this
106
            max_val: Raise a ValueError if the parsed value is more than this
107
108
        Returns:
109
            The arabic numeral as a Python int
110
        """
111
        # this order is IMPORTANT!
112
        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...
113
            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
114
        )
115
        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...
116
            roman = roman.replace(k, str(v))
117
        # it'll just error if it's empty
118
        try:
119
            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...
120
        except (ValueError, StopIteration):
121
            raise ValueError(f"Cannot parse roman numerals '{roman}'")
122
        if min_val is not None and value < min_val or min_val is not None and roman > max_val:
123
            raise ValueError(f"Value {roman} (int={value}) is out of range ({min_val}, {max_val})")
124
        return value
125
126
    @classmethod
127
    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...
128
        """
129
        Converts indentation with spaces to tab indentation.
130
131
        Args:
132
            s: The string to convert
133
            nspaces: A tab is this number of spaces
134
        """
135
136
        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...
137
            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...
138
            return "\t" * n + " " * (len(m.group(1)) % nspaces)
139
140
        return regex.sub("^( +)", fix, s, flags=regex.V1 | regex.MULTILINE)
141
142
    @classmethod
143
    def strip_empty_decimal(cls, num: Union[float, str]) -> str:
144
        """
145
        Replaces prefix . with 0. and strips trailing .0 and trailing .
146
        """
147
        try:
148
            float(num)
149
        except TypeError:
150
            if not isinstance(num, str):
151
                raise TypeError("Must be either str or float-like") from None
152
        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...
153
        if t.startswith("."):
154
            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...
155
        if "." in t:
0 ignored issues
show
unused-code introduced by
Unnecessary "else" after "return"
Loading history...
156
            return t.rstrip("0").rstrip(".")
157
        else:
158
            return t
159
160
    @classmethod
161
    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...
162
        """
163
        Splits by tabs, but preserving quoted tabs, stripping quotes.
164
        In other words, will not split within a quoted substring.
165
        Double and single quotes are handled.
166
        """
167
        pat = regex.compile(r"""((?:[^\t"']|"[^"]*"|'[^']*')+)""", flags=regex.V1)
168
        # Don't strip double 2x quotes: ex ""55"" should be "55", not 55
169
        def strip(i: str) -> str:
170
            if i.endswith('"') or i.endswith("'"):
171
                i = i[:-1]
172
            if i.startswith('"') or i.startswith("'"):
173
                i = i[1:]
174
            return i.strip()
175
176
        return [strip(i) for i in pat.findall(s)]
177
178
    @classmethod
179
    def truncate(
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...
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...
180
        cls,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
181
        s: Optional[str],
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
182
        n: int,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
183
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
184
        null: Optional[str] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
185
    ) -> Optional[str]:
186
        """
187
        Returns a string if it has ``n`` or fewer characters;
188
        otherwise truncates to length ``n-1`` and appends ``…`` (UTF character).
189
        If ``s`` is None and ``always_dots`` is True, returns ``n`` copies of ``.`` (as a string).
190
        If ``s`` is None otherwise, returns None.
191
192
        Args:
193
            s: The string
194
            n: The maximum length, inclusive
195
            null: Replace ``None`` with this string
196
197
        Returns:
198
            A string or None
199
        """
200
        if s is None:
201
            return null
202
        if len(s) > n:
203
            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...
204
            return s[:nx] + "…"
205
        return s
206
207
    # these are provided to avoid having to call with labdas or functools.partial
208
    @classmethod
209
    def truncating(
0 ignored issues
show
introduced by
Missing function or method docstring
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...
210
        cls,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
211
        n: int = 40,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
212
        always_dots: bool = False,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
213
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
214
        null: Optional[str] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
215
    ) -> Callable[[str], str]:
216
        # pretty much functools.partial
217
        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...
218
            return cls.truncate(s, n, null=null)
219
220
        trunc.__name__ = f"truncate({n},{'…' if always_dots else ''})"
221
        return trunc
222
223
    @classmethod
224
    def longest(cls, parts: Iterable[T]) -> T:
225
        """
226
        Returns an element with the highest ``len``.
227
        """
228
        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...
229
        for i, x in enumerate(parts):
0 ignored issues
show
Unused Code introduced by
The variable i seems to be unused.
Loading history...
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...
230
            if len(x) > len(mx):
231
                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...
232
        return mx
233
234
    @classmethod
235
    def longest_str(cls, parts: Iterable[str]) -> str:
236
        """
237
        Returns the argmax by length.
238
        """
239
        return cls.longest(parts)
240
241
    @classmethod
242
    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...
243
        """
244
        Strips the full string ``pre`` from the start of ``str``.
245
        See ``Tools.strip_off`` for more info.
246
        """
247
        if not isinstance(pre, str):
248
            raise TypeError(f"{pre} is not a string")
249
        if s.startswith(pre):
250
            s = s[len(pre) :]
251
        return s
252
253
    @classmethod
254
    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...
255
        """
256
        Strips the full string ``suf`` from the end of ``str``.
257
        See `Tools.strip_off` for more info.
258
        """
259
        if not isinstance(suf, str):
260
            raise TypeError(f"{suf} is not a string")
261
        if s.endswith(suf):
262
            s = s[: -len(suf)]
263
        return s
264
265
    @classmethod
266
    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...
267
        """
268
        Strip a substring from the beginning or end of a string (at most 1 occurrence).
269
        """
270
        return StringTools.strip_off_start(
271
            StringTools.strip_off_end(s, prefix_or_suffix), prefix_or_suffix
272
        )
273
274
    @classmethod
275
    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...
276
        """
277
        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...
278
        """
279
        return StringTools.strip_off_start(StringTools.strip_off_end(s, suffix), prefix)
280
281
    @classmethod
282
    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...
283
        cls,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
284
        s: str,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
285
        prefixes: Union[str, Sequence[str]],
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
286
        suffixes: Union[str, Sequence[str]],
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
287
    ) -> str:
288
        """
289
        Flexible variant that strips any number of prefixes and any number of suffixes.
290
        Also less type-safe than more specific variants.
291
        Note that the order of the prefixes (or suffixes) DOES matter.
292
        """
293
        prefixes = (
294
            [str(z) for z in prefixes]
295
            if StringTools.is_true_iterable(prefixes)
296
            else [str(prefixes)]
297
        )
298
        suffixes = (
299
            [str(z) for z in suffixes]
300
            if StringTools.is_true_iterable(suffixes)
301
            else [str(suffixes)]
302
        )
303
        s = str(s)
304
        for pre in prefixes:
305
            if s.startswith(pre):
306
                s = s[len(pre) :]
307
        for suf in suffixes:
308
            if s.endswith(suf):
309
                s = s[: -len(suf)]
310
        return s
311
312
    @classmethod
313
    def strip_brackets(cls, text: str) -> str:
314
        """
315
        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...
316
        See Also:
317
             strip_paired
318
        """
319
        pieces = [
320
            ("(", ")"),
321
            ("[", "]"),
322
            ("[", "]"),
323
            ("{", "}"),
324
            ("<", ">"),
325
            (Chars.lshell, Chars.rshell),
326
            (Chars.langle, Chars.rangle),
327
            (Chars.ldparen, Chars.rdparen),
328
            (Chars.ldbracket, Chars.rdbracket),
329
            (Chars.ldangle, Chars.rdangle),
330
            (Chars.ldshell, Chars.rdshell),
331
        ]
332
        return StringTools.strip_paired(text, pieces)
333
334
    @classmethod
335
    def strip_quotes(cls, text: str) -> str:
336
        """
337
        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...
338
339
        See Also:
340
            strip_paired
341
        """
342
        pieces = [
343
            ("`", "`"),
344
            (Chars.lsq, Chars.rsq),
345
            (Chars.ldq, Chars.rdq),
346
            ("'", "'"),
347
            ('"', '"'),
348
        ]
349
        return StringTools.strip_paired(text, pieces)
350
351
    @classmethod
352
    def strip_brackets_and_quotes(cls, text: str) -> str:
353
        """
354
        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...
355
356
        See Also:
357
            strip_paired
358
        """
359
        pieces = [
360
            ("(", ")"),
361
            ("[", "]"),
362
            ("[", "]"),
363
            ("{", "}"),
364
            ("<", ">"),
365
            (Chars.lshell, Chars.rshell),
366
            (Chars.langle, Chars.rangle),
367
            ("`", "`"),
368
            (Chars.lsq, Chars.rsq),
369
            (Chars.ldq, Chars.rdq),
370
            ("'", "'"),
371
            ('"', '"'),
372
            (Chars.ldparen, Chars.rdparen),
373
            (Chars.ldbracket, Chars.rdbracket),
374
            (Chars.ldangle, Chars.rdangle),
375
            (Chars.ldshell, Chars.rdshell),
376
        ]
377
        return StringTools.strip_paired(text, pieces)
378
379
    @classmethod
380
    def strip_paired(cls, text: str, pieces: Iterable[Tuple[str, str]]) -> str:
381
        """
382
        Strips pairs of (start, end) from the ends of strings.
383
384
        Example:
385
            >>> StringTools.strip_paired("[(abc]", [("(", ")"), ("[", "]"))  # returns "(abc"
386
387
        Also see ``strip_brackets``
388
        """
389
        if any([a for a in pieces if len(a) != 2]):
390
            raise ValueError(f"Each item must be a string of length 2: (stard, end); got {pieces}")
391
        text = str(text)
392
        while len(text) > 0:
393
            yes = False
394
            for a, b in pieces:
0 ignored issues
show
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...
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...
395
                while text.startswith(a) and text.endswith(b):
396
                    text = text[1:-1]
397
                    yes = True
398
            if not yes:
399
                break
400
        return text
401
402
    @classmethod
403
    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...
404
        """
405
        Simply replace multiple things in a string.
406
        """
407
        warnings.warn("replace_all will be removed", DeprecationWarning)
408
        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...
409
            s = s.replace(k, v)
410
        return s
411
412
    @classmethod
413
    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...
414
        """
415
        Replaces digits, +, =, (, and ) with equivalent Unicode superscript chars (ex ¹).
416
        """
417
        return "".join(dict(zip("0123456789-+=()", "⁰¹²³⁴⁵⁶⁷⁸⁹⁻⁺⁼⁽⁾")).get(c, c) for c in s)
418
419
    @classmethod
420
    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...
421
        """
422
        Replaces digits, +, =, (, and ) with equivalent Unicode subscript chars (ex ₁).
423
        """
424
        return "".join(dict(zip("0123456789+-=()", "₀₁₂₃₄₅₆₇₈₉₊₋₌₍₎")).get(c, c) for c in s)
425
426
    @classmethod
427
    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...
428
        """
429
        Replaces Unicode superscript digits, +, =, (, and ) with normal chars.
430
        """
431
        return "".join(dict(zip("⁰¹²³⁴⁵⁶⁷⁸⁹⁻⁺⁼⁽⁾", "0123456789-+=()")).get(c, c) for c in s)
432
433
    @classmethod
434
    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...
435
        """
436
        Replaces Unicode superscript digits, +, =, (, and ) with normal chars.
437
        """
438
        return "".join(dict(zip("₀₁₂₃₄₅₆₇₈₉₊₋₌₍₎", "0123456789+-=()")).get(c, c) for c in s)
439
440
    @classmethod
441
    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...
442
        """
443
        Replaces most Latin-alphabet dash-like and hyphen-like characters with a hyphen-minus.
444
        """
445
        smallem = "﹘"
446
        smallhm = "﹣"
447
        fullhm = "-"
448
        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...
449
            Chars.em,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
450
            Chars.en,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
451
            Chars.fig,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
452
            Chars.minus,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
453
            Chars.hyphen,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
454
            Chars.nbhyphen,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
455
            smallem,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
456
            smallhm,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
457
            fullhm,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
458
        ]:
459
            s = str(s).replace(c, "-")
460
        return s
461
462
    @classmethod
463
    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...
464
        """
465
        Represents a float as a string, with symbols for NaN and infinity.
466
        The returned string always has a minus or + prepended. Strip off the plus with .lstrip('+').
467
        If v is an integer (by isinstance), makes sure to display without a decimal point.
468
        If n_sigfigs < 2, will never have a
469
        For ex:
470
            - StringTools.pretty_float(.2222222)       # '+0.22222'
471
            - StringTools.pretty_float(-.2222222)      # '−0.22222' (Unicode minus)
472
            - StringTools.pretty_float(-float('inf'))  # '−∞'
473
            - StringTools.pretty_float(np.NaN)         # '⌀'
474
        """
475
        # 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...
476
        if n_sigfigs is None or n_sigfigs < 1:
477
            raise OutOfRangeError(
478
                f"Sigfigs of {n_sigfigs} is nonpositive",
479
                value=n_sigfigs,
480
                minimum=1,
481
            )
482
        # first, handle NaN and infinities
483
        if np.isneginf(v):
0 ignored issues
show
unused-code introduced by
Unnecessary "elif" after "return"
Loading history...
484
            return Chars.minus + Chars.inf
485
        elif np.isposinf(v):
486
            return "+" + Chars.inf
487
        elif np.isnan(v):
488
            return Chars.null
489
        # sweet. it's a regular float or int.
490
        if n_sigfigs is None:
491
            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...
492
        else:
493
            # yes, this is weird. we need to convert from str to float then back to str
494
            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...
495
        # remove the .0 if the precision doesn't support it
496
        # if v >= 1 and n_sigfigs<2, it couldn't have a decimal
497
        # and if n_sigfigs<1, it definitely can't
498
        # and ... %g does this.
499
        if isinstance(v, int) or n_sigfigs is not None and n_sigfigs < 2:
500
            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...
501
        # prepend + or - (unless 0)
502
        if float(s) == 0.0:
503
            return s
504
        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...
505
        if not s.startswith(Chars.minus):
506
            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...
507
        if len(s) > 1 and s[1] == ".":
508
            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...
509
        return s
510
511
    @classmethod
512
    def pretty_function(
0 ignored issues
show
best-practice introduced by
Too many return statements (8/6)
Loading history...
513
        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...
514
    ) -> str:
515
        """
516
        Get a better and shorter name for a function than str(function).
517
        Ex: pprint_function(lambda s: s)  == '<λ>'
518
        - Instead of '<bound method ...', you'll get '<name(nargs)>'
519
        - Instead of 'lambda ...', you'll get '<λ(nargs)>'
520
        - etc.
521
        NOTE 1: If function is None, returns '⌀'
522
        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...
523
        NOTE 3: If it's a primitive, returns str(function)
524
525
        Args:
526
            function: Can be anything, but especially useful for functions
527
            with_address: Include `@ hex-mem-addr` in the name
528
            prefix: Prefix to the whole string
529
            suffix: Suffix to the whole string
530
        """
531
        if function is None:
532
            return Chars.null
533
        n_args = str(function.__code__.co_argcount) if hasattr(function, "__code__") else "?"
534
        pat = regex.compile(r"^<bound method [^ .]+\.([^ ]+) of (.+)>$", flags=regex.V1)
535
        boundmatch = pat.fullmatch(str(function))
536
        pat = regex.compile(r"<([A-Za-z0-9_.<>]+)[ ']*object", flags=regex.V1)
537
        objmatch = pat.search(str(function))  # instance of global or local class
538
        addr = " @ " + hex(id(function)) if with_address else ""
539
        if cls.is_lambda(function):
0 ignored issues
show
unused-code introduced by
Unnecessary "elif" after "return"
Loading history...
540
            # simplify lambda functions!
541
            return prefix + "λ(" + n_args + ")" + addr + suffix
542
        elif boundmatch is not None:
543
            # it's a method (bound function)
544
            # don't show the address of the instance AND its method
545
            pat = regex.compile(r"@ ?0x[0-9a-hA-H]+\)?$", flags=regex.V1)
546
            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...
547
            return (
548
                prefix + "`" + s + "`." + boundmatch.group(1) + "(" + n_args + ")" + addr + suffix
549
            )
550
        elif isinstance(function, type):
551
            # it's a class
552
            return prefix + "type:" + function.__name__ + suffix
553
        elif callable(function):
554
            # it's an actual function
555
            return prefix + function.__name__ + addr + suffix
556
        elif hasattr(function, "__dict__") and len(function.__dict__) > 0:
557
            # it's a member with attributes
558
            # it's interesting enough that it may have a good __str__
559
            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...
560
                StringTools.strip_off_start(str(function), prefix), suffix
561
            )
562
            return prefix + s + addr + suffix
563
        elif objmatch is not None:
564
            # it's an instance without attributes
565
            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...
566
            if "." in s:
567
                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...
568
            return prefix + s + addr + suffix
569
        else:
570
            # it's a primitive, etc
571
            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...
572
                StringTools.strip_off_start(str(function), prefix), suffix
573
            )
574
            return s
575
576
    @classmethod
577
    def greek_to_name(cls) -> Mapping[str, str]:
578
        """
579
        Returns a dict from Greek lowercase+uppercase Unicode chars to their full names
580
        @return A defensive copy
581
        """
582
        return copy(StringTools._greek_alphabet)
583
584
    @classmethod
585
    def name_to_greek(cls) -> Mapping[str, str]:
586
        """
587
        Returns a dict from Greek lowercase+uppercase letter names to their Unicode chars
588
        @return A defensive copy
589
        """
590
        return {v: k for k, v in StringTools._greek_alphabet.items()}
591
592
    @classmethod
593
    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...
594
        """
595
        Replaces Greek letter names with their Unicode equivalents.
596
        Does this correctly by replacing superstrings before substrings.
597
        Ex: '1-beta' is '1-β' rather than '1-bη'
598
        If lowercase is True: Replaces Beta, BeTa, and BETA with β
599
        Else: Replaces Beta with a capital Greek Beta and ignores BETA and BeTa.
600
        """
601
        # Clever if I may say so:
602
        # If we just sort from longest to shortest, we can't replace substrings by accident
603
        # For example we'll replace 'beta' before 'eta', so '1-beta' won't become '1-bη'
604
        greek = sorted(
605
            [(v, k) for k, v in StringTools._greek_alphabet.items()],
606
            key=lambda t: -len(t[1]),
607
        )
608
        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...
609
            if k[0].isupper() and lowercase:
610
                continue
611
            if lowercase:
612
                s = regex.compile(k, flags=regex.V1 | regex.IGNORECASE).sub(v, s)
613
            else:
614
                s = s.replace(k, v)
615
        return s
616
617
    @classmethod
618
    def join(
619
        cls,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
620
        seq: Iterable[T],
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
621
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
622
        sep: str = "\t",
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
623
        attr: Optional[str] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
624
        prefix: str = "",
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
625
        suffix: str = "",
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
626
    ) -> str:
627
        """
628
        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...
629
        Won't break with ValueError if the elements aren't strs.
630
        Ex:
631
            - StringTools.join([1,2,3])  # "1    2    3"
632
            - 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...
633
634
            seq: Sequence of elements
635
            sep: Delimiter
636
            attr: Get this attribute from each element (in `seq`), or use the element itself if None
637
            prefix: Prefix before each item
638
            suffix: Suffix after each item
639
640
        Returns:
641
            A string
642
        """
643
        if attr is None:
0 ignored issues
show
unused-code introduced by
Unnecessary "else" after "return"
Loading history...
644
            return sep.join([prefix + str(s) + suffix for s in seq])
645
        else:
646
            return sep.join([prefix + str(getattr(s, attr)) + suffix for s in seq])
647
648
    @classmethod
649
    def join_kv(
650
        cls,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
651
        seq: Mapping[T, V],
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
652
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
653
        sep: str = "\t",
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
654
        eq: str = "=",
0 ignored issues
show
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...
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
655
        prefix: str = "",
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
656
        suffix: str = "",
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
657
    ) -> str:
658
        """
659
        Joins dict elements into a str like 'a=1, b=2, c=3`.
660
        Won't break with ValueError if the keys or values aren't strs.
661
662
        Args:
663
            seq: Dict-like, with ``items()``
664
            sep: Delimiter
665
            eq: Separates a key with its value
666
            prefix: Prepend before every key
667
            suffix: Append after every value
668
669
        Returns:
670
            A string
671
        """
672
        return sep.join([prefix + str(k) + eq + str(v) + suffix for k, v in seq.items()])
673
674
    _greek_alphabet = {
675
        "\u0391": "Alpha",
676
        "\u0392": "Beta",
677
        "\u0393": "Gamma",
678
        "\u0394": "Delta",
679
        "\u0395": "Epsilon",
680
        "\u0396": "Zeta",
681
        "\u0397": "Eta",
682
        "\u0398": "Theta",
683
        "\u0399": "Iota",
684
        "\u039A": "Kappa",
685
        "\u039B": "Lambda",
686
        "\u039C": "Mu",
687
        "\u039D": "Nu",
688
        "\u039E": "Xi",
689
        "\u039F": "Omicron",
690
        "\u03A0": "Pi",
691
        "\u03A1": "Rho",
692
        "\u03A3": "Sigma",
693
        "\u03A4": "Tau",
694
        "\u03A5": "Upsilon",
695
        "\u03A6": "Phi",
696
        "\u03A7": "Chi",
697
        "\u03A8": "Psi",
698
        "\u03A9": "Omega",
699
        "\u03B1": "alpha",
700
        "\u03B2": "beta",
701
        "\u03B3": "gamma",
702
        "\u03B4": "delta",
703
        "\u03B5": "epsilon",
704
        "\u03B6": "zeta",
705
        "\u03B7": "eta",
706
        "\u03B8": "theta",
707
        "\u03B9": "iota",
708
        "\u03BA": "kappa",
709
        "\u03BB": "lambda",
710
        "\u03BC": "mu",
711
        "\u03BD": "nu",
712
        "\u03BE": "xi",
713
        "\u03BF": "omicron",
714
        "\u03C0": "pi",
715
        "\u03C1": "rho",
716
        "\u03C3": "sigma",
717
        "\u03C4": "tau",
718
        "\u03C5": "upsilon",
719
        "\u03C6": "phi",
720
        "\u03C7": "chi",
721
        "\u03C8": "psi",
722
        "\u03C9": "omega",
723
    }
724
725
726
__all__ = ["StringTools"]
727