xulbux 1.6.0__py3-none-any.whl → 1.6.2__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/xx_format_codes.py CHANGED
@@ -1,56 +1,74 @@
1
1
  """
2
- Functions to be able to use special (easy) formatting codes directly inside some message (string).<br>
2
+ Functions to be able to use special (easy) formatting codes directly inside some message (string).\n
3
3
  These codes, when used within following functions, will change the look of log within the console:
4
- - `FormatCodes.print()` (*print a special format-codes containing string*)
5
- - `FormatCodes.input()` (*input with a special format-codes containing prompt*)
6
- - `FormatCodes.to_ansi()` (*transform all special format-codes into ANSI codes in a string*)\n
4
+ - `FormatCodes.print()` (print a special format-codes containing string)
5
+ - `FormatCodes.input()` (input with a special format-codes containing prompt)
6
+ - `FormatCodes.to_ansi()` (transform all special format-codes into ANSI codes in a string)\n
7
7
  --------------------------------------------------------------------------------------------------------------------
8
- How to change the text format and color?<br>
9
- **Example string with formatting codes:**<br>
10
- > `[bold]This is bold text, [#F08]which is pink now [black|BG:#FF0088] and now it changed`<br>
11
- > `to black with a pink background. [_]And this is the boring text, where everything is reset.`\n
12
- ⇾ **Instead of writing the formats all separate** `[][…][…]` **you can join them like this** `[…|…|…]`\n
8
+ How to change the text format and color?\n
9
+ Example string with formatting codes:
10
+ ```string
11
+ [bold]This is bold text, [#F08]which is pink now [black|BG:#FF0088] and now it changed`
12
+ to black with a pink background. [_]And this is the boring text, where everything is reset.
13
+ ```
14
+ ⇾ Instead of writing the formats all separate `[x][y][z]` you can join them like this `[x|y|z]`\n
13
15
  --------------------------------------------------------------------------------------------------------------------
14
- You can also automatically reset a certain format, behind text like shown in the following example:<br>
15
- > `This is normal text [b](which is bold now) but now it was automatically reset to normal.`\n
16
- This will only reset formats, that have a reset listed below. Colors and BG-colors won't be reset.<br>
17
- This is what will happen, if you use it with a color-format:<br>
18
- > `[cyan]This is cyan text [b](which is bold now.) Now it's not bold any more but still cyan.`\n
19
- If you want to ignore the `()` brackets you can put a `\\` or `/` between:<br>
20
- > `[cyan]This is cyan text [b]/(which is bold now.) And now it is still bold and cyan.`\n
21
- ⇾ **To see these examples in action, you can put them into the** `FormatCodes.print()` **function.**\n
16
+ You can also automatically reset a certain format, behind text like shown in the following example:
17
+ ```string
18
+ This is normal text [b](which is bold now) but now it was automatically reset to normal.
19
+ ```
20
+ This will only reset formats, that have a reset listed below. Colors and BG-colors won't be reset.\n
21
+ This is what will happen, if you use it with a color-format:
22
+ ```string
23
+ [cyan]This is cyan text [b](which is bold now.) Now it's not bold any more but still cyan.
24
+ ```
25
+ If you want to ignore the `()` brackets you can put a `\\` or `/` between:
26
+ ```string
27
+ [cyan]This is cyan text [b]/(which is bold now.) And now it is still bold and cyan.
28
+ ```
29
+ ⇾ To see these examples in action, you can put them into the `FormatCodes.print()` function.\n
22
30
  --------------------------------------------------------------------------------------------------------------------
23
- **All possible formatting codes:**
24
- - HEX colors: `[#F08]` or `[#FF0088]` (*with or without leading #*)
31
+ All possible formatting codes:
32
+ - HEX colors: `[#F08]` or `[#FF0088]` (with or without leading #)
25
33
  - RGB colors: `[rgb(255, 0, 136)]`
26
- - bright colors: `[bright:#F08]`
27
34
  - background colors: `[BG:#F08]`
28
35
  - standard cmd colors:
29
- - `[black]`
30
- - `[red]`
31
- - `[green]`
32
- - `[yellow]`
33
- - `[blue]`
34
- - `[magenta]`
35
- - `[cyan]`
36
- - `[white]`
36
+ - `[black]`
37
+ - `[red]`
38
+ - `[green]`
39
+ - `[yellow]`
40
+ - `[blue]`
41
+ - `[magenta]`
42
+ - `[cyan]`
43
+ - `[white]`
37
44
  - bright cmd colors: `[bright:black]` or `[br:black]`, `[bright:red]` or `[br:red]`, ...
38
45
  - background cmd colors: `[BG:black]`, `[BG:red]`, ...
39
- - bright background cmd colors: `[BG:bright:black]` or `[BG:br:black]`, `[BG:bright:red]` or `[BG:br:red]`, ...<br>
40
- ⇾ **The order of** `BG:` **and** `bright:` or `br:` **does not matter.**
46
+ - bright background cmd colors: `[BG:bright:black]` or `[BG:br:black]`, `[BG:bright:red]` or `[BG:br:red]`, ...\n
47
+ ⇾ The order of `BG:` and `bright:` or `br:` does not matter.
41
48
  - text formats:
42
- - `[bold]` or `[b]`
43
- - `[dim]`
44
- - `[italic]` or `[i]`
45
- - `[underline]` or `[u]`
46
- - `[inverse]`, `[invert]` or `[in]`
47
- - `[hidden]`, `[hide]` or `[h]`
48
- - `[strikethrough]` or `[s]`
49
- - `[double-underline]` or `[du]`
50
- - specific reset: `[_bold]` or `[_b]`, `[_dim]`, ... or `[_color]` or `[_c]`, `[_background]` or `[_bg]`
51
- - total reset: `[_]` (only if no `default_color` is set, otherwise see **↓** )
49
+ - `[bold]` or `[b]`
50
+ - `[dim]`
51
+ - `[italic]` or `[i]`
52
+ - `[underline]` or `[u]`
53
+ - `[inverse]`, `[invert]` or `[in]`
54
+ - `[hidden]`, `[hide]` or `[h]`
55
+ - `[strikethrough]` or `[s]`
56
+ - `[double-underline]` or `[du]`
57
+ - specific reset:
58
+ - `[_bold]` or `[_b]`
59
+ - `[_dim]`
60
+ - `[_italic]` or `[_i]`
61
+ - `[_underline]` or `[_u]`
62
+ - `[_inverse]`, `[_invert]` or `[_in]`
63
+ - `[_hidden]`, `[_hide]` or `[_h]`
64
+ - `[_strikethrough]` or `[_s]`
65
+ - `[_double-underline]` or `[_du]`
66
+ - `[_color]` or `[_c]`
67
+ - `[_background]` or `[_bg]`
68
+ - total reset:
69
+ - `[_]`
52
70
  --------------------------------------------------------------------------------------------------------------------
53
- **Special formatting when param `default_color` is set to a color:**
71
+ Additional formats when a `default_color` is set:
54
72
  - `[*]` will reset everything, just like `[_]`, but the text-color will remain in `default_color`
55
73
  - `[*color]` will reset the text-color, just like `[_color]`, but then also make it `default_color`
56
74
  - `[default]` will just color the text in `default_color`,
@@ -78,20 +96,32 @@ import regex as _rx
78
96
  import sys as _sys
79
97
  import re as _re
80
98
 
81
-
99
+ PREFIX = {
100
+ "BG": {"background", "bg"},
101
+ "BR": {"bright", "br"},
102
+ }
103
+ PREFIXES = {val for values in PREFIX.values() for val in values}
104
+ PREFIX_RX = {
105
+ "BG": rf"(?:{'|'.join(PREFIX['BG'])})\s*:",
106
+ "BR": rf"(?:{'|'.join(PREFIX['BR'])})\s*:",
107
+ }
82
108
  COMPILED = { # PRECOMPILE REGULAR EXPRESSIONS
83
109
  "*": _re.compile(r"\[\s*([^]_]*?)\s*\*\s*([^]_]*?)\]"),
84
110
  "*color": _re.compile(r"\[\s*([^]_]*?)\s*\*color\s*([^]_]*?)\]"),
85
111
  "format": _rx.compile(
86
112
  Regex.brackets("[", "]", is_group=True) + r"(?:\s*([/\\]?)\s*" + Regex.brackets("(", ")", is_group=True) + r")?"
87
113
  ),
88
- "bg?_default": _re.compile(r"(?i)((?:BG\s*:)?)\s*default"),
89
- "bg_default": _re.compile(r"(?i)BG\s*:\s*default"),
114
+ "bg?_default": _re.compile(r"(?i)((?:" + PREFIX_RX["BG"] + r")?)\s*default"),
115
+ "bg_default": _re.compile(r"(?i)" + PREFIX_RX["BG"] + r"\s*default"),
90
116
  "modifier": _re.compile(
91
- rf'(?i)((?:BG\s*:)?)\s*({"|".join([f"{_re.escape(m)}+" for m in ANSI.modifier["lighten"] + ANSI.modifier["darken"]])})$'
117
+ r"(?i)((?:BG\s*:)?)\s*("
118
+ + "|".join([f"{_re.escape(m)}+" for m in ANSI.modifier["lighten"] + ANSI.modifier["darken"]])
119
+ + r")$"
92
120
  ),
93
- "rgb": _re.compile(r"(?i)^\s*(BG\s*:)?\s*(?:rgb|rgba)?\s*\(?\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)?\s*$"),
94
- "hex": _re.compile(r"(?i)^\s*(BG\s*:)?\s*(?:#|0x)?([0-9A-F]{6}|[0-9A-F]{3})\s*$"),
121
+ "rgb": _re.compile(
122
+ r"(?i)^\s*(" + PREFIX_RX["BG"] + r")?\s*(?:rgb|rgba)?\s*\(?\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)?\s*$"
123
+ ),
124
+ "hex": _re.compile(r"(?i)^\s*(" + PREFIX_RX["BG"] + r")?\s*(?:#|0x)?([0-9A-F]{6}|[0-9A-F]{3})\s*$"),
95
125
  }
96
126
 
97
127
 
@@ -106,6 +136,10 @@ class FormatCodes:
106
136
  end: str = "\n",
107
137
  flush: bool = True,
108
138
  ) -> None:
139
+ """Print a string that can be formatted using special formatting codes.\n
140
+ --------------------------------------------------------------------------
141
+ For exact information about how to use special formatting codes, see the
142
+ `xx_format_codes` module documentation."""
109
143
  FormatCodes.__config_console()
110
144
  _sys.stdout.write(FormatCodes.to_ansi(sep.join(map(str, values)) + end, default_color, brightness_steps))
111
145
  if flush:
@@ -116,16 +150,29 @@ class FormatCodes:
116
150
  prompt: object = "",
117
151
  default_color: hexa | rgba = None,
118
152
  brightness_steps: int = 20,
153
+ reset_ansi: bool = False,
119
154
  ) -> str:
155
+ """An input, which's prompt can be formatted using special formatting codes.\n
156
+ -------------------------------------------------------------------------------
157
+ If `reset_ansi` is true, all ANSI formatting will be reset, after the user has
158
+ confirmed the input and the program continues.\n
159
+ -------------------------------------------------------------------------------
160
+ For exact information about how to use special formatting codes, see the
161
+ `xx_format_codes` module documentation."""
120
162
  FormatCodes.__config_console()
121
- return input(FormatCodes.to_ansi(prompt, default_color, brightness_steps))
163
+ user_input = input(FormatCodes.to_ansi(prompt, default_color, brightness_steps))
164
+ if reset_ansi:
165
+ _sys.stdout.write("\x1b[0m")
166
+ return user_input
122
167
 
123
168
  @staticmethod
124
169
  def to_ansi(
125
170
  string: str, default_color: hexa | rgba = None, brightness_steps: int = 20, _default_start: bool = True
126
171
  ) -> str:
127
- result, bg_kwd, color_pref = string, {"bg"}, {"br", "bright"}
128
-
172
+ """Convert the special formatting codes inside a string to printable ANSI codes.\n
173
+ -----------------------------------------------------------------------------------
174
+ For exact information about how to use special formatting codes, see the
175
+ `xx_format_codes` module documentation."""
129
176
  if Color.is_valid_rgba(default_color, False):
130
177
  use_default = True
131
178
  elif Color.is_valid_hexa(default_color, False):
@@ -158,10 +205,9 @@ class FormatCodes:
158
205
  reset_keys = []
159
206
  for k in format_keys:
160
207
  k_lower = k.lower()
161
- k_parts = k_lower.split(":")
162
- k_set = set(k_parts)
163
- if bg_kwd & k_set and len(k_parts) <= 3:
164
- if k_set & color_pref:
208
+ k_set = set(k_lower.split(":"))
209
+ if PREFIX["BG"] & k_set and len(k_set) <= 3:
210
+ if k_set & PREFIX["BR"]:
165
211
  for i in range(len(k)):
166
212
  if is_valid_color(k[i:]):
167
213
  reset_keys.extend(["_bg", "_color"])
@@ -173,7 +219,7 @@ class FormatCodes:
173
219
  break
174
220
  elif is_valid_color(k) or any(
175
221
  k_lower.startswith(pref_colon := f"{prefix}:") and is_valid_color(k[len(pref_colon) :])
176
- for prefix in color_pref
222
+ for prefix in PREFIX["BR"]
177
223
  ):
178
224
  reset_keys.append("_color")
179
225
  else:
@@ -201,8 +247,8 @@ class FormatCodes:
201
247
  + ("" if escaped else "".join(ansi_resets))
202
248
  )
203
249
 
204
- result = "\n".join(COMPILED["format"].sub(replace_keys, line) for line in string.split("\n"))
205
- return (FormatCodes.__get_default_ansi(default_color) if _default_start else "") + result if use_default else result
250
+ string = "\n".join(COMPILED["format"].sub(replace_keys, line) for line in string.split("\n"))
251
+ return (FormatCodes.__get_default_ansi(default_color) if _default_start else "") + string if use_default else string
206
252
 
207
253
  @staticmethod
208
254
  def escape_ansi(ansi_string: str, escaped_char: str = ANSI.char_esc) -> str:
@@ -212,6 +258,7 @@ class FormatCodes:
212
258
  @staticmethod
213
259
  @lru_cache(maxsize=64)
214
260
  def __config_console() -> None:
261
+ """Configure the console to be able to interpret ANSI formatting."""
215
262
  _sys.stdout.flush()
216
263
  kernel32 = _ctypes.windll.kernel32
217
264
  h = kernel32.GetStdHandle(-11)
@@ -226,6 +273,7 @@ class FormatCodes:
226
273
  brightness_steps: int = None,
227
274
  _modifiers: tuple[str, str] = (ANSI.modifier["lighten"], ANSI.modifier["darken"]),
228
275
  ) -> str | None:
276
+ """Get the `default_color` and lighter/darker versions of it in ANSI format."""
229
277
  if not brightness_steps or (format_key and COMPILED["bg?_default"].search(format_key)):
230
278
  return (ANSI.seq_bg_color if format_key and COMPILED["bg_default"].search(format_key) else ANSI.seq_color).format(
231
279
  *default_color[:3]
@@ -252,15 +300,11 @@ class FormatCodes:
252
300
 
253
301
  @staticmethod
254
302
  def __get_replacement(format_key: str, default_color: rgba = None, brightness_steps: int = 20) -> str:
255
- """Gives you the corresponding ANSI code for the given format key.<br>
256
- If `default_color` is not `None`, the text color will be `default_color` if all formats<br>
303
+ """Gives you the corresponding ANSI code for the given format key.
304
+ If `default_color` is not `None`, the text color will be `default_color` if all formats
257
305
  are reset or you can get lighter or darker version of `default_color` (also as BG)"""
