xulbux 1.5.5__py3-none-any.whl → 1.5.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of xulbux might be problematic. Click here for more details.
- xulbux/__init__.py +28 -18
- xulbux/{__help__.py → _cli_.py} +23 -45
- xulbux/_consts_.py +108 -111
- xulbux/xx_code.py +31 -28
- xulbux/xx_color.py +364 -205
- xulbux/xx_console.py +381 -0
- xulbux/xx_data.py +358 -261
- xulbux/xx_env_path.py +113 -0
- xulbux/xx_file.py +22 -16
- xulbux/xx_format_codes.py +156 -91
- xulbux/xx_json.py +38 -18
- xulbux/xx_path.py +38 -23
- xulbux/xx_regex.py +64 -29
- xulbux/xx_string.py +104 -47
- xulbux/xx_system.py +37 -26
- {xulbux-1.5.5.dist-info → xulbux-1.5.8.dist-info}/METADATA +23 -19
- xulbux-1.5.8.dist-info/RECORD +20 -0
- {xulbux-1.5.5.dist-info → xulbux-1.5.8.dist-info}/WHEEL +1 -1
- xulbux-1.5.8.dist-info/entry_points.txt +3 -0
- xulbux/xx_cmd.py +0 -240
- xulbux/xx_env_vars.py +0 -60
- xulbux-1.5.5.dist-info/RECORD +0 -20
- xulbux-1.5.5.dist-info/entry_points.txt +0 -2
- {xulbux-1.5.5.dist-info → xulbux-1.5.8.dist-info}/licenses/LICENSE +0 -0
xulbux/xx_path.py
CHANGED
|
@@ -5,41 +5,48 @@ import sys as _sys
|
|
|
5
5
|
import os as _os
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
8
|
class Path:
|
|
11
9
|
|
|
12
10
|
@staticmethod
|
|
13
|
-
def get(cwd:bool = False, base_dir:bool = False) -> str|list:
|
|
11
|
+
def get(cwd: bool = False, base_dir: bool = False) -> str | list:
|
|
14
12
|
paths = []
|
|
15
13
|
if cwd:
|
|
16
14
|
paths.append(_os.getcwd())
|
|
17
15
|
if base_dir:
|
|
18
|
-
if getattr(_sys,
|
|
16
|
+
if getattr(_sys, "frozen", False):
|
|
19
17
|
base_path = _os.path.dirname(_sys.executable)
|
|
20
18
|
else:
|
|
21
|
-
main_module = _sys.modules[
|
|
22
|
-
if hasattr(main_module,
|
|
19
|
+
main_module = _sys.modules["__main__"]
|
|
20
|
+
if hasattr(main_module, "__file__"):
|
|
23
21
|
base_path = _os.path.dirname(_os.path.abspath(main_module.__file__))
|
|
24
|
-
elif
|
|
22
|
+
elif (
|
|
23
|
+
hasattr(main_module, "__spec__") and main_module.__spec__ and getattr(main_module.__spec__, "origin", None)
|
|
24
|
+
):
|
|
25
25
|
base_path = _os.path.dirname(_os.path.abspath(main_module.__spec__.origin))
|
|
26
26
|
else:
|
|
27
|
-
raise RuntimeError(
|
|
27
|
+
raise RuntimeError("Can only get base directory if ran from a file.")
|
|
28
28
|
paths.append(base_path)
|
|
29
29
|
return paths[0] if len(paths) == 1 else paths
|
|
30
30
|
|
|
31
31
|
@staticmethod
|
|
32
|
-
def extend(
|
|
33
|
-
|
|
32
|
+
def extend(
|
|
33
|
+
path: str,
|
|
34
|
+
search_in: str | list[str] = None,
|
|
35
|
+
raise_error: bool = False,
|
|
36
|
+
correct_path: bool = False,
|
|
37
|
+
) -> str:
|
|
38
|
+
if path in (None, ""):
|
|
34
39
|
return path
|
|
35
|
-
|
|
40
|
+
|
|
41
|
+
def get_closest_match(dir: str, part: str) -> str | None:
|
|
36
42
|
try:
|
|
37
43
|
files_and_dirs = _os.listdir(dir)
|
|
38
44
|
matches = _difflib.get_close_matches(part, files_and_dirs, n=1, cutoff=0.6)
|
|
39
45
|
return matches[0] if matches else None
|
|
40
|
-
except:
|
|
46
|
+
except Exception:
|
|
41
47
|
return None
|
|
42
|
-
|
|
48
|
+
|
|
49
|
+
def find_path(start: str, parts: list[str]) -> str | None:
|
|
43
50
|
current = start
|
|
44
51
|
for part in parts:
|
|
45
52
|
if _os.path.isfile(current):
|
|
@@ -49,23 +56,30 @@ class Path:
|
|
|
49
56
|
if current is None:
|
|
50
57
|
return None
|
|
51
58
|
return current if _os.path.exists(current) and current != start else None
|
|
52
|
-
|
|
53
|
-
|
|
59
|
+
|
|
60
|
+
def expand_env_path(p: str) -> str:
|
|
61
|
+
if "%" not in p:
|
|
54
62
|
return p
|
|
55
|
-
parts = p.split(
|
|
63
|
+
parts = p.split("%")
|
|
56
64
|
for i in range(1, len(parts), 2):
|
|
57
65
|
if parts[i].upper() in _os.environ:
|
|
58
66
|
parts[i] = _os.environ[parts[i].upper()]
|
|
59
|
-
return
|
|
67
|
+
return "".join(parts)
|
|
68
|
+
|
|
60
69
|
path = _os.path.normpath(expand_env_path(path))
|
|
61
70
|
if _os.path.isabs(path):
|
|
62
71
|
drive, rel_path = _os.path.splitdrive(path)
|
|
63
72
|
rel_path = rel_path.lstrip(_os.sep)
|
|
64
|
-
search_dirs =
|
|
73
|
+
search_dirs = (drive + _os.sep) if drive else [_os.sep]
|
|
65
74
|
else:
|
|
66
75
|
rel_path = path.lstrip(_os.sep)
|
|
67
76
|
base_dir = Path.get(base_dir=True)
|
|
68
|
-
search_dirs =
|
|
77
|
+
search_dirs = (
|
|
78
|
+
_os.getcwd(),
|
|
79
|
+
base_dir,
|
|
80
|
+
_os.path.expanduser("~"),
|
|
81
|
+
_tempfile.gettempdir(),
|
|
82
|
+
)
|
|
69
83
|
if search_in:
|
|
70
84
|
search_dirs.extend([search_in] if isinstance(search_in, str) else search_in)
|
|
71
85
|
path_parts = rel_path.split(_os.sep)
|
|
@@ -74,13 +88,14 @@ class Path:
|
|
|
74
88
|
if _os.path.exists(full_path):
|
|
75
89
|
return full_path
|
|
76
90
|
match = find_path(search_dir, path_parts) if correct_path else None
|
|
77
|
-
if match:
|
|
91
|
+
if match:
|
|
92
|
+
return match
|
|
78
93
|
if raise_error:
|
|
79
|
-
raise FileNotFoundError(f
|
|
94
|
+
raise FileNotFoundError(f"Path '{path}' not found in specified directories.")
|
|
80
95
|
return _os.path.join(search_dirs[0], rel_path)
|
|
81
96
|
|
|
82
97
|
@staticmethod
|
|
83
|
-
def remove(path:str, only_content:bool = False) -> None:
|
|
98
|
+
def remove(path: str, only_content: bool = False) -> None:
|
|
84
99
|
if not _os.path.exists(path):
|
|
85
100
|
return None
|
|
86
101
|
if not only_content:
|
|
@@ -94,4 +109,4 @@ class Path:
|
|
|
94
109
|
elif _os.path.isdir(file_path):
|
|
95
110
|
_shutil.rmtree(file_path)
|
|
96
111
|
except Exception as e:
|
|
97
|
-
raise Exception(f
|
|
112
|
+
raise Exception(f"Failed to delete {file_path}. Reason: {e}")
|
xulbux/xx_regex.py
CHANGED
|
@@ -9,12 +9,9 @@ Really long regex code presets:<br>
|
|
|
9
9
|
`hsla_str` match a HSLA color
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
-
|
|
13
12
|
import re as _re
|
|
14
13
|
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
15
|
class Regex:
|
|
19
16
|
|
|
20
17
|
@staticmethod
|
|
@@ -25,42 +22,54 @@ class Regex:
|
|
|
25
22
|
**`quote`** the quote type (single or double)<br>
|
|
26
23
|
**`string`** everything inside the found quote pair\n
|
|
27
24
|
------------------------------------------------------------------------------------
|
|
28
|
-
**Attention:** Requires non standard library `regex` not standard library `re`!
|
|
25
|
+
**Attention:** Requires non standard library `regex` not standard library `re`!
|
|
26
|
+
"""
|
|
29
27
|
return r'(?P<quote>[\'"])(?P<string>(?:\\.|(?!\g<quote>).)*?)\g<quote>'
|
|
30
28
|
|
|
31
29
|
@staticmethod
|
|
32
|
-
def brackets(bracket1:str =
|
|
30
|
+
def brackets(bracket1: str = "(", bracket2: str = ")", is_group: bool = False) -> str:
|
|
33
31
|
"""Matches everything inside brackets, including other nested brackets.\n
|
|
34
32
|
------------------------------------------------------------------------------------
|
|
35
|
-
**Attention:** Requires non standard library `regex` not standard library `re`!
|
|
36
|
-
|
|
33
|
+
**Attention:** Requires non standard library `regex` not standard library `re`!
|
|
34
|
+
"""
|
|
35
|
+
g, b1, b2 = (
|
|
36
|
+
"" if is_group else "?:",
|
|
37
|
+
_re.escape(bracket1) if len(bracket1) == 1 else bracket1,
|
|
38
|
+
_re.escape(bracket2) if len(bracket2) == 1 else bracket2,
|
|
39
|
+
)
|
|
37
40
|
return rf'{b1}\s*({g}(?:[^{b1}{b2}"\']|"(?:\\.|[^"\\])*"|\'(?:\\.|[^\'\\])*\'|{b1}(?:[^{b1}{b2}"\']|"(?:\\.|[^"\\])*"|\'(?:\\.|[^\'\\])*\'|(?R))*{b2})*)\s*{b2}'
|
|
38
41
|
|
|
39
42
|
@staticmethod
|
|
40
|
-
def outside_strings(pattern:str = r
|
|
43
|
+
def outside_strings(pattern: str = r".*") -> str:
|
|
41
44
|
"""Matches the `pattern` only when it is not found inside a string (`'...'` or `"..."`)."""
|
|
42
45
|
return rf'(?<!["\'])(?:{pattern})(?!["\'])'
|
|
43
46
|
|
|
44
47
|
@staticmethod
|
|
45
|
-
def all_except(
|
|
48
|
+
def all_except(
|
|
49
|
+
disallowed_pattern: str,
|
|
50
|
+
ignore_pattern: str = "",
|
|
51
|
+
is_group: bool = False,
|
|
52
|
+
) -> str:
|
|
46
53
|
"""Matches everything except `disallowed_pattern`, unless the `disallowed_pattern` is found inside a string (`'...'` or `"..."`).\n
|
|
47
54
|
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
48
55
|
The `ignore_pattern` is just always ignored. For example if `disallowed_pattern` is `>` and `ignore_pattern` is `->`, the `->`-arrows will be allowed, even though they have `>` in them.<br>
|
|
49
|
-
If `is_group` is `True`, you will be able to reference the matched content as a group (e.g. <code>match.group(<i>int</i>)</code> or <code>r'\\<i>int</i>'</code>).
|
|
56
|
+
If `is_group` is `True`, you will be able to reference the matched content as a group (e.g. <code>match.group(<i>int</i>)</code> or <code>r'\\<i>int</i>'</code>).
|
|
57
|
+
"""
|
|
50
58
|
return rf'({"" if is_group else "?:"}(?:(?!{ignore_pattern}).)*(?:(?!{Regex.outside_strings(disallowed_pattern)}).)*)'
|
|
51
59
|
|
|
52
60
|
@staticmethod
|
|
53
|
-
def func_call(func_name:str = None) -> str:
|
|
61
|
+
def func_call(func_name: str = None) -> str:
|
|
54
62
|
"""Match a function call<br>
|
|
55
63
|
**`1`** function name<br>
|
|
56
64
|
**`2`** the function's arguments\n
|
|
57
65
|
If no `func_name` is given, it will match any function call.\n
|
|
58
66
|
------------------------------------------------------------------------------------
|
|
59
|
-
**Attention:** Requires non standard library `regex` not standard library `re`!
|
|
60
|
-
|
|
67
|
+
**Attention:** Requires non standard library `regex` not standard library `re`!
|
|
68
|
+
"""
|
|
69
|
+
return r"(?<=\b)(" + (func_name if func_name else r"[\w_]+") + r")\s*" + Regex.brackets("(", ")", is_group=True)
|
|
61
70
|
|
|
62
71
|
@staticmethod
|
|
63
|
-
def rgba_str(fix_sep:str =
|
|
72
|
+
def rgba_str(fix_sep: str = ",", allow_alpha: bool = True) -> str:
|
|
64
73
|
"""Matches an RGBA color inside a string.\n
|
|
65
74
|
--------------------------------------------------------------------------------
|
|
66
75
|
The RGBA color can be in the formats (for `fix_sep = ','`):<br>
|
|
@@ -78,21 +87,25 @@ class Regex:
|
|
|
78
87
|
`a` 0-1 (float: opacity)\n
|
|
79
88
|
--------------------------------------------------------------------------------
|
|
80
89
|
If the `fix_sep` is set to nothing, any char that is not a letter or number<br>
|
|
81
|
-
can be used to separate the
|
|
82
|
-
if fix_sep in (None,
|
|
83
|
-
fix_sep = r
|
|
90
|
+
can be used to separate the RGBA values, including just a space."""
|
|
91
|
+
if fix_sep in (None, ""):
|
|
92
|
+
fix_sep = r"[^0-9A-Z]"
|
|
84
93
|
else:
|
|
85
94
|
fix_sep = _re.escape(fix_sep)
|
|
86
|
-
rgb_part = rf
|
|
95
|
+
rgb_part = rf"""((?:0*(?:25[0-5]|2[0-4][0-9]|1?[0-9]{{1,2}})))
|
|
87
96
|
(?:\s*{fix_sep}\s*)((?:0*(?:25[0-5]|2[0-4][0-9]|1?[0-9]{{1,2}})))
|
|
88
|
-
(?:\s*{fix_sep}\s*)((?:0*(?:25[0-5]|2[0-4][0-9]|1?[0-9]{{1,2}})))
|
|
89
|
-
return
|
|
97
|
+
(?:\s*{fix_sep}\s*)((?:0*(?:25[0-5]|2[0-4][0-9]|1?[0-9]{{1,2}})))"""
|
|
98
|
+
return (
|
|
99
|
+
rf"""(?ix)
|
|
90
100
|
(?:rgb|rgba)?\s*(?:\(?\s*{rgb_part}
|
|
91
101
|
(?:(?:\s*{fix_sep}\s*)((?:0*(?:0?\.[0-9]+|1\.0+|[0-9]+\.[0-9]+|[0-9]+))))?
|
|
92
|
-
\s*\)?)
|
|
102
|
+
\s*\)?)"""
|
|
103
|
+
if allow_alpha
|
|
104
|
+
else rf"(?ix)(?:rgb|rgba)?\s*(?:\(?\s*{rgb_part}\s*\)?)"
|
|
105
|
+
)
|
|
93
106
|
|
|
94
107
|
@staticmethod
|
|
95
|
-
def hsla_str(fix_sep:str =
|
|
108
|
+
def hsla_str(fix_sep: str = ",", allow_alpha: bool = True) -> str:
|
|
96
109
|
"""Matches a HSLA color inside a string.\n
|
|
97
110
|
--------------------------------------------------------------------------------
|
|
98
111
|
The HSLA color can be in the formats (for `fix_sep = ','`):<br>
|
|
@@ -110,15 +123,37 @@ class Regex:
|
|
|
110
123
|
`a` 0-1 (float: opacity)\n
|
|
111
124
|
--------------------------------------------------------------------------------
|
|
112
125
|
If the `fix_sep` is set to nothing, any char that is not a letter or number<br>
|
|
113
|
-
can be used to separate the
|
|
114
|
-
if fix_sep in (None,
|
|
115
|
-
fix_sep = r
|
|
126
|
+
can be used to separate the HSLA values, including just a space."""
|
|
127
|
+
if fix_sep in (None, ""):
|
|
128
|
+
fix_sep = r"[^0-9A-Z]"
|
|
116
129
|
else:
|
|
117
130
|
fix_sep = _re.escape(fix_sep)
|
|
118
|
-
hsl_part = rf
|
|
131
|
+
hsl_part = rf"""((?:0*(?:360|3[0-5][0-9]|[12][0-9][0-9]|[1-9]?[0-9])))
|
|
119
132
|
(?:\s*{fix_sep}\s*)((?:0*(?:100|[1-9][0-9]|[0-9])))
|
|
120
|
-
(?:\s*{fix_sep}\s*)((?:0*(?:100|[1-9][0-9]|[0-9])))
|
|
121
|
-
return
|
|
133
|
+
(?:\s*{fix_sep}\s*)((?:0*(?:100|[1-9][0-9]|[0-9])))"""
|
|
134
|
+
return (
|
|
135
|
+
rf"""(?ix)
|
|
122
136
|
(?:hsl|hsla)?\s*(?:\(?\s*{hsl_part}
|
|
123
137
|
(?:(?:\s*{fix_sep}\s*)((?:0*(?:0?\.[0-9]+|1\.0+|[0-9]+\.[0-9]+|[0-9]+))))?
|
|
124
|
-
\s*\)?)
|
|
138
|
+
\s*\)?)"""
|
|
139
|
+
if allow_alpha
|
|
140
|
+
else rf"(?ix)(?:hsl|hsla)?\s*(?:\(?\s*{hsl_part}\s*\)?)"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
@staticmethod
|
|
144
|
+
def hexa_str(allow_alpha: bool = True) -> str:
|
|
145
|
+
"""Matches a HEXA color inside a string.\n
|
|
146
|
+
--------------------------------------------------------------------------
|
|
147
|
+
The HEXA color can be in the formats (prefix `#`, `0x` or no prefix):<br>
|
|
148
|
+
`RGB`<br>
|
|
149
|
+
`RGBA` (if `allow_alpha=True`)<br>
|
|
150
|
+
`RRGGBB`<br>
|
|
151
|
+
`RRGGBBAA` (if `allow_alpha=True`)\n
|
|
152
|
+
--------------------------------------------------------------------------
|
|
153
|
+
### Valid ranges:<br>
|
|
154
|
+
each channel from 0-9 and A-F (*case insensitive*)"""
|
|
155
|
+
return (
|
|
156
|
+
r"(?i)^(?:#|0x)?[0-9A-F]{8}|[0-9A-F]{6}|[0-9A-F]{4}|[0-9A-F]{3}$"
|
|
157
|
+
if allow_alpha
|
|
158
|
+
else r"(?i)^(?:#|0x)?[0-9A-F]{6}|[0-9A-F]{3}$"
|
|
159
|
+
)
|
xulbux/xx_string.py
CHANGED
|
@@ -1,64 +1,116 @@
|
|
|
1
1
|
import re as _re
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
4
|
class String:
|
|
7
5
|
|
|
8
6
|
@staticmethod
|
|
9
|
-
def to_type(
|
|
7
|
+
def to_type(string: str) -> any:
|
|
10
8
|
"""Will convert a string to a type."""
|
|
11
|
-
if
|
|
12
|
-
return
|
|
13
|
-
elif
|
|
9
|
+
if string.lower() in ("true", "false"): # BOOLEAN
|
|
10
|
+
return string.lower() == "true"
|
|
11
|
+
elif string.lower() in ("none", "null", "undefined"): # NONE
|
|
14
12
|
return None
|
|
15
|
-
elif
|
|
16
|
-
return [String.to_type(item.strip()) for item in
|
|
17
|
-
elif
|
|
18
|
-
return tuple(String.to_type(item.strip()) for item in
|
|
19
|
-
elif
|
|
20
|
-
return {String.to_type(item.strip()) for item in
|
|
21
|
-
elif
|
|
22
|
-
return {
|
|
13
|
+
elif string.startswith("[") and string.endswith("]"): # LIST
|
|
14
|
+
return [String.to_type(item.strip()) for item in string[1:-1].split(",") if item.strip()]
|
|
15
|
+
elif string.startswith("(") and string.endswith(")"): # TUPLE
|
|
16
|
+
return tuple(String.to_type(item.strip()) for item in string[1:-1].split(",") if item.strip())
|
|
17
|
+
elif string.startswith("{") and string.endswith("}"): # SET
|
|
18
|
+
return {String.to_type(item.strip()) for item in string[1:-1].split(",") if item.strip()}
|
|
19
|
+
elif string.startswith("{") and string.endswith("}") and ":" in string: # DICTIONARY
|
|
20
|
+
return {
|
|
21
|
+
String.to_type(k.strip()): String.to_type(v.strip())
|
|
22
|
+
for k, v in (item.split(":") for item in string[1:-1].split(",") if item.strip())
|
|
23
|
+
}
|
|
23
24
|
try: # NUMBER (INT OR FLOAT)
|
|
24
|
-
if
|
|
25
|
-
return float(
|
|
25
|
+
if "." in string or "e" in string.lower():
|
|
26
|
+
return float(string)
|
|
26
27
|
else:
|
|
27
|
-
return int(
|
|
28
|
-
except ValueError:
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
return int(string)
|
|
29
|
+
except ValueError:
|
|
30
|
+
pass
|
|
31
|
+
if string.startswith(("'", '"')) and string.endswith(("'", '"')): # STRING (WITH OR WITHOUT QUOTES)
|
|
32
|
+
return string[1:-1]
|
|
31
33
|
try: # COMPLEX
|
|
32
|
-
return complex(
|
|
33
|
-
except ValueError:
|
|
34
|
-
|
|
34
|
+
return complex(string)
|
|
35
|
+
except ValueError:
|
|
36
|
+
pass
|
|
37
|
+
return string # IF NOTHING ELSE MATCHES, RETURN AS IS
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def normalize_spaces(string: str, tab_spaces: int = 4) -> str:
|
|
41
|
+
"""Replaces all special space characters with normal spaces.<br>
|
|
42
|
+
Also replaces tab characters with `tab_spaces` spaces."""
|
|
43
|
+
return (
|
|
44
|
+
string.replace("\t", " " * tab_spaces)
|
|
45
|
+
.replace("\u2000", " ")
|
|
46
|
+
.replace("\u2001", " ")
|
|
47
|
+
.replace("\u2002", " ")
|
|
48
|
+
.replace("\u2003", " ")
|
|
49
|
+
.replace("\u2004", " ")
|
|
50
|
+
.replace("\u2005", " ")
|
|
51
|
+
.replace("\u2006", " ")
|
|
52
|
+
.replace("\u2007", " ")
|
|
53
|
+
.replace("\u2008", " ")
|
|
54
|
+
.replace("\u2009", " ")
|
|
55
|
+
.replace("\u200A", " ")
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def escape(string: str, str_quotes: str = '"') -> str:
|
|
60
|
+
"""Escapes the special characters and quotes inside a string.\n
|
|
61
|
+
----------------------------------------------------------------------------
|
|
62
|
+
`str_quotes` can be either `"` or `'` and should match the quotes,<br>
|
|
63
|
+
the string will be put inside of. So if your string will be `"string"`,<br>
|
|
64
|
+
you should pass `"` to the parameter `str_quotes`.<br>
|
|
65
|
+
That way, if the string includes the same quotes, they will be escaped."""
|
|
66
|
+
string = (
|
|
67
|
+
string.replace("\\", r"\\")
|
|
68
|
+
.replace("\n", r"\n")
|
|
69
|
+
.replace("\r", r"\r")
|
|
70
|
+
.replace("\t", r"\t")
|
|
71
|
+
.replace("\b", r"\b")
|
|
72
|
+
.replace("\f", r"\f")
|
|
73
|
+
.replace("\a", r"\a")
|
|
74
|
+
)
|
|
75
|
+
if str_quotes == '"':
|
|
76
|
+
string = string.replace(r"\\'", "'").replace(r'"', r"\"")
|
|
77
|
+
elif str_quotes == "'":
|
|
78
|
+
string = string.replace(r'\\"', '"').replace(r"'", r"\'")
|
|
79
|
+
return string
|
|
35
80
|
|
|
36
81
|
@staticmethod
|
|
37
|
-
def
|
|
38
|
-
"""
|
|
39
|
-
If
|
|
40
|
-
|
|
41
|
-
|
|
82
|
+
def is_empty(string: str, spaces_are_empty: bool = False):
|
|
83
|
+
"""Returns `True` if the string is empty and `False` otherwise.<br>
|
|
84
|
+
If `spaces_are_empty` is true, it will also return `True` if the string is only spaces."""
|
|
85
|
+
return (string in (None, "")) or (spaces_are_empty and isinstance(string, str) and not string.strip())
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def single_char_repeats(string: str, char: str) -> int | bool:
|
|
89
|
+
"""If the string consists of only the same `char`, it returns the number of times it is present.<br>
|
|
90
|
+
If the string doesn't consist of only the same character, it returns `False`.
|
|
91
|
+
"""
|
|
92
|
+
if len(string) == len(char) * string.count(char):
|
|
93
|
+
return string.count(char)
|
|
42
94
|
else:
|
|
43
95
|
return False
|
|
44
96
|
|
|
45
97
|
@staticmethod
|
|
46
|
-
def decompose(case_string:str, seps:str =
|
|
98
|
+
def decompose(case_string: str, seps: str = "-_", lower_all: bool = True) -> list[str]:
|
|
47
99
|
"""Will decompose the string (*any type of casing, also mixed*) into parts."""
|
|
48
|
-
return [(part.lower() if lower_all else part) for part in _re.split(rf
|
|
100
|
+
return [(part.lower() if lower_all else part) for part in _re.split(rf"(?<=[a-z])(?=[A-Z])|[{seps}]", case_string)]
|
|
49
101
|
|
|
50
102
|
@staticmethod
|
|
51
|
-
def to_camel_case(string:str) -> str:
|
|
103
|
+
def to_camel_case(string: str) -> str:
|
|
52
104
|
"""Will convert the string of any type of casing to camel case."""
|
|
53
|
-
return
|
|
105
|
+
return "".join(part.capitalize() for part in String.decompose(string))
|
|
54
106
|
|
|
55
107
|
@staticmethod
|
|
56
|
-
def to_snake_case(string:str, sep:str =
|
|
108
|
+
def to_snake_case(string: str, sep: str = "_", screaming: bool = False) -> str:
|
|
57
109
|
"""Will convert the string of any type of casing to snake case."""
|
|
58
110
|
return sep.join(part.upper() if screaming else part for part in String.decompose(string))
|
|
59
111
|
|
|
60
112
|
@staticmethod
|
|
61
|
-
def get_string_lines(string:str, remove_empty_lines:bool = False) -> list[str]:
|
|
113
|
+
def get_string_lines(string: str, remove_empty_lines: bool = False) -> list[str]:
|
|
62
114
|
"""Will split the string into lines."""
|
|
63
115
|
if not remove_empty_lines:
|
|
64
116
|
return string.splitlines()
|
|
@@ -71,46 +123,51 @@ class String:
|
|
|
71
123
|
return non_empty_lines
|
|
72
124
|
|
|
73
125
|
@staticmethod
|
|
74
|
-
def remove_consecutive_empty_lines(string:str, max_consecutive:int = 0) -> str:
|
|
126
|
+
def remove_consecutive_empty_lines(string: str, max_consecutive: int = 0) -> str:
|
|
75
127
|
"""Will remove consecutive empty lines from the string.\n
|
|
76
128
|
----------------------------------------------------------------------------------------------
|
|
77
129
|
If `max_consecutive` is `0`, it will remove all consecutive empty lines.<br>
|
|
78
130
|
If `max_consecutive` is bigger than `0`, it will only allow `max_consecutive` consecutive<br>
|
|
79
|
-
empty lines and everything above it will be cut down to `max_consecutive` empty lines.
|
|
80
|
-
|
|
131
|
+
empty lines and everything above it will be cut down to `max_consecutive` empty lines.
|
|
132
|
+
"""
|
|
133
|
+
return _re.sub(r"(\n\s*){2,}", r"\1" * (max_consecutive + 1), string)
|
|
81
134
|
|
|
82
135
|
@staticmethod
|
|
83
|
-
def
|
|
84
|
-
"""Will split the string every `
|
|
85
|
-
return [string[i:i +
|
|
136
|
+
def split_count(string: str, count: int) -> list[str]:
|
|
137
|
+
"""Will split the string every `count` characters."""
|
|
138
|
+
return [string[i : i + count] for i in range(0, len(string), count)]
|
|
86
139
|
|
|
87
140
|
@staticmethod
|
|
88
|
-
def multi_strip(string:str, strip_chars:str =
|
|
141
|
+
def multi_strip(string: str, strip_chars: str = " _-") -> str:
|
|
89
142
|
"""Will remove all leading and trailing `strip_chars` from the string."""
|
|
90
143
|
for char in string:
|
|
91
144
|
if char in strip_chars:
|
|
92
145
|
string = string[1:]
|
|
93
|
-
else:
|
|
146
|
+
else:
|
|
147
|
+
break
|
|
94
148
|
for char in string[::-1]:
|
|
95
149
|
if char in strip_chars:
|
|
96
150
|
string = string[:-1]
|
|
97
|
-
else:
|
|
151
|
+
else:
|
|
152
|
+
break
|
|
98
153
|
return string
|
|
99
154
|
|
|
100
155
|
@staticmethod
|
|
101
|
-
def multi_lstrip(string:str, strip_chars:str =
|
|
156
|
+
def multi_lstrip(string: str, strip_chars: str = " _-") -> str:
|
|
102
157
|
"""Will remove all leading `strip_chars` from the string."""
|
|
103
158
|
for char in string:
|
|
104
159
|
if char in strip_chars:
|
|
105
160
|
string = string[1:]
|
|
106
|
-
else:
|
|
161
|
+
else:
|
|
162
|
+
break
|
|
107
163
|
return string
|
|
108
164
|
|
|
109
165
|
@staticmethod
|
|
110
|
-
def multi_rstrip(string:str, strip_chars:str =
|
|
166
|
+
def multi_rstrip(string: str, strip_chars: str = " _-") -> str:
|
|
111
167
|
"""Will remove all trailing `strip_chars` from the string."""
|
|
112
168
|
for char in string[::-1]:
|
|
113
169
|
if char in strip_chars:
|
|
114
170
|
string = string[:-1]
|
|
115
|
-
else:
|
|
171
|
+
else:
|
|
172
|
+
break
|
|
116
173
|
return string
|
xulbux/xx_system.py
CHANGED
|
@@ -5,71 +5,82 @@ import sys as _sys
|
|
|
5
5
|
import os as _os
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
8
|
class System:
|
|
11
9
|
|
|
12
10
|
@staticmethod
|
|
13
|
-
def restart(
|
|
11
|
+
def restart(
|
|
12
|
+
prompt: object = None,
|
|
13
|
+
wait: int = 0,
|
|
14
|
+
continue_program: bool = False,
|
|
15
|
+
force: bool = False,
|
|
16
|
+
) -> None:
|
|
14
17
|
"""Starts a system restart:<br>
|
|
15
18
|
`prompt` is the message to be displayed in the systems restart notification.<br>
|
|
16
19
|
`wait` is the time to wait until restarting in seconds.<br>
|
|
17
20
|
`continue_program` is whether to continue the current Python program after calling this function.<br>
|
|
18
|
-
`force` is whether to force a restart even if other processes are still running.
|
|
21
|
+
`force` is whether to force a restart even if other processes are still running.
|
|
22
|
+
"""
|
|
19
23
|
system = _platform.system().lower()
|
|
20
|
-
if system ==
|
|
24
|
+
if system == "windows":
|
|
21
25
|
if not force:
|
|
22
|
-
output = _subprocess.check_output(
|
|
26
|
+
output = _subprocess.check_output("tasklist", shell=True).decode()
|
|
23
27
|
processes = [line.split()[0] for line in output.splitlines()[3:] if line.strip()]
|
|
24
28
|
if len(processes) > 2: # EXCLUDING THE PYTHON PROCESS AND CMD
|
|
25
|
-
raise RuntimeError(
|
|
29
|
+
raise RuntimeError("Processes are still running. Use the parameter `force=True` to restart anyway.")
|
|
26
30
|
if prompt:
|
|
27
31
|
_os.system(f'shutdown /r /t {wait} /c "{prompt}"')
|
|
28
32
|
else:
|
|
29
|
-
_os.system(
|
|
33
|
+
_os.system("shutdown /r /t 0")
|
|
30
34
|
if continue_program:
|
|
31
|
-
print(f
|
|
35
|
+
print(f"Restarting in {wait} seconds...")
|
|
32
36
|
_time.sleep(wait)
|
|
33
|
-
elif system in
|
|
37
|
+
elif system in ("linux", "darwin"):
|
|
34
38
|
if not force:
|
|
35
|
-
output = _subprocess.check_output([
|
|
39
|
+
output = _subprocess.check_output(["ps", "-A"]).decode()
|
|
36
40
|
processes = output.splitlines()[1:] # EXCLUDE HEADER
|
|
37
41
|
if len(processes) > 2: # EXCLUDING THE PYTHON PROCESS AND PS
|
|
38
|
-
raise RuntimeError(
|
|
42
|
+
raise RuntimeError("Processes are still running. Use the parameter `force=True` to restart anyway.")
|
|
39
43
|
if prompt:
|
|
40
|
-
_subprocess.Popen([
|
|
44
|
+
_subprocess.Popen(["notify-send", "System Restart", prompt])
|
|
41
45
|
_time.sleep(wait)
|
|
42
46
|
try:
|
|
43
|
-
_subprocess.run([
|
|
47
|
+
_subprocess.run(["sudo", "shutdown", "-r", "now"])
|
|
44
48
|
except _subprocess.CalledProcessError:
|
|
45
|
-
raise PermissionError(
|
|
49
|
+
raise PermissionError("Failed to restart: insufficient privileges. Ensure sudo permissions are granted.")
|
|
46
50
|
if continue_program:
|
|
47
|
-
print(f
|
|
51
|
+
print(f"Restarting in {wait} seconds...")
|
|
48
52
|
_time.sleep(wait)
|
|
49
53
|
else:
|
|
50
|
-
raise NotImplementedError(f
|
|
54
|
+
raise NotImplementedError(f"Restart not implemented for `{system}`")
|
|
51
55
|
|
|
52
56
|
@staticmethod
|
|
53
|
-
def check_libs(
|
|
57
|
+
def check_libs(
|
|
58
|
+
lib_names: list[str],
|
|
59
|
+
install_missing: bool = False,
|
|
60
|
+
confirm_install: bool = True,
|
|
61
|
+
) -> None | list[str]:
|
|
54
62
|
"""Checks if the given list of libraries are installed. If not:
|
|
55
63
|
- If `install_missing` is `False` the missing libraries will be returned as a list.
|
|
56
|
-
- If `install_missing` is `True` the missing libraries will be installed. If `confirm_install` is `True` the user will first be asked if they want to install the missing libraries.
|
|
64
|
+
- If `install_missing` is `True` the missing libraries will be installed. If `confirm_install` is `True` the user will first be asked if they want to install the missing libraries.
|
|
65
|
+
"""
|
|
57
66
|
missing = []
|
|
58
67
|
for lib in lib_names:
|
|
59
|
-
try:
|
|
60
|
-
|
|
68
|
+
try:
|
|
69
|
+
__import__(lib)
|
|
70
|
+
except ImportError:
|
|
71
|
+
missing.append(lib)
|
|
61
72
|
if not missing:
|
|
62
73
|
return None
|
|
63
74
|
elif not install_missing:
|
|
64
75
|
return missing
|
|
65
76
|
if confirm_install:
|
|
66
|
-
print(
|
|
77
|
+
print("The following required libraries are missing:")
|
|
67
78
|
for lib in missing:
|
|
68
|
-
print(f
|
|
69
|
-
if input(
|
|
70
|
-
raise ImportError(
|
|
79
|
+
print(f"- {lib}")
|
|
80
|
+
if input("Do you want to install them now (Y/n): ").strip().lower() not in ("", "y", "yes"):
|
|
81
|
+
raise ImportError("Missing required libraries.")
|
|
71
82
|
try:
|
|
72
|
-
_subprocess.check_call([_sys.executable,
|
|
83
|
+
_subprocess.check_call([_sys.executable, "-m", "pip", "install"] + missing)
|
|
73
84
|
return None
|
|
74
85
|
except _subprocess.CalledProcessError:
|
|
75
86
|
return missing
|