stouputils 1.18.5__py3-none-any.whl → 1.18.6__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.
@@ -94,15 +94,22 @@ def launch_tests(root_dir: str, strict: bool = True, pattern: str = "*") -> int:
94
94
  if not modules_file_paths:
95
95
  raise ValueError(f"No modules found in '{relative_path(root_dir)}'")
96
96
 
97
+ # Sort module by number of submodules and alphabetically
98
+ modules_file_paths.sort(key=lambda x: (x.count('.'), x))
99
+
97
100
  # Filter modules based on pattern
98
101
  if pattern != "*":
99
102
  import fnmatch
100
- modules_file_paths = [
103
+ new_paths: list[str] = [
101
104
  path for path in modules_file_paths
102
105
  if fnmatch.fnmatch(path, pattern)
103
106
  ]
104
- if not modules_file_paths:
105
- raise ValueError(f"No modules matching pattern '{pattern}' found in '{relative_path(root_dir)}'")
107
+ if not new_paths:
108
+ raise ValueError(
109
+ f"No modules matching pattern '{pattern}' found in '{relative_path(root_dir)}'.\n"
110
+ f"Candidates were: {', '.join(relative_path(p) for p in modules_file_paths)[:500]}..."
111
+ )
112
+ modules_file_paths = new_paths
106
113
 
107
114
  # Find longest module path for alignment
108
115
  max_length: int = max(len(path) for path in modules_file_paths)
stouputils/print.py CHANGED
@@ -107,45 +107,67 @@ def format_colored(*values: Any) -> str:
107
107
  Examples:
108
108
  >>> # Test function names with parentheses
109
109
  >>> result = format_colored("Call print() with 42 items")
110
- >>> result.count(MAGENTA) == 2 # print and 42
111
- True
110
+ >>> result.count(MAGENTA) # print and 42
111
+ 2
112
112
 
113
113
  >>> # Test function names without parentheses
114
114
  >>> result = format_colored("Use len and sum functions")
115
- >>> result.count(MAGENTA) == 2 # len and sum
116
- True
115
+ >>> result.count(MAGENTA) # len and sum
116
+ 2
117
117
 
118
118
  >>> # Test exceptions (bold magenta)
119
119
  >>> result = format_colored("Got ValueError when parsing")
120
- >>> result.count(MAGENTA) == 1 and result.count(BOLD) == 1 # ValueError in bold magenta
121
- True
120
+ >>> result.count(MAGENTA), result.count(BOLD) # ValueError in bold magenta
121
+ (1, 1)
122
122
 
123
123
  >>> # Test file paths
124
124
  >>> result = format_colored("Processing ./data.csv file")
125
- >>> result.count(MAGENTA) == 1 # ./data.csv
126
- True
125
+ >>> result.count(MAGENTA) # ./data.csv
126
+ 1
127
127
 
128
128
  >>> # Test file paths with quotes
129
129
  >>> result = format_colored('File "/path/to/script.py" line 42')
130
- >>> result.count(MAGENTA) == 2 # /path/to/script.py and 42
131
- True
130
+ >>> result.count(MAGENTA) # /path/to/script.py and 42
131
+ 2
132
132
 
133
133
  >>> # Test numbers
134
- >>> result = format_colored("Found 100 items and 3.14 value")
135
- >>> result.count(MAGENTA) == 2 # 100 and 3.14
136
- True
134
+ >>> result = format_colored("Found 100 items and 3.14 value, 3.0e+10 is big")
135
+ >>> result.count(MAGENTA) # 100 and 3.14
136
+ 3
137
137
 
138
138
  >>> # Test mixed content
139
139
  >>> result = format_colored("Call sum() got IndexError at line 256 in utils.py")
140
- >>> result.count(MAGENTA) == 3 # sum, IndexError (bold), and 256
141
- True
142
- >>> result.count(BOLD) == 1 # IndexError is bold
143
- True
140
+ >>> result.count(MAGENTA) # sum, IndexError (bold), and 256
141
+ 3
142
+ >>> result.count(BOLD) # IndexError is bold
143
+ 1
144
+
145
+ >>> # Test keywords always colored
146
+ >>> result = format_colored("Check class dtype type")
147
+ >>> result.count(MAGENTA) # class, dtype, type
148
+ 3
144
149
 
145
150
  >>> # Test plain text (no coloring)
146
151
  >>> result = format_colored("This is plain text")
147
152
  >>> result.count(MAGENTA) == 0 and result == "This is plain text"
148
153
  True
154
+
155
+ >>> # Affix punctuation should not be colored (assert exact coloring, punctuation uncolored)
156
+ >>> result = format_colored("<class")
157
+ >>> result == "<" + MAGENTA + "class" + RESET
158
+ True
159
+ >>> result = format_colored("(dtype:")
160
+ >>> result == "(" + MAGENTA + "dtype" + RESET + ":"
161
+ True
162
+ >>> result = format_colored("[1.")
163
+ >>> result == "[" + MAGENTA + "1" + RESET + "."
164
+ True
165
+
166
+ >>> # Test complex
167
+ >>> text = "<class 'numpy.ndarray'>, <id 140357548266896>: (dtype: float32, shape: (6,), min: 0.0, max: 1.0) [1. 0. 0. 0. 1. 0.]"
168
+ >>> result = format_colored(text)
169
+ >>> result.count(MAGENTA) # class, numpy, ndarray, float32, 6, 0.0, 1.0, 1. 0.
170
+ 16
149
171
  """
150
172
  import builtins
151
173
  import re
@@ -163,6 +185,9 @@ def format_colored(*values: Any) -> str:
163
185
  and issubclass(getattr(builtins, name), BaseException))
164
186
  }
165
187
 
188
+ # Additional keywords always colored (case-insensitive on stripped words)
189
+ KEYWORDS: set[str] = {"class", "dtype", "type"}
190
+
166
191
  def is_filepath(word: str) -> bool:
167
192
  """ Check if a word looks like a file path """
168
193
  # Remove quotes if present
@@ -187,7 +212,7 @@ def format_colored(*values: Any) -> str:
187
212
 
188
213
  def is_number(word: str) -> bool:
189
214
  try:
190
- float(word.strip('.,;:!?'))
215
+ float(''.join(c for c in word if c.isdigit() or c in '.-+e'))
191
216
  return True
192
217
  except ValueError:
193
218
  return False
@@ -201,7 +226,22 @@ def format_colored(*values: Any) -> str:
201
226
 
202
227
  def is_exception(word: str) -> bool:
203
228
  """ Check if a word is a known exception name """
204
- return word.strip('.,;:!?') in EXCEPTION_NAMES
229
+ return ''.join(c for c in word if c.isalnum()) in EXCEPTION_NAMES
230
+
231
+ def is_keyword(word: str) -> bool:
232
+ """ Check if a word is one of the always-colored keywords """
233
+ clean_alnum = ''.join(c for c in word if c.isalnum())
234
+ return clean_alnum in KEYWORDS
235
+
236
+ def split_affixes(w: str) -> tuple[str, str, str]:
237
+ """ Split leading/trailing non-word characters and return (prefix, core, suffix).
238
+
239
+ This preserves punctuation like '<', '(', '[', '"', etc., while operating on the core text.
240
+ """
241
+ m = re.match(r'^(\W*)(.*?)(\W*)$', w, re.ASCII)
242
+ if m:
243
+ return m.group(1), m.group(2), m.group(3)
244
+ return "", w, ""
205
245
 
206
246
  # Convert all values to strings and join them and split into words while preserving separators
207
247
  text: str = " ".join(str(v) for v in values)
@@ -219,36 +259,43 @@ def format_colored(*values: Any) -> str:
219
259
  i += 1
220
260
  continue
221
261
 
222
- # Try to identify and color the word
262
+ # If the whole token looks like a filepath (e.g. './data.csv' or '/path/to/file'), color it as-is
223
263
  colored: bool = False
224
264
  if is_filepath(word):
225
265
  colored_words.append(f"{MAGENTA}{word}{RESET}")
226
266
  colored = True
227
- elif is_exception(word):
228
- colored_words.append(f"{BOLD}{MAGENTA}{word}{RESET}")
229
- colored = True
230
- elif is_number(word):
231
- # Preserve punctuation
232
- clean_word = word.strip('.,;:!?')
233
- prefix = word[:len(word) - len(word.lstrip('.,;:!?'))]
234
- suffix = word[len(clean_word) + len(prefix):]
235
- colored_words.append(f"{prefix}{MAGENTA}{clean_word}{RESET}{suffix}")
236
- colored = True
237
- elif is_function_name(word)[0]:
238
- func_name = is_function_name(word)[1]
239
- # Find where the function name ends in the original word
240
- func_start = word.find(func_name)
241
- if func_start != -1:
242
- prefix = word[:func_start]
243
- func_end = func_start + len(func_name)
244
- suffix = word[func_end:]
245
- colored_words.append(f"{prefix}{MAGENTA}{func_name}{RESET}{suffix}")
246
- else:
247
- # Fallback if we can't find it (shouldn't happen)
248
- colored_words.append(f"{MAGENTA}{word}{RESET}")
249
- colored = True
267
+ else:
268
+ # Split affixes to preserve punctuation like '<', '(', '[' etc.
269
+ prefix, core, suffix = split_affixes(word)
270
+
271
+ # Try to identify and color the word (operate on core where applicable)
272
+ if is_filepath(core):
273
+ colored_words.append(f"{prefix}{MAGENTA}{core}{RESET}{suffix}")
274
+ colored = True
275
+ elif is_exception(core):
276
+ colored_words.append(f"{prefix}{BOLD}{MAGENTA}{core}{RESET}{suffix}")
277
+ colored = True
278
+ elif is_number(core):
279
+ colored_words.append(f"{prefix}{MAGENTA}{core}{RESET}{suffix}")
280
+ colored = True
281
+ elif is_keyword(core):
282
+ colored_words.append(f"{prefix}{MAGENTA}{core}{RESET}{suffix}")
283
+ colored = True
284
+ elif is_function_name(core)[0]:
285
+ func_name = is_function_name(core)[1]
286
+ # Find where the function name ends in the core
287
+ func_start = core.find(func_name)
288
+ if func_start != -1:
289
+ pre_core = core[:func_start]
290
+ func_end = func_start + len(func_name)
291
+ post_core = core[func_end:]
292
+ colored_words.append(f"{prefix}{pre_core}{MAGENTA}{func_name}{RESET}{post_core}{suffix}")
293
+ else:
294
+ # Fallback if we can't find it (shouldn't happen)
295
+ colored_words.append(f"{prefix}{MAGENTA}{core}{RESET}{suffix}")
296
+ colored = True
250
297
 
251
- # If nothing matched, keep the word as is
298
+ # If nothing matched, keep the original word
252
299
  if not colored:
253
300
  colored_words.append(word)
254
301
  i += 1
stouputils/print.pyi CHANGED
@@ -53,45 +53,67 @@ def format_colored(*values: Any) -> str:
53
53
  \tExamples:
54
54
  \t\t>>> # Test function names with parentheses
55
55
  \t\t>>> result = format_colored("Call print() with 42 items")
56
- \t\t>>> result.count(MAGENTA) == 2 # print and 42
57
- \t\tTrue
56
+ \t\t>>> result.count(MAGENTA) # print and 42
57
+ \t\t2
58
58
 
59
59
  \t\t>>> # Test function names without parentheses
60
60
  \t\t>>> result = format_colored("Use len and sum functions")
61
- \t\t>>> result.count(MAGENTA) == 2 # len and sum
62
- \t\tTrue
61
+ \t\t>>> result.count(MAGENTA) # len and sum
62
+ \t\t2
63
63
 
64
64
  \t\t>>> # Test exceptions (bold magenta)
65
65
  \t\t>>> result = format_colored("Got ValueError when parsing")
66
- \t\t>>> result.count(MAGENTA) == 1 and result.count(BOLD) == 1 # ValueError in bold magenta
67
- \t\tTrue
66
+ \t\t>>> result.count(MAGENTA), result.count(BOLD) # ValueError in bold magenta
67
+ \t\t(1, 1)
68
68
 
69
69
  \t\t>>> # Test file paths
70
70
  \t\t>>> result = format_colored("Processing ./data.csv file")
71
- \t\t>>> result.count(MAGENTA) == 1 # ./data.csv
72
- \t\tTrue
71
+ \t\t>>> result.count(MAGENTA) # ./data.csv
72
+ \t\t1
73
73
 
74
74
  \t\t>>> # Test file paths with quotes
75
75
  \t\t>>> result = format_colored(\'File "/path/to/script.py" line 42\')
76
- \t\t>>> result.count(MAGENTA) == 2 # /path/to/script.py and 42
77
- \t\tTrue
76
+ \t\t>>> result.count(MAGENTA) # /path/to/script.py and 42
77
+ \t\t2
78
78
 
79
79
  \t\t>>> # Test numbers
80
- \t\t>>> result = format_colored("Found 100 items and 3.14 value")
81
- \t\t>>> result.count(MAGENTA) == 2 # 100 and 3.14
82
- \t\tTrue
80
+ \t\t>>> result = format_colored("Found 100 items and 3.14 value, 3.0e+10 is big")
81
+ \t\t>>> result.count(MAGENTA) # 100 and 3.14
82
+ \t\t3
83
83
 
84
84
  \t\t>>> # Test mixed content
85
85
  \t\t>>> result = format_colored("Call sum() got IndexError at line 256 in utils.py")
86
- \t\t>>> result.count(MAGENTA) == 3 # sum, IndexError (bold), and 256
87
- \t\tTrue
88
- \t\t>>> result.count(BOLD) == 1 # IndexError is bold
89
- \t\tTrue
86
+ \t\t>>> result.count(MAGENTA) # sum, IndexError (bold), and 256
87
+ \t\t3
88
+ \t\t>>> result.count(BOLD) # IndexError is bold
89
+ \t\t1
90
+
91
+ \t\t>>> # Test keywords always colored
92
+ \t\t>>> result = format_colored("Check class dtype type")
93
+ \t\t>>> result.count(MAGENTA) # class, dtype, type
94
+ \t\t3
90
95
 
91
96
  \t\t>>> # Test plain text (no coloring)
92
97
  \t\t>>> result = format_colored("This is plain text")
93
98
  \t\t>>> result.count(MAGENTA) == 0 and result == "This is plain text"
94
99
  \t\tTrue
100
+
101
+ \t\t>>> # Affix punctuation should not be colored (assert exact coloring, punctuation uncolored)
102
+ \t\t>>> result = format_colored("<class")
103
+ \t\t>>> result == "<" + MAGENTA + "class" + RESET
104
+ \t\tTrue
105
+ \t\t>>> result = format_colored("(dtype:")
106
+ \t\t>>> result == "(" + MAGENTA + "dtype" + RESET + ":"
107
+ \t\tTrue
108
+ \t\t>>> result = format_colored("[1.")
109
+ \t\t>>> result == "[" + MAGENTA + "1" + RESET + "."
110
+ \t\tTrue
111
+
112
+ \t\t>>> # Test complex
113
+ \t\t>>> text = "<class \'numpy.ndarray\'>, <id 140357548266896>: (dtype: float32, shape: (6,), min: 0.0, max: 1.0) [1. 0. 0. 0. 1. 0.]"
114
+ \t\t>>> result = format_colored(text)
115
+ \t\t>>> result.count(MAGENTA) # class, numpy, ndarray, float32, 6, 0.0, 1.0, 1. 0.
116
+ \t\t16
95
117
  \t'''
96
118
  def colored(*values: Any, file: TextIO | None = None, **print_kwargs: Any) -> None:
97
119
  ''' Print with Python 3.14 style colored formatting.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: stouputils
3
- Version: 1.18.5
3
+ Version: 1.18.6
4
4
  Summary: Stouputils is a collection of utility modules designed to simplify and enhance the development process. It includes a range of tools for tasks such as execution of doctests, display utilities, decorators, as well as context managers, and many more.
5
5
  Keywords: utilities,tools,helpers,development,python
6
6
  Author: Stoupy51
@@ -3,7 +3,7 @@ stouputils/__init__.pyi,sha256=T8-ovn3Kl5CxYtBjJJ5AzR2qujECMI1Biwahz0nooBo,395
3
3
  stouputils/__main__.py,sha256=MA3jjc1yL8_0Z9oB55BMqrFq1ot_-e5rNqSxFQGsMzs,2910
4
4
  stouputils/_deprecated.py,sha256=Bcq6YjdM9Rk9Vq-WMhc_tuEbPORX6U8HAJ9Vh-VIWTA,1478
5
5
  stouputils/_deprecated.pyi,sha256=6-8YsftJd2fRAdBLsysc6jf-uA8V2wiqkiFAbdfWfJQ,664
6
- stouputils/all_doctests.py,sha256=alihPKBSdOBuNBQu7zoyYowN1PumZwuemUZOFvfGFrk,6313
6
+ stouputils/all_doctests.py,sha256=jQ7HXWoj2VTUbsu8eBq7EiW9mbx-_yXycdpS1fD8Tro,6566
7
7
  stouputils/all_doctests.pyi,sha256=R3FRKaQv3sTZbxLvvsChHZZKygVMhmL6pqrYYLqvZCg,2017
8
8
  stouputils/applications/__init__.py,sha256=dbjwZt8PZF043KoJSItqCpH32FtRxN5sgV-8Q2b1l10,457
9
9
  stouputils/applications/__init__.pyi,sha256=DTYq2Uqq1uLzCMkFByjRqdtREA-9SaQnp4QpgmCEPFg,56
@@ -149,14 +149,14 @@ stouputils/parallel/multi.py,sha256=tHJgcQJwsI6QeKEHoGJC4tsVK_6t1Fazkb06i1u-W_8,
149
149
  stouputils/parallel/multi.pyi,sha256=DWolZn1UoXxOfuw7LqEJcU8aQJsN-_DRhPGJlJCA5pQ,8021
150
150
  stouputils/parallel/subprocess.py,sha256=LWbwwAmnz54dCz9TAcKNg1TOMCVSP0C-0GIXaS5nVx0,6728
151
151
  stouputils/parallel/subprocess.pyi,sha256=gzRtpTslvoENLtSNk79fe3Xz8lV3IwuopT9uMHW9BTU,3680
152
- stouputils/print.py,sha256=WITobaFWrnVfrE4YRwHTRzf6H6V0BhxSIoQ9LRIrRiQ,24831
153
- stouputils/print.pyi,sha256=GOgbg7YomG-D9n2hcM0bUOaUU1PBjazWIn4vUSBYDDU,10365
152
+ stouputils/print.py,sha256=86Qjyyj_riU7w3RQdYIHTlPVICUzKsfEBF6NBwZc20g,26745
153
+ stouputils/print.pyi,sha256=qu7Pr1c6let2fLcBvbfrrcfCg0s3rf_1jD8FDhR1bgk,11188
154
154
  stouputils/py.typed,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
155
155
  stouputils/typing.py,sha256=TwvxrvxhBRkyHkoOpfyXebN13M3xJb8MAjKXiNIWjew,2205
156
156
  stouputils/typing.pyi,sha256=U2UmFZausMYpnsUQROQE2JOwHcjx2hKV0rJuOdR57Ew,1341
157
157
  stouputils/version_pkg.py,sha256=Jsp-s03L14DkiZ94vQgrlQmaxApfn9DC8M_nzT1SJLk,7014
158
158
  stouputils/version_pkg.pyi,sha256=QPvqp1U3QA-9C_CC1dT9Vahv1hXEhstbM7x5uzMZSsQ,755
159
- stouputils-1.18.5.dist-info/WHEEL,sha256=e_m4S054HL0hyR3CpOk-b7Q7fDX6BuFkgL5OjAExXas,80
160
- stouputils-1.18.5.dist-info/entry_points.txt,sha256=tx0z9VOnE-sfkmbFbA93zaBMzV3XSsKEJa_BWIqUzxw,57
161
- stouputils-1.18.5.dist-info/METADATA,sha256=EZJ0wIP6L2MHi74YnsJio3ofTMjC4k5ca9tlf6OSXmM,14011
162
- stouputils-1.18.5.dist-info/RECORD,,
159
+ stouputils-1.18.6.dist-info/WHEEL,sha256=e_m4S054HL0hyR3CpOk-b7Q7fDX6BuFkgL5OjAExXas,80
160
+ stouputils-1.18.6.dist-info/entry_points.txt,sha256=tx0z9VOnE-sfkmbFbA93zaBMzV3XSsKEJa_BWIqUzxw,57
161
+ stouputils-1.18.6.dist-info/METADATA,sha256=w_R6exClXWoaJI171uO0hX5rZ3oh3hTvCtCua1di9RM,14011
162
+ stouputils-1.18.6.dist-info/RECORD,,