258
306
  use_default = default_color and Color.is_valid_rgba(default_color, False)
259
- _format_key, format_key = format_key, ( # NORMALIZE THE FORMAT KEY (+ SAVE ORIGINAL)
260
- "bg:" if "bg" in (parts := format_key.replace(" ", "").lower().split(":")) else ""
261
- ) + ("bright:" if any(x in parts for x in ("bright", "br")) else "") + ":".join(
262
- p for p in parts if p not in ("bg", "bright", "br")
263
- )
307
+ _format_key, format_key = format_key, FormatCodes.__normalize_key(format_key) # NORMALIZE KEY AND SAVE ORIGINAL
264
308
  if use_default:
265
309
  if new_default_color := FormatCodes.__get_default_ansi(default_color, format_key, brightness_steps):
266
310
  return new_default_color
@@ -295,3 +339,17 @@ class FormatCodes:
295
339
  except Exception:
296
340
  pass
297
341
  return _format_key
342
+
343
+ @staticmethod
344
+ @lru_cache(maxsize=64)
345
+ def __normalize_key(format_key: str) -> str:
346
+ """Normalizes the given format key."""
347
+ k_parts = format_key.replace(" ", "").lower().split(":")
348
+ prefix_str = "".join(
349
+ f"{prefix_key.lower()}:"
350
+ for prefix_key, prefix_values in PREFIX.items()
351
+ if any(k_part in prefix_values for k_part in k_parts)
352
+ )
353
+ return prefix_str + ":".join(
354
+ part for part in k_parts if part not in {val for values in PREFIX.values() for val in values}
355
+ )
xulbux/xx_json.py CHANGED
@@ -16,13 +16,15 @@ class Json:
16
16
  ) -> dict | tuple[dict, dict]:
