puild 0.1.0__tar.gz → 0.1.2__tar.gz
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.
- {puild-0.1.0 → puild-0.1.2}/PKG-INFO +11 -6
- {puild-0.1.0 → puild-0.1.2}/README.md +10 -5
- {puild-0.1.0 → puild-0.1.2}/pyproject.toml +1 -5
- {puild-0.1.0 → puild-0.1.2}/pyproject.toml.orig +1 -5
- {puild-0.1.0 → puild-0.1.2}/src/puild/__init__.py +11 -1
- {puild-0.1.0 → puild-0.1.2}/src/puild/command.py +167 -42
- puild-0.1.2/src/puild/logging.py +71 -0
- puild-0.1.0/src/puild/logging.py +0 -30
- {puild-0.1.0 → puild-0.1.2}/src/puild/fs.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: puild
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: A lightweight, modern build tool and command runner for Python.
|
|
5
5
|
Requires-Dist: rich>=15.0.0
|
|
6
6
|
Requires-Python: >=3.14
|
|
@@ -69,15 +69,20 @@ cmd = Command("echo", "second step completed") >> "build.log"
|
|
|
69
69
|
cmd.run()
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
### 4. Live Streaming Output
|
|
72
|
+
### 4. Live Streaming Output & Indentation
|
|
73
73
|
|
|
74
|
-
Stream output to stdout/stderr in real-time
|
|
74
|
+
Stream command output to stdout/stderr in real-time. Output is automatically indented (default 4 spaces) so it stays visually separated from puild's action logs:
|
|
75
75
|
|
|
76
76
|
```python
|
|
77
|
-
from puild import Command
|
|
77
|
+
from puild import Command, set_default_indent
|
|
78
|
+
|
|
79
|
+
# Stream with default 4-space indentation
|
|
80
|
+
cmd = Command("npm", "install")
|
|
81
|
+
cmd.run(stream=True, text=True)
|
|
78
82
|
|
|
79
|
-
|
|
80
|
-
|
|
83
|
+
# Custom indentation per-command or globally
|
|
84
|
+
cmd.run(stream=True, indent=" ") # 2 spaces
|
|
85
|
+
set_default_indent(" ") # Set global default
|
|
81
86
|
```
|
|
82
87
|
|
|
83
88
|
### 5. Incremental Builds (`needs_rebuild`)
|
|
@@ -61,15 +61,20 @@ cmd = Command("echo", "second step completed") >> "build.log"
|
|
|
61
61
|
cmd.run()
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
### 4. Live Streaming Output
|
|
64
|
+
### 4. Live Streaming Output & Indentation
|
|
65
65
|
|
|
66
|
-
Stream output to stdout/stderr in real-time
|
|
66
|
+
Stream command output to stdout/stderr in real-time. Output is automatically indented (default 4 spaces) so it stays visually separated from puild's action logs:
|
|
67
67
|
|
|
68
68
|
```python
|
|
69
|
-
from puild import Command
|
|
69
|
+
from puild import Command, set_default_indent
|
|
70
|
+
|
|
71
|
+
# Stream with default 4-space indentation
|
|
72
|
+
cmd = Command("npm", "install")
|
|
73
|
+
cmd.run(stream=True, text=True)
|
|
70
74
|
|
|
71
|
-
|
|
72
|
-
|
|
75
|
+
# Custom indentation per-command or globally
|
|
76
|
+
cmd.run(stream=True, indent=" ") # 2 spaces
|
|
77
|
+
set_default_indent(" ") # Set global default
|
|
73
78
|
```
|
|
74
79
|
|
|
75
80
|
### 5. Incremental Builds (`needs_rebuild`)
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "puild"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.2"
|
|
4
4
|
description = "A lightweight, modern build tool and command runner for Python."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.14"
|
|
7
7
|
dependencies = ["rich>=15.0.0"]
|
|
8
8
|
|
|
9
|
-
[project.scripts]
|
|
10
|
-
run = "puild.main:main"
|
|
11
|
-
puild = "puild.main:main"
|
|
12
|
-
|
|
13
9
|
[build-system]
|
|
14
10
|
requires = ["uv_build>=0.12.19,<0.13"]
|
|
15
11
|
build-backend = "uv_build"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "puild"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.2"
|
|
4
4
|
description = "A lightweight, modern build tool and command runner for Python."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.14"
|
|
@@ -12,7 +12,3 @@ dependencies = [
|
|
|
12
12
|
[build-system]
|
|
13
13
|
requires = ["uv_build>=0.12.19,<0.13"]
|
|
14
14
|
build-backend = "uv_build"
|
|
15
|
-
|
|
16
|
-
[project.scripts]
|
|
17
|
-
run = "puild.main:main"
|
|
18
|
-
puild = "puild.main:main"
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
from puild.command import Command, Pipeline, Result, is_dry_run, set_dry_run
|
|
2
2
|
from puild.fs import copy, find_files, mkdir, needs_rebuild, rm
|
|
3
|
-
from puild.logging import
|
|
3
|
+
from puild.logging import (
|
|
4
|
+
get_default_indent,
|
|
5
|
+
is_quiet,
|
|
6
|
+
log_action,
|
|
7
|
+
log_indented,
|
|
8
|
+
set_default_indent,
|
|
9
|
+
set_quiet,
|
|
10
|
+
)
|
|
4
11
|
|
|
5
12
|
__all__ = [
|
|
6
13
|
"Command",
|
|
@@ -8,12 +15,15 @@ __all__ = [
|
|
|
8
15
|
"Result",
|
|
9
16
|
"copy",
|
|
10
17
|
"find_files",
|
|
18
|
+
"get_default_indent",
|
|
11
19
|
"is_dry_run",
|
|
12
20
|
"is_quiet",
|
|
13
21
|
"log_action",
|
|
22
|
+
"log_indented",
|
|
14
23
|
"mkdir",
|
|
15
24
|
"needs_rebuild",
|
|
16
25
|
"rm",
|
|
26
|
+
"set_default_indent",
|
|
17
27
|
"set_dry_run",
|
|
18
28
|
"set_quiet",
|
|
19
29
|
]
|
|
@@ -10,7 +10,12 @@ from dataclasses import dataclass
|
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
from typing import Any, Literal, Self, overload
|
|
12
12
|
|
|
13
|
-
from puild.logging import
|
|
13
|
+
from puild.logging import (
|
|
14
|
+
get_default_indent,
|
|
15
|
+
log_action,
|
|
16
|
+
log_indented,
|
|
17
|
+
set_default_indent,
|
|
18
|
+
)
|
|
14
19
|
|
|
15
20
|
_DRY_RUN = False
|
|
16
21
|
|
|
@@ -123,6 +128,7 @@ class Command:
|
|
|
123
128
|
return Pipeline([self, other])
|
|
124
129
|
elif isinstance(other, Pipeline):
|
|
125
130
|
return Pipeline([self, *other.commands])
|
|
131
|
+
return NotImplemented
|
|
126
132
|
|
|
127
133
|
def __gt__(self, target: str | Path) -> Command:
|
|
128
134
|
"""Redirect stdout to file (overwrite): cmd > 'output.txt'"""
|
|
@@ -147,9 +153,11 @@ class Command:
|
|
|
147
153
|
log: bool = True,
|
|
148
154
|
capture: bool = True,
|
|
149
155
|
stream: bool = False,
|
|
156
|
+
indent: str | None = None,
|
|
150
157
|
env: dict[str, str] | None = None,
|
|
151
158
|
input: bytes | None = None,
|
|
152
159
|
check: bool = False,
|
|
160
|
+
raw: bool = False,
|
|
153
161
|
dry_run: bool | None = None,
|
|
154
162
|
) -> Result[bytes]: ...
|
|
155
163
|
|
|
@@ -162,9 +170,11 @@ class Command:
|
|
|
162
170
|
log: bool = True,
|
|
163
171
|
capture: bool = True,
|
|
164
172
|
stream: bool = False,
|
|
173
|
+
indent: str | None = None,
|
|
165
174
|
env: dict[str, str] | None = None,
|
|
166
175
|
input: str | None = None,
|
|
167
176
|
check: bool = False,
|
|
177
|
+
raw: bool = False,
|
|
168
178
|
dry_run: bool | None = None,
|
|
169
179
|
) -> Result[str]: ...
|
|
170
180
|
|
|
@@ -177,9 +187,11 @@ class Command:
|
|
|
177
187
|
log: bool = True,
|
|
178
188
|
capture: bool = True,
|
|
179
189
|
stream: bool = False,
|
|
190
|
+
indent: str | None = None,
|
|
180
191
|
env: dict[str, str] | None = None,
|
|
181
192
|
input: Any = None,
|
|
182
193
|
check: bool = False,
|
|
194
|
+
raw: bool = False,
|
|
183
195
|
dry_run: bool | None = None,
|
|
184
196
|
) -> Result[Any]: ...
|
|
185
197
|
|
|
@@ -191,13 +203,16 @@ class Command:
|
|
|
191
203
|
log: bool = True,
|
|
192
204
|
capture: bool = True,
|
|
193
205
|
stream: bool = False,
|
|
206
|
+
indent: str | None = None,
|
|
194
207
|
env: dict[str, str] | None = None,
|
|
195
208
|
input: Any = None,
|
|
196
209
|
check: bool = False,
|
|
210
|
+
raw: bool = False,
|
|
197
211
|
dry_run: bool | None = None,
|
|
198
212
|
) -> Result[Any]:
|
|
199
213
|
effective_cwd = str(cwd) if cwd is not None else self._cwd
|
|
200
214
|
is_dry = _DRY_RUN if dry_run is None else dry_run
|
|
215
|
+
effective_indent = get_default_indent() if indent is None else indent
|
|
201
216
|
|
|
202
217
|
cmd_display = " ".join(self.to_list())
|
|
203
218
|
if self._redirect_stdout:
|
|
@@ -251,15 +266,32 @@ class Command:
|
|
|
251
266
|
stdout_res = empty_out
|
|
252
267
|
stderr_res = res.stderr if capture and res.stderr is not None else empty_out
|
|
253
268
|
return_code = res.returncode
|
|
254
|
-
elif
|
|
269
|
+
elif raw:
|
|
270
|
+
# Raw interactive TTY execution without interception
|
|
271
|
+
res = subprocess.run(
|
|
272
|
+
self.to_list(),
|
|
273
|
+
cwd=effective_cwd,
|
|
274
|
+
text=text,
|
|
275
|
+
env=merged_env,
|
|
276
|
+
input=input,
|
|
277
|
+
capture_output=False,
|
|
278
|
+
)
|
|
279
|
+
stdout_res = empty_out
|
|
280
|
+
stderr_res = empty_out
|
|
281
|
+
return_code = res.returncode
|
|
282
|
+
elif stream or not capture:
|
|
283
|
+
# Stream output live to terminal with indentation
|
|
255
284
|
stdout_res, stderr_res, return_code = self._run_stream(
|
|
256
285
|
cmd_list=self.to_list(),
|
|
257
286
|
cwd=effective_cwd,
|
|
258
287
|
text=text,
|
|
259
288
|
env=merged_env,
|
|
260
289
|
input_data=input,
|
|
290
|
+
indent=effective_indent,
|
|
291
|
+
collect_output=capture,
|
|
261
292
|
)
|
|
262
|
-
|
|
293
|
+
else:
|
|
294
|
+
# Default capture=True, stream=False
|
|
263
295
|
res = subprocess.run(
|
|
264
296
|
self.to_list(),
|
|
265
297
|
cwd=effective_cwd,
|
|
@@ -271,29 +303,30 @@ class Command:
|
|
|
271
303
|
stdout_res = res.stdout
|
|
272
304
|
stderr_res = res.stderr
|
|
273
305
|
return_code = res.returncode
|
|
274
|
-
else:
|
|
275
|
-
res = subprocess.run(
|
|
276
|
-
self.to_list(),
|
|
277
|
-
cwd=effective_cwd,
|
|
278
|
-
text=text,
|
|
279
|
-
env=merged_env,
|
|
280
|
-
input=input,
|
|
281
|
-
capture_output=False,
|
|
282
|
-
)
|
|
283
|
-
stdout_res = empty_out
|
|
284
|
-
stderr_res = empty_out
|
|
285
|
-
return_code = res.returncode
|
|
286
306
|
|
|
287
307
|
elapsed = time.perf_counter() - start
|
|
288
308
|
|
|
289
309
|
if log:
|
|
290
310
|
if return_code != 0:
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
311
|
+
if stream or not capture:
|
|
312
|
+
log_action(
|
|
313
|
+
"Failed",
|
|
314
|
+
f"Command failed with exit code {return_code}",
|
|
315
|
+
"red",
|
|
316
|
+
)
|
|
317
|
+
else:
|
|
318
|
+
err_msg = (
|
|
319
|
+
stderr_res
|
|
320
|
+
if isinstance(stderr_res, str)
|
|
321
|
+
else stderr_res.decode("utf-8", errors="replace")
|
|
322
|
+
)
|
|
323
|
+
log_action(
|
|
324
|
+
"Failed",
|
|
325
|
+
f"Command failed with exit code {return_code}",
|
|
326
|
+
"red",
|
|
327
|
+
)
|
|
328
|
+
if err_msg.strip():
|
|
329
|
+
log_indented(err_msg.strip(), indent=effective_indent)
|
|
297
330
|
else:
|
|
298
331
|
log_action("Success", f"Took {elapsed:.3f}s", "green")
|
|
299
332
|
|
|
@@ -318,6 +351,8 @@ class Command:
|
|
|
318
351
|
text: bool,
|
|
319
352
|
env: dict[str, str],
|
|
320
353
|
input_data: Any,
|
|
354
|
+
indent: str,
|
|
355
|
+
collect_output: bool = True,
|
|
321
356
|
) -> tuple[Any, Any, int]:
|
|
322
357
|
stdin_setting = subprocess.PIPE if input_data is not None else None
|
|
323
358
|
proc = subprocess.Popen(
|
|
@@ -346,14 +381,23 @@ class Command:
|
|
|
346
381
|
try:
|
|
347
382
|
if text:
|
|
348
383
|
for line in iter(pipe.readline, ""):
|
|
349
|
-
|
|
350
|
-
|
|
384
|
+
if collect_output:
|
|
385
|
+
chunks.append(line)
|
|
386
|
+
if not line.strip():
|
|
387
|
+
dest_stream.write(line)
|
|
388
|
+
else:
|
|
389
|
+
dest_stream.write(f"{indent}{line}")
|
|
351
390
|
dest_stream.flush()
|
|
352
391
|
else:
|
|
353
392
|
target = getattr(dest_stream, "buffer", dest_stream)
|
|
393
|
+
indent_bytes = indent.encode("utf-8")
|
|
354
394
|
for line in iter(pipe.readline, b""):
|
|
355
|
-
|
|
356
|
-
|
|
395
|
+
if collect_output:
|
|
396
|
+
chunks.append(line)
|
|
397
|
+
if not line.strip():
|
|
398
|
+
target.write(line)
|
|
399
|
+
else:
|
|
400
|
+
target.write(indent_bytes + line)
|
|
357
401
|
target.flush()
|
|
358
402
|
finally:
|
|
359
403
|
pipe.close()
|
|
@@ -371,10 +415,19 @@ class Command:
|
|
|
371
415
|
t_out.join()
|
|
372
416
|
t_err.join()
|
|
373
417
|
|
|
374
|
-
if text
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
418
|
+
empty = "" if text else b""
|
|
419
|
+
out_res = (
|
|
420
|
+
("".join(stdout_chunks) if text else b"".join(stdout_chunks))
|
|
421
|
+
if collect_output
|
|
422
|
+
else empty
|
|
423
|
+
)
|
|
424
|
+
err_res = (
|
|
425
|
+
("".join(stderr_chunks) if text else b"".join(stderr_chunks))
|
|
426
|
+
if collect_output
|
|
427
|
+
else empty
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
return out_res, err_res, proc.returncode
|
|
378
431
|
|
|
379
432
|
|
|
380
433
|
class Pipeline:
|
|
@@ -397,6 +450,8 @@ class Pipeline:
|
|
|
397
450
|
cwd: str | Path = ".",
|
|
398
451
|
text: bool = True,
|
|
399
452
|
log: bool = True,
|
|
453
|
+
stream: bool = False,
|
|
454
|
+
indent: str | None = None,
|
|
400
455
|
input: str | bytes | None = None,
|
|
401
456
|
check: bool = False,
|
|
402
457
|
dry_run: bool | None = None,
|
|
@@ -404,6 +459,7 @@ class Pipeline:
|
|
|
404
459
|
is_dry = _DRY_RUN if dry_run is None else dry_run
|
|
405
460
|
cmd_display = " | ".join(" ".join(c.to_list()) for c in self.commands)
|
|
406
461
|
effective_cwd = str(cwd)
|
|
462
|
+
effective_indent = get_default_indent() if indent is None else indent
|
|
407
463
|
|
|
408
464
|
if log:
|
|
409
465
|
if effective_cwd != ".":
|
|
@@ -429,7 +485,6 @@ class Pipeline:
|
|
|
429
485
|
|
|
430
486
|
for i, cmd in enumerate(self.commands):
|
|
431
487
|
is_first = i == 0
|
|
432
|
-
is_last = i == len(self.commands) - 1
|
|
433
488
|
|
|
434
489
|
if is_first:
|
|
435
490
|
stdin = subprocess.PIPE if input is not None else None
|
|
@@ -458,10 +513,46 @@ class Pipeline:
|
|
|
458
513
|
processes.append(proc)
|
|
459
514
|
|
|
460
515
|
last_proc = processes[-1]
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
516
|
+
|
|
517
|
+
if stream:
|
|
518
|
+
stdout_chunks: list[Any] = []
|
|
519
|
+
stderr_chunks: list[Any] = []
|
|
520
|
+
|
|
521
|
+
def reader(pipe, chunks, dest_stream):
|
|
522
|
+
if pipe is None:
|
|
523
|
+
return
|
|
524
|
+
try:
|
|
525
|
+
if text:
|
|
526
|
+
for line in iter(pipe.readline, ""):
|
|
527
|
+
chunks.append(line)
|
|
528
|
+
if not line.strip():
|
|
529
|
+
dest_stream.write(line)
|
|
530
|
+
else:
|
|
531
|
+
dest_stream.write(f"{effective_indent}{line}")
|
|
532
|
+
dest_stream.flush()
|
|
533
|
+
else:
|
|
534
|
+
target = getattr(dest_stream, "buffer", dest_stream)
|
|
535
|
+
indent_bytes = effective_indent.encode("utf-8")
|
|
536
|
+
for line in iter(pipe.readline, b""):
|
|
537
|
+
chunks.append(line)
|
|
538
|
+
if not line.strip():
|
|
539
|
+
target.write(line)
|
|
540
|
+
else:
|
|
541
|
+
target.write(indent_bytes + line)
|
|
542
|
+
target.flush()
|
|
543
|
+
finally:
|
|
544
|
+
pipe.close()
|
|
545
|
+
|
|
546
|
+
t_out = threading.Thread(
|
|
547
|
+
target=reader, args=(last_proc.stdout, stdout_chunks, sys.stdout)
|
|
548
|
+
)
|
|
549
|
+
t_err = threading.Thread(
|
|
550
|
+
target=reader, args=(last_proc.stderr, stderr_chunks, sys.stderr)
|
|
551
|
+
)
|
|
552
|
+
t_out.start()
|
|
553
|
+
t_err.start()
|
|
554
|
+
|
|
555
|
+
if input is not None and processes[0].stdin:
|
|
465
556
|
try:
|
|
466
557
|
if text and isinstance(input, str):
|
|
467
558
|
processes[0].stdin.write(input)
|
|
@@ -470,9 +561,30 @@ class Pipeline:
|
|
|
470
561
|
processes[0].stdin.close()
|
|
471
562
|
except BrokenPipeError:
|
|
472
563
|
pass
|
|
473
|
-
|
|
564
|
+
|
|
565
|
+
last_proc.wait()
|
|
566
|
+
t_out.join()
|
|
567
|
+
t_err.join()
|
|
568
|
+
|
|
569
|
+
stdout_res = "".join(stdout_chunks) if text else b"".join(stdout_chunks)
|
|
570
|
+
stderr_res = "".join(stderr_chunks) if text else b"".join(stderr_chunks)
|
|
474
571
|
else:
|
|
475
|
-
|
|
572
|
+
if input is not None and processes[0].stdin:
|
|
573
|
+
stdout_res, stderr_res = last_proc.communicate(
|
|
574
|
+
input=input if len(processes) == 1 else None
|
|
575
|
+
)
|
|
576
|
+
if len(processes) > 1 and processes[0].stdin:
|
|
577
|
+
try:
|
|
578
|
+
if text and isinstance(input, str):
|
|
579
|
+
processes[0].stdin.write(input)
|
|
580
|
+
elif not text and isinstance(input, bytes):
|
|
581
|
+
processes[0].stdin.write(input)
|
|
582
|
+
processes[0].stdin.close()
|
|
583
|
+
except BrokenPipeError:
|
|
584
|
+
pass
|
|
585
|
+
stdout_res, stderr_res = last_proc.communicate()
|
|
586
|
+
else:
|
|
587
|
+
stdout_res, stderr_res = last_proc.communicate()
|
|
476
588
|
|
|
477
589
|
for p in processes[:-1]:
|
|
478
590
|
if p.stderr:
|
|
@@ -484,12 +596,25 @@ class Pipeline:
|
|
|
484
596
|
|
|
485
597
|
if log:
|
|
486
598
|
if return_code != 0:
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
599
|
+
if stream:
|
|
600
|
+
log_action(
|
|
601
|
+
"Failed",
|
|
602
|
+
f"Command failed with exit code {return_code}",
|
|
603
|
+
"red",
|
|
604
|
+
)
|
|
605
|
+
else:
|
|
606
|
+
err_msg = (
|
|
607
|
+
stderr_res
|
|
608
|
+
if isinstance(stderr_res, str)
|
|
609
|
+
else stderr_res.decode("utf-8", errors="replace")
|
|
610
|
+
)
|
|
611
|
+
log_action(
|
|
612
|
+
"Failed",
|
|
613
|
+
f"Command failed with exit code {return_code}",
|
|
614
|
+
"red",
|
|
615
|
+
)
|
|
616
|
+
if err_msg.strip():
|
|
617
|
+
log_indented(err_msg.strip(), indent=effective_indent)
|
|
493
618
|
else:
|
|
494
619
|
log_action("Success", f"Took {elapsed:.3f}s", "green")
|
|
495
620
|
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Any, Literal
|
|
5
|
+
from rich import print as rich_print
|
|
6
|
+
from rich.markup import escape
|
|
7
|
+
|
|
8
|
+
ActionColor = Literal["red", "green", "yellow", "blue", "magenta", "cyan", "white"]
|
|
9
|
+
|
|
10
|
+
_QUIET = False
|
|
11
|
+
_DEFAULT_INDENT = " "
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def set_quiet(quiet: bool) -> None:
|
|
15
|
+
"""Set global quiet mode for logging."""
|
|
16
|
+
global _QUIET
|
|
17
|
+
_QUIET = quiet
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def is_quiet() -> bool:
|
|
21
|
+
"""Check if quiet mode is enabled."""
|
|
22
|
+
return _QUIET
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def set_default_indent(indent: str) -> None:
|
|
26
|
+
"""Set global default indentation string for command outputs."""
|
|
27
|
+
global _DEFAULT_INDENT
|
|
28
|
+
_DEFAULT_INDENT = indent
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_default_indent() -> str:
|
|
32
|
+
"""Get the global default indentation string."""
|
|
33
|
+
return _DEFAULT_INDENT
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def log_action(
|
|
37
|
+
action_name: str,
|
|
38
|
+
action_message: str,
|
|
39
|
+
action_color: ActionColor = "white",
|
|
40
|
+
) -> None:
|
|
41
|
+
"""Print a formatted action log line if quiet mode is not enabled."""
|
|
42
|
+
if _QUIET:
|
|
43
|
+
return
|
|
44
|
+
prefix = f"[bold {action_color}]{action_name:>10}[/bold {action_color}]: "
|
|
45
|
+
lines = action_message.splitlines()
|
|
46
|
+
if not lines:
|
|
47
|
+
rich_print(prefix.rstrip())
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
rich_print(f"{prefix}{escape(lines[0])}")
|
|
51
|
+
# Indent subsequent lines of a multiline action message to align after the prefix
|
|
52
|
+
indent_pad = " " * 12
|
|
53
|
+
for line in lines[1:]:
|
|
54
|
+
rich_print(f"{indent_pad}{escape(line)}")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def log_indented(
|
|
58
|
+
message: str,
|
|
59
|
+
indent: str | None = None,
|
|
60
|
+
stream: Any = sys.stdout,
|
|
61
|
+
) -> None:
|
|
62
|
+
"""Print multiline text with each line indented."""
|
|
63
|
+
if _QUIET or not message:
|
|
64
|
+
return
|
|
65
|
+
eff_indent = _DEFAULT_INDENT if indent is None else indent
|
|
66
|
+
for line in message.splitlines():
|
|
67
|
+
if not line.strip():
|
|
68
|
+
stream.write("\n")
|
|
69
|
+
else:
|
|
70
|
+
stream.write(f"{eff_indent}{line}\n")
|
|
71
|
+
stream.flush()
|
puild-0.1.0/src/puild/logging.py
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
from typing import Literal
|
|
2
|
-
from rich import print as rich_print
|
|
3
|
-
|
|
4
|
-
ActionColor = Literal["red", "green", "yellow", "blue", "magenta", "cyan", "white"]
|
|
5
|
-
|
|
6
|
-
_QUIET = False
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def set_quiet(quiet: bool) -> None:
|
|
10
|
-
"""Set global quiet mode for logging."""
|
|
11
|
-
global _QUIET
|
|
12
|
-
_QUIET = quiet
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def is_quiet() -> bool:
|
|
16
|
-
"""Check if quiet mode is enabled."""
|
|
17
|
-
return _QUIET
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def log_action(
|
|
21
|
-
action_name: str,
|
|
22
|
-
action_message: str,
|
|
23
|
-
action_color: ActionColor = "white",
|
|
24
|
-
) -> None:
|
|
25
|
-
"""Print a formatted action log line if quiet mode is not enabled."""
|
|
26
|
-
if _QUIET:
|
|
27
|
-
return
|
|
28
|
-
rich_print(
|
|
29
|
-
f"[bold {action_color}]{action_name:>10}[/bold {action_color}]: {action_message}"
|
|
30
|
-
)
|
|
File without changes
|