Passed
Push — main ( a79885...50e5ea )
by Douglas
02:25
created

StringTools.subscript()   A

Complexity

Conditions 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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