17
17
  """Read JSON files, ignoring comments.\n
18
18
  -------------------------------------------------------------------------
19
- If only `comment_start` is found at the beginning of an item,<br>
20
- the whole item is counted as a comment and therefore ignored.<br>
21
- If `comment_start` and `comment_end` are found inside an item,<br>
22
- the the section from `comment_start` to `comment_end` is ignored.<br>
23
- If `return_original` is set to `True`, the original JSON is returned<br>
19
+ If only `comment_start` is found at the beginning of an item,
20
+ the whole item is counted as a comment and therefore ignored.
21
+ If `comment_start` and `comment_end` are found inside an item,
22
+ the the section from `comment_start` to `comment_end` is ignored.
23
+ If `return_original` is set to `True`, the original JSON is returned
24
24
  additionally. (returns: `[processed_json, original_json]`)"""
25
- file_path = File.make_path(json_file, "json", prefer_base_dir=True)
25
+ if not json_file.endswith(".json"):
26
+ json_file += ".json"
27
+ file_path = File.extend_or_make_path(json_file, prefer_base_dir=True)
26
28
  with open(file_path, "r") as f:
27
29
  content = f.read()
28
30
  try:
@@ -42,7 +44,9 @@ class Json:
42
44
  compactness: int = 1,
43
45
  force: bool = False,
44
46
  ) -> str:
45
- file_path = File.make_path(new_file, "json", prefer_base_dir=True)
47
+ if not new_file.endswith(".json"):
48
+ new_file += ".json"
49
+ file_path = File.extend_or_make_path(new_file, prefer_base_dir=True)
46
50
  if _os.path.exists(file_path) and not force:
47
51
  with open(file_path, "r", encoding="utf-8") as existing_f:
48
52
  existing_content = _json.load(existing_f)
@@ -63,27 +67,28 @@ class Json:
63
67
  sep: tuple[str, str] = ("->", "::"),
64
68
  ) -> None:
65
69
  """Function to easily update single/multiple values inside JSON files.\n
66
- --------------------------------------------------------------------------------------------------------
70
+ ------------------------------------------------------------------------------------------------------
67
71
  The param `json_file` is the path to the JSON file or just the name of the JSON file to be updated.\n
68
- --------------------------------------------------------------------------------------------------------
69
- The param `update_values` is a sort of path (or a list of paths) to the value/s to be updated, with<br>
70
- the new value at the end of the path.<br>
72
+ ------------------------------------------------------------------------------------------------------
73
+ The param `update_values` is a sort of path (or a list of paths) to the value/s to be updated, with
74
+ the new value at the end of the path.\n
71
75
  In this example:
72
- ```\n {
76
+ ```python
77
+ {
73
78
  'healthy': {
74
79
  'fruit': ['apples', 'bananas', 'oranges'],
75
80
  'vegetables': ['carrots', 'broccoli', 'celery']
76
81
  }
77
- }\n```
78
- ... if you want to change the value of `'apples'` to `'strawberries'`, `update_values` would<br>
79
- be `healthy->fruit->apples::strawberries` or if you don't know that the value to update<br>
80
- is `apples` you can also use the position of the value, so `healthy->fruit->0::strawberries`.\n
81
- ⇾ **If the path from `update_values` doesn't exist, it will be created.**\n
82
- --------------------------------------------------------------------------------------------------------
83
- If only `comment_start` is found at the beginning of an item, the whole item is counted<br>
84
- as a comment and therefore ignored. If `comment_start` and `comment_end` are found<br>
85
- inside an item, the the section from `comment_start` to `comment_end` is ignored.
86
- """
82
+ }
83
+ ```
84
+ ... if you want to change the value of `'apples'` to `'strawberries'`, `update_values` would be
85
+ `healthy->fruit->apples::strawberries` or if you don't know that the value to update is `apples` you
86
+ can also use the position of the value, so `healthy->fruit->0::strawberries`.\n
87
+ ⇾ If the path from `update_values` doesn't exist, it will be created.\n
88
+ ------------------------------------------------------------------------------------------------------
89
+ If only `comment_start` is found at the beginning of an item, the whole item is counted as a comment
90
+ and therefore ignored. If `comment_start` and `comment_end` are found inside an item, the the section
91
+ from `comment_start` to `comment_end` is ignored."""
87
92
  if isinstance(update_values, str):
88
93
  update_values = [update_values]
89
94
  valid_entries = [
xulbux/xx_path.py CHANGED
@@ -8,7 +8,7 @@ import os as _os
8
8
  class Path:
9
9
 
10
10
  @staticmethod
11
- def get(cwd: bool = False, base_dir: bool = False) -> str | list:
11
+ def get(cwd: bool = False, base_dir: bool = False) -> str | list[str]:
12
12
  paths = []
13
13
  if cwd:
14
14
  paths.append(_os.getcwd())
@@ -29,12 +29,7 @@ class Path:
29
29
  return paths[0] if len(paths) == 1 else paths
30
30
 
31
31
  @staticmethod
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:
32
+ def extend(path: str, search_in: str | list[str] = None, raise_error: bool = False, correct_path: bool = False) -> str:
38
33
  if path in (None, ""):
39
34
  return path
40
35
 
xulbux/xx_regex.py CHANGED
@@ -1,11 +1,11 @@
1
1
  """
2
- Really long regex code presets:<br>
3
- `quotes` match everything inside quotes<br>
4
- `brackets` match everything inside brackets<br>
5
- `outside_strings` match the pattern but not inside strings<br>
6
- `all_except` match everything except a certain pattern<br>
7
- `func_call` match a function call<br>
8
- `rgba_str` match an RGBA color<br>
2
+ Really long regex code presets:
3
+ `quotes` match everything inside quotes
4
+ `brackets` match everything inside brackets
5
+ `outside_strings` match the pattern but not inside strings
6
+ `all_except` match everything except a certain pattern
7
+ `func_call` match a function call
8
+ `rgba_str` match an RGBA color
9
9
  `hsla_str` match a HSLA color
10
10
  """
11
11
 
@@ -16,20 +16,20 @@ class Regex:
16
16
 
17
17
  @staticmethod
18
18
  def quotes() -> str:
19
- """Matches everything inside quotes. (Strings)\n
19
+ """Matches everything inside quotes. (strings)\n
20
20
  ------------------------------------------------------------------------------------
21
- Will create two named groups:<br>
22
- **`quote`** the quote type (single or double)<br>
23
- **`string`** everything inside the found quote pair\n
21
+ Will create two named groups:
22
+ - `quote` the quote type (single or double)
23
+ - `string` everything inside the found quote pair\n
24
24
  ------------------------------------------------------------------------------------
25
- **Attention:** Requires non standard library `regex` not standard library `re`!"""
25
+ Attention: Requires non standard library `regex` not standard library `re`!"""
26
26
  return r'(?P<quote>[\'"])(?P<string>(?:\\.|(?!\g<quote>).)*?)\g<quote>'
27
27
 
28
28
  @staticmethod
29
29
  def brackets(bracket1: str = "(", bracket2: str = ")", is_group: bool = False) -> str:
30
30
  """Matches everything inside brackets, including other nested brackets.\n
31
31
  ------------------------------------------------------------------------------------
32
- **Attention:** Requires non standard library `regex` not standard library `re`!"""
32
+ Attention: Requires non standard library `regex` not standard library `re`!"""
33
33
  g, b1, b2 = (
34
34
  "" if is_group else "?:",
35
35
  _re.escape(bracket1) if len(bracket1) == 1 else bracket1,
@@ -50,40 +50,40 @@ class Regex:
50
50
  ) -> str:
51
51
  """Matches everything except `disallowed_pattern`, unless the `disallowed_pattern` is found inside a string (`'...'` or `"..."`).\n
52
52
  ------------------------------------------------------------------------------------------------------------------------------------
53
- The `ignore_pattern` is just always ignored. For example if `disallowed_pattern` is `>` and `ignore_pattern` is `->`, the `->`<br>
54
- -arrows will be allowed, even though they have `>` in them. If `is_group` is `True`, you will be able to reference the matched<br>
55
- content as a group (e.g. <code>match.group(<i>int</i>)</code> or <code>r'\\<i>int</i>'</code>)."""
53
+ The `ignore_pattern` is just always ignored. For example if `disallowed_pattern` is `>` and `ignore_pattern` is `->`, the `->`
54
+ -arrows will be allowed, even though they have `>` in them. If `is_group` is `True`, you will be able to reference the matched
55
+ content as a group (e.g. `match.group(int)` or `r'\\int'`)."""
56
56
  return rf'({"" if is_group else "?:"}(?:(?!{ignore_pattern}).)*(?:(?!{Regex.outside_strings(disallowed_pattern)}).)*)'
57
57
 
58
58
  @staticmethod
59
59
  def func_call(func_name: str = None) -> str:
60
- """Match a function call<br>
61
- **`1`** function name<br>
62
- **`2`** the function's arguments\n
60
+ """Match a function call
61
+ - `1` function name
62
+ - `2` the function's arguments\n
63
63
  If no `func_name` is given, it will match any function call.\n
64
64
  ------------------------------------------------------------------------------------
65
- **Attention:** Requires non standard library `regex` not standard library `re`!"""
65
+ Attention: Requires non standard library `regex` not standard library `re`!"""
66
66
  return r"(?<=\b)(" + (func_name if func_name else r"[\w_]+") + r")\s*" + Regex.brackets("(", ")", is_group=True)
67
67
 
68
68
  @staticmethod
69
69
  def rgba_str(fix_sep: str = ",", allow_alpha: bool = True) -> str:
70
70
  """Matches an RGBA color inside a string.\n
71
- --------------------------------------------------------------------------------
72
- The RGBA color can be in the formats (for `fix_sep = ','`):<br>
73
- `rgba(r, g, b)`<br>
74
- `rgba(r, g, b, a)` (if `allow_alpha=True`)<br>
75
- `(r, g, b)`<br>
76
- `(r, g, b, a)` (if `allow_alpha=True`)<br>
77
- `r, g, b`<br>
78
- `r, g, b, a` (if `allow_alpha=True`)\n
79
- --------------------------------------------------------------------------------
80
- ### Valid ranges:<br>
81
- `r` 0-255 (amount: red)<br>
82
- `g` 0-255 (amount: green)<br>
83
- `b` 0-255 (amount: blue)<br>
84
- `a` 0-1 (float: opacity)\n
85
- --------------------------------------------------------------------------------
86
- If the `fix_sep` is set to nothing, any char that is not a letter or number<br>
71
+ ----------------------------------------------------------------------------
72
+ The RGBA color can be in the formats (for `fix_sep = ','`):
73
+ - `rgba(r, g, b)`
74
+ - `rgba(r, g, b, a)` (if `allow_alpha=True`)
75
+ - `(r, g, b)`
76
+ - `(r, g, b, a)` (if `allow_alpha=True`)
77
+ - `r, g, b`
78
+ - `r, g, b, a` (if `allow_alpha=True`)\n
79
+ ----------------------------------------------------------------------------
80
+ ### Valid ranges:
81
+ - `r` 0-255 (amount: red)
82
+ - `g` 0-255 (amount: green)
83
+ - `b` 0-255 (amount: blue)
84
+ - `a` 0-1 (float: opacity)\n
85
+ ----------------------------------------------------------------------------
86
+ If the `fix_sep` is set to nothing, any char that is not a letter or number
87
87
  can be used to separate the RGBA values, including just a space."""
88
88
  if fix_sep in (None, ""):
89
89
  fix_sep = r"[^0-9A-Z]"
@@ -104,22 +104,22 @@ class Regex:
104
104
  @staticmethod
105
105
  def hsla_str(fix_sep: str = ",", allow_alpha: bool = True) -> str:
106
106
  """Matches a HSLA color inside a string.\n
107
- --------------------------------------------------------------------------------
108
- The HSLA color can be in the formats (for `fix_sep = ','`):<br>
109
- `hsla(h, s, l)`<br>
110
- `hsla(h, s, l, a)` (if `allow_alpha=True`)<br>
111
- `(h, s, l)`<br>
112
- `(h, s, l, a)` (if `allow_alpha=True`)<br>
113
- `h, s, l`<br>
114
- `h, s, l, a` (if `allow_alpha=True`)\n
115
- --------------------------------------------------------------------------------
116
- ### Valid ranges:<br>
117
- `h` 0-360 (degrees: hue)<br>
118
- `s` 0-100 (percentage: saturation)<br>
119
- `l` 0-100 (percentage: lightness)<br>
120
- `a` 0-1 (float: opacity)\n
121
- --------------------------------------------------------------------------------
122
- If the `fix_sep` is set to nothing, any char that is not a letter or number<br>
107
+ ----------------------------------------------------------------------------
108
+ The HSLA color can be in the formats (for `fix_sep = ','`):
109
+ - `hsla(h, s, l)`
110
+ - `hsla(h, s, l, a)` (if `allow_alpha=True`)
111
+ - `(h, s, l)`
112
+ - `(h, s, l, a)` (if `allow_alpha=True`)
113
+ - `h, s, l`
114
+ - `h, s, l, a` (if `allow_alpha=True`)\n
115
+ ----------------------------------------------------------------------------
116
+ ### Valid ranges:
117
+ - `h` 0-360 (degrees: hue)
118
+ - `s` 0-100 (percentage: saturation)
119
+ - `l` 0-100 (percentage: lightness)
120
+ - `a` 0-1 (float: opacity)\n
121
+ ----------------------------------------------------------------------------
122
+ If the `fix_sep` is set to nothing, any char that is not a letter or number
123
123
  can be used to separate the HSLA values, including just a space."""
124
124
  if fix_sep in (None, ""):
125
125
  fix_sep = r"[^0-9A-Z]"
@@ -140,15 +140,15 @@ class Regex:
140
140
  @staticmethod
141
141
  def hexa_str(allow_alpha: bool = True) -> str:
142
142
  """Matches a HEXA color inside a string.\n
143
- --------------------------------------------------------------------------
144
- The HEXA color can be in the formats (prefix `#`, `0x` or no prefix):<br>
145
- `RGB`<br>
146
- `RGBA` (if `allow_alpha=True`)<br>
147
- `RRGGBB`<br>
148
- `RRGGBBAA` (if `allow_alpha=True`)\n
149
- --------------------------------------------------------------------------
150
- ### Valid ranges:<br>
151
- each channel from 0-9 and A-F (*case insensitive*)"""
143
+ ----------------------------------------------------------------------
144
+ The HEXA color can be in the formats (prefix `#`, `0x` or no prefix):
145
+ - `RGB`
146
+ - `RGBA` (if `allow_alpha=True`)
147
+ - `RRGGBB`
148
+ - `RRGGBBAA` (if `allow_alpha=True`)\n
149
+ ----------------------------------------------------------------------
150
+ ### Valid ranges:
151
+ each channel from 0-9 and A-F (case insensitive)"""
152
152
  return (
153
153
  r"(?i)^(?:#|0x)?[0-9A-F]{8}|[0-9A-F]{6}|[0-9A-F]{4}|[0-9A-F]{3}$"
154
154
  if allow_alpha
xulbux/xx_string.py CHANGED
@@ -56,7 +56,7 @@ class String:
56
56
 
57
57
  @staticmethod
58
58
  def normalize_spaces(string: str, tab_spaces: int = 4) -> str:
59
- """Replaces all special space characters with normal spaces.<br>
59
+ """Replaces all special space characters with normal spaces.
60
60
  Also replaces tab characters with `tab_spaces` spaces."""
61
61
  return (
62
62
  string.replace("\t", " " * tab_spaces)
@@ -76,10 +76,10 @@ class String:
76
76
  @staticmethod
77
77
  def escape(string: str, str_quotes: str = '"') -> str:
78
78
  """Escapes the special characters and quotes inside a string.\n
79
- ----------------------------------------------------------------------------
80
- `str_quotes` can be either `"` or `'` and should match the quotes,<br>
81
- the string will be put inside of. So if your string will be `"string"`,<br>
82
- you should pass `"` to the parameter `str_quotes`.<br>
79
+ ---------------------------------------------------------------------------
80
+ `str_quotes` can be either `"` or `'` and should match the quotes,
81
+ the string will be put inside of. So if your string will be `"string"`,
82
+ you should pass `"` to the parameter `str_quotes`.
83
83
  That way, if the string includes the same quotes, they will be escaped."""
84
84
  string = (
85
85
  string.replace("\\", r"\\")
@@ -98,14 +98,15 @@ class String:
98
98
 
99
99
  @staticmethod
100
100
  def is_empty(string: str, spaces_are_empty: bool = False):
101
- """Returns `True` if the string is empty and `False` otherwise.<br>
101
+ """Returns `True` if the string is empty and `False` otherwise.\n
102
+ -------------------------------------------------------------------------------------------
102
103
  If `spaces_are_empty` is true, it will also return `True` if the string is only spaces."""
103
104
  return (string in (None, "")) or (spaces_are_empty and isinstance(string, str) and not string.strip())
104
105
 
105
106
  @staticmethod
106
107
  def single_char_repeats(string: str, char: str) -> int | bool:
107
- """If the string consists of only the same `char`, it returns the number of times it is present.<br>
108
- If the string doesn't consist of only the same character, it returns `False`."""
108
+ """- If the string consists of only the same `char`, it returns the number of times it is present.
109
+ - If the string doesn't consist of only the same character, it returns `False`."""
109
110
  if len(string) == len(char) * string.count(char):
110
111
  return string.count(char)
111
112
  else:
@@ -113,7 +114,7 @@ class String:
113
114
 
114
115
  @staticmethod
115
116
  def decompose(case_string: str, seps: str = "-_", lower_all: bool = True) -> list[str]:
116
- """Will decompose the string (*any type of casing, also mixed*) into parts."""
117
+ """Will decompose the string (any type of casing, also mixed) into parts."""
117
118
  return [
118
119
  (part.lower() if lower_all else part)
119
120
  for part in _re.split(rf"(?<=[a-z])(?=[A-Z])|[{_re.escape(seps)}]", case_string)
@@ -121,7 +122,7 @@ class String:
121
122
 
122
123
  @staticmethod
123
124
  def to_camel_case(string: str, upper: bool = True) -> str:
124
- """Will convert the string of any type of casing to UpperCamelCase or lowerCamelCase if `upper` is false."""
125
+ """Will convert the string of any type of casing to `UpperCamelCase` or `lowerCamelCase` if `upper` is false."""
125
126
  parts = String.decompose(string)
126
127
  return ("" if upper else parts[0].lower()) + "".join(part.capitalize() for part in (parts if upper else parts[1:]))
127
128
 
@@ -146,9 +147,9 @@ class String:
146
147
  @staticmethod
147
148
  def remove_consecutive_empty_lines(string: str, max_consecutive: int = 0) -> str:
148
149
  """Will remove consecutive empty lines from the string.\n
149
- ----------------------------------------------------------------------------------------------
150
- If `max_consecutive` is `0`, it will remove all consecutive empty lines.<br>
151
- If `max_consecutive` is bigger than `0`, it will only allow `max_consecutive` consecutive<br>
150
+ --------------------------------------------------------------------------------------------
151
+ - If `max_consecutive` is `0`, it will remove all consecutive empty lines.
152
+ - If `max_consecutive` is bigger than `0`, it will only allow `max_consecutive` consecutive
152
153
  empty lines and everything above it will be cut down to `max_consecutive` empty lines."""
153
154
  return _re.sub(r"(\n\s*){2,}", r"\1" * (max_consecutive + 1), string)
154
155