puild 0.1.2__tar.gz → 0.2.1__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.2 → puild-0.2.1}/PKG-INFO +27 -1
- {puild-0.1.2 → puild-0.2.1}/README.md +26 -0
- {puild-0.1.2 → puild-0.2.1}/pyproject.toml +1 -1
- {puild-0.1.2 → puild-0.2.1}/pyproject.toml.orig +1 -1
- {puild-0.1.2 → puild-0.2.1}/src/puild/__init__.py +4 -0
- {puild-0.1.2 → puild-0.2.1}/src/puild/command.py +29 -14
- {puild-0.1.2 → puild-0.2.1}/src/puild/fs.py +1 -1
- puild-0.2.1/src/puild/logging.py +211 -0
- puild-0.1.2/src/puild/logging.py +0 -71
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: puild
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
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
|
|
@@ -116,3 +116,29 @@ copy("src/config.json", "build/config.json")
|
|
|
116
116
|
# Remove files or directories
|
|
117
117
|
rm("build", recursive=True)
|
|
118
118
|
```
|
|
119
|
+
|
|
120
|
+
### 7. Logging & File Routing
|
|
121
|
+
|
|
122
|
+
`puild` uses Python's standard `logging` library (`logging.getLogger("puild")`). You can route logs to a file, rotate files, or customize levels:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from puild import Command, configure_logging
|
|
126
|
+
|
|
127
|
+
# Route logs to both console and a log file
|
|
128
|
+
configure_logging(log_file="build.log")
|
|
129
|
+
|
|
130
|
+
# Or log strictly to a file without console output
|
|
131
|
+
configure_logging(log_file="build.log", console=False)
|
|
132
|
+
|
|
133
|
+
# Or with log rotation
|
|
134
|
+
configure_logging(log_file="build.log", max_bytes=10_000_000, backup_count=3)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
You can also attach standard Python `logging` handlers directly:
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
import logging
|
|
141
|
+
|
|
142
|
+
handler = logging.FileHandler("custom.log")
|
|
143
|
+
logging.getLogger("puild").addHandler(handler)
|
|
144
|
+
```
|
|
@@ -108,3 +108,29 @@ copy("src/config.json", "build/config.json")
|
|
|
108
108
|
# Remove files or directories
|
|
109
109
|
rm("build", recursive=True)
|
|
110
110
|
```
|
|
111
|
+
|
|
112
|
+
### 7. Logging & File Routing
|
|
113
|
+
|
|
114
|
+
`puild` uses Python's standard `logging` library (`logging.getLogger("puild")`). You can route logs to a file, rotate files, or customize levels:
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from puild import Command, configure_logging
|
|
118
|
+
|
|
119
|
+
# Route logs to both console and a log file
|
|
120
|
+
configure_logging(log_file="build.log")
|
|
121
|
+
|
|
122
|
+
# Or log strictly to a file without console output
|
|
123
|
+
configure_logging(log_file="build.log", console=False)
|
|
124
|
+
|
|
125
|
+
# Or with log rotation
|
|
126
|
+
configure_logging(log_file="build.log", max_bytes=10_000_000, backup_count=3)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
You can also attach standard Python `logging` handlers directly:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
import logging
|
|
133
|
+
|
|
134
|
+
handler = logging.FileHandler("custom.log")
|
|
135
|
+
logging.getLogger("puild").addHandler(handler)
|
|
136
|
+
```
|
|
@@ -1,10 +1,12 @@
|
|
|
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
3
|
from puild.logging import (
|
|
4
|
+
configure_logging,
|
|
4
5
|
get_default_indent,
|
|
5
6
|
is_quiet,
|
|
6
7
|
log_action,
|
|
7
8
|
log_indented,
|
|
9
|
+
logger,
|
|
8
10
|
set_default_indent,
|
|
9
11
|
set_quiet,
|
|
10
12
|
)
|
|
@@ -13,6 +15,7 @@ __all__ = [
|
|
|
13
15
|
"Command",
|
|
14
16
|
"Pipeline",
|
|
15
17
|
"Result",
|
|
18
|
+
"configure_logging",
|
|
16
19
|
"copy",
|
|
17
20
|
"find_files",
|
|
18
21
|
"get_default_indent",
|
|
@@ -20,6 +23,7 @@ __all__ = [
|
|
|
20
23
|
"is_quiet",
|
|
21
24
|
"log_action",
|
|
22
25
|
"log_indented",
|
|
26
|
+
"logger",
|
|
23
27
|
"mkdir",
|
|
24
28
|
"needs_rebuild",
|
|
25
29
|
"rm",
|
|
@@ -14,7 +14,7 @@ from puild.logging import (
|
|
|
14
14
|
get_default_indent,
|
|
15
15
|
log_action,
|
|
16
16
|
log_indented,
|
|
17
|
-
|
|
17
|
+
write_stream_to_file_handlers,
|
|
18
18
|
)
|
|
19
19
|
|
|
20
20
|
_DRY_RUN = False
|
|
@@ -262,6 +262,7 @@ class Command:
|
|
|
262
262
|
input=input,
|
|
263
263
|
stdout=f,
|
|
264
264
|
stderr=subprocess.PIPE if capture else None,
|
|
265
|
+
check=False,
|
|
265
266
|
)
|
|
266
267
|
stdout_res = empty_out
|
|
267
268
|
stderr_res = res.stderr if capture and res.stderr is not None else empty_out
|
|
@@ -275,6 +276,7 @@ class Command:
|
|
|
275
276
|
env=merged_env,
|
|
276
277
|
input=input,
|
|
277
278
|
capture_output=False,
|
|
279
|
+
check=False,
|
|
278
280
|
)
|
|
279
281
|
stdout_res = empty_out
|
|
280
282
|
stderr_res = empty_out
|
|
@@ -299,6 +301,7 @@ class Command:
|
|
|
299
301
|
env=merged_env,
|
|
300
302
|
input=input,
|
|
301
303
|
capture_output=True,
|
|
304
|
+
check=False,
|
|
302
305
|
)
|
|
303
306
|
stdout_res = res.stdout
|
|
304
307
|
stderr_res = res.stderr
|
|
@@ -366,9 +369,9 @@ class Command:
|
|
|
366
369
|
)
|
|
367
370
|
|
|
368
371
|
if input_data is not None and proc.stdin:
|
|
369
|
-
if text and isinstance(input_data, str)
|
|
370
|
-
|
|
371
|
-
|
|
372
|
+
if (text and isinstance(input_data, str)) or (
|
|
373
|
+
not text and isinstance(input_data, bytes)
|
|
374
|
+
):
|
|
372
375
|
proc.stdin.write(input_data)
|
|
373
376
|
proc.stdin.close()
|
|
374
377
|
|
|
@@ -385,8 +388,11 @@ class Command:
|
|
|
385
388
|
chunks.append(line)
|
|
386
389
|
if not line.strip():
|
|
387
390
|
dest_stream.write(line)
|
|
391
|
+
write_stream_to_file_handlers(line)
|
|
388
392
|
else:
|
|
389
|
-
|
|
393
|
+
formatted = f"{indent}{line}"
|
|
394
|
+
dest_stream.write(formatted)
|
|
395
|
+
write_stream_to_file_handlers(formatted)
|
|
390
396
|
dest_stream.flush()
|
|
391
397
|
else:
|
|
392
398
|
target = getattr(dest_stream, "buffer", dest_stream)
|
|
@@ -396,8 +402,11 @@ class Command:
|
|
|
396
402
|
chunks.append(line)
|
|
397
403
|
if not line.strip():
|
|
398
404
|
target.write(line)
|
|
405
|
+
write_stream_to_file_handlers(line.decode("utf-8", errors="replace"))
|
|
399
406
|
else:
|
|
400
|
-
|
|
407
|
+
formatted_bytes = indent_bytes + line
|
|
408
|
+
target.write(formatted_bytes)
|
|
409
|
+
write_stream_to_file_handlers(formatted_bytes.decode("utf-8", errors="replace"))
|
|
401
410
|
target.flush()
|
|
402
411
|
finally:
|
|
403
412
|
pipe.close()
|
|
@@ -527,8 +536,11 @@ class Pipeline:
|
|
|
527
536
|
chunks.append(line)
|
|
528
537
|
if not line.strip():
|
|
529
538
|
dest_stream.write(line)
|
|
539
|
+
write_stream_to_file_handlers(line)
|
|
530
540
|
else:
|
|
531
|
-
|
|
541
|
+
formatted = f"{effective_indent}{line}"
|
|
542
|
+
dest_stream.write(formatted)
|
|
543
|
+
write_stream_to_file_handlers(formatted)
|
|
532
544
|
dest_stream.flush()
|
|
533
545
|
else:
|
|
534
546
|
target = getattr(dest_stream, "buffer", dest_stream)
|
|
@@ -537,8 +549,11 @@ class Pipeline:
|
|
|
537
549
|
chunks.append(line)
|
|
538
550
|
if not line.strip():
|
|
539
551
|
target.write(line)
|
|
552
|
+
write_stream_to_file_handlers(line.decode("utf-8", errors="replace"))
|
|
540
553
|
else:
|
|
541
|
-
|
|
554
|
+
formatted_bytes = indent_bytes + line
|
|
555
|
+
target.write(formatted_bytes)
|
|
556
|
+
write_stream_to_file_handlers(formatted_bytes.decode("utf-8", errors="replace"))
|
|
542
557
|
target.flush()
|
|
543
558
|
finally:
|
|
544
559
|
pipe.close()
|
|
@@ -554,9 +569,9 @@ class Pipeline:
|
|
|
554
569
|
|
|
555
570
|
if input is not None and processes[0].stdin:
|
|
556
571
|
try:
|
|
557
|
-
if text and isinstance(input, str)
|
|
558
|
-
|
|
559
|
-
|
|
572
|
+
if (text and isinstance(input, str)) or (
|
|
573
|
+
not text and isinstance(input, bytes)
|
|
574
|
+
):
|
|
560
575
|
processes[0].stdin.write(input)
|
|
561
576
|
processes[0].stdin.close()
|
|
562
577
|
except BrokenPipeError:
|
|
@@ -575,9 +590,9 @@ class Pipeline:
|
|
|
575
590
|
)
|
|
576
591
|
if len(processes) > 1 and processes[0].stdin:
|
|
577
592
|
try:
|
|
578
|
-
if text and isinstance(input, str)
|
|
579
|
-
|
|
580
|
-
|
|
593
|
+
if (text and isinstance(input, str)) or (
|
|
594
|
+
not text and isinstance(input, bytes)
|
|
595
|
+
):
|
|
581
596
|
processes[0].stdin.write(input)
|
|
582
597
|
processes[0].stdin.close()
|
|
583
598
|
except BrokenPipeError:
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
import logging
|
|
5
|
+
import re
|
|
6
|
+
import sys
|
|
7
|
+
from logging.handlers import RotatingFileHandler
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Literal
|
|
10
|
+
|
|
11
|
+
from rich import print as rich_print
|
|
12
|
+
from rich.markup import escape
|
|
13
|
+
|
|
14
|
+
ActionColor = Literal["red", "green", "yellow", "blue", "magenta", "cyan", "white"]
|
|
15
|
+
|
|
16
|
+
_QUIET = False
|
|
17
|
+
_DEFAULT_INDENT = " "
|
|
18
|
+
_ANSI_RE = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def strip_ansi(text: str) -> str:
|
|
22
|
+
"""Remove ANSI escape sequences from text."""
|
|
23
|
+
return _ANSI_RE.sub("", text)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def set_quiet(quiet: bool) -> None:
|
|
27
|
+
"""Set global quiet mode for logging."""
|
|
28
|
+
global _QUIET
|
|
29
|
+
_QUIET = quiet
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def is_quiet() -> bool:
|
|
33
|
+
"""Check if quiet mode is enabled."""
|
|
34
|
+
return _QUIET
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def set_default_indent(indent: str) -> None:
|
|
38
|
+
"""Set global default indentation string for command outputs."""
|
|
39
|
+
global _DEFAULT_INDENT
|
|
40
|
+
_DEFAULT_INDENT = indent
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_default_indent() -> str:
|
|
44
|
+
"""Get the global default indentation string."""
|
|
45
|
+
return _DEFAULT_INDENT
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ConsoleActionHandler(logging.Handler):
|
|
49
|
+
"""Handler that formats action logs to the terminal using rich."""
|
|
50
|
+
|
|
51
|
+
def emit(self, record: logging.LogRecord) -> None:
|
|
52
|
+
if _QUIET:
|
|
53
|
+
return
|
|
54
|
+
try:
|
|
55
|
+
action = getattr(record, "action", None)
|
|
56
|
+
color = getattr(record, "color", "white")
|
|
57
|
+
msg = record.getMessage()
|
|
58
|
+
|
|
59
|
+
if action:
|
|
60
|
+
prefix = f"[bold {color}]{action:>10}[/bold {color}]: "
|
|
61
|
+
lines = msg.splitlines()
|
|
62
|
+
if not lines:
|
|
63
|
+
rich_print(prefix.rstrip())
|
|
64
|
+
return
|
|
65
|
+
rich_print(f"{prefix}{escape(lines[0])}")
|
|
66
|
+
indent_pad = " " * 12
|
|
67
|
+
for line in lines[1:]:
|
|
68
|
+
rich_print(f"{indent_pad}{escape(line)}")
|
|
69
|
+
else:
|
|
70
|
+
# Direct message or indented block
|
|
71
|
+
if "\x1b" in msg:
|
|
72
|
+
sys.stdout.write(msg + "\n")
|
|
73
|
+
sys.stdout.flush()
|
|
74
|
+
else:
|
|
75
|
+
for line in msg.splitlines():
|
|
76
|
+
if not line.strip():
|
|
77
|
+
sys.stdout.write("\n")
|
|
78
|
+
else:
|
|
79
|
+
sys.stdout.write(f"{line}\n")
|
|
80
|
+
sys.stdout.flush()
|
|
81
|
+
except Exception: # noqa: BLE001
|
|
82
|
+
self.handleError(record)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class FileActionFormatter(logging.Formatter):
|
|
86
|
+
"""Formats log records for file output without ANSI codes or markup."""
|
|
87
|
+
|
|
88
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
89
|
+
action = getattr(record, "action", None)
|
|
90
|
+
raw_msg = record.getMessage()
|
|
91
|
+
clean_msg = strip_ansi(raw_msg)
|
|
92
|
+
asctime = self.formatTime(record, self.datefmt or "%Y-%m-%d %H:%M:%S")
|
|
93
|
+
|
|
94
|
+
if action:
|
|
95
|
+
lines = clean_msg.splitlines()
|
|
96
|
+
if not lines:
|
|
97
|
+
return f"{asctime} [{record.levelname:<5}] {action:>10}:"
|
|
98
|
+
first_line = f"{asctime} [{record.levelname:<5}] {action:>10}: {lines[0]}"
|
|
99
|
+
if len(lines) == 1:
|
|
100
|
+
return first_line
|
|
101
|
+
indent_pad = " " * 12
|
|
102
|
+
rest = [f"{asctime} [{record.levelname:<5}] {indent_pad}{line}" for line in lines[1:]]
|
|
103
|
+
return "\n".join([first_line, *rest])
|
|
104
|
+
else:
|
|
105
|
+
lines = clean_msg.splitlines()
|
|
106
|
+
if not lines:
|
|
107
|
+
return ""
|
|
108
|
+
return "\n".join([f"{asctime} [{record.levelname:<5}] {line}" for line in lines])
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# Core package logger
|
|
112
|
+
logger = logging.getLogger("puild")
|
|
113
|
+
logger.setLevel(logging.INFO)
|
|
114
|
+
# Attach default console handler
|
|
115
|
+
_default_console_handler = ConsoleActionHandler()
|
|
116
|
+
logger.addHandler(_default_console_handler)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def configure_logging(
|
|
120
|
+
log_file: str | Path | None = None,
|
|
121
|
+
level: int | str = logging.INFO,
|
|
122
|
+
console: bool = True,
|
|
123
|
+
file_mode: str = "a",
|
|
124
|
+
max_bytes: int = 0,
|
|
125
|
+
backup_count: int = 0,
|
|
126
|
+
datefmt: str = "%Y-%m-%d %H:%M:%S",
|
|
127
|
+
) -> logging.Logger:
|
|
128
|
+
"""Configure puild logger to console and an optional file destination."""
|
|
129
|
+
if isinstance(level, str):
|
|
130
|
+
level = getattr(logging, level.upper(), logging.INFO)
|
|
131
|
+
logger.setLevel(level)
|
|
132
|
+
|
|
133
|
+
# Clean and close existing handlers
|
|
134
|
+
for h in logger.handlers:
|
|
135
|
+
h.close()
|
|
136
|
+
logger.handlers.clear()
|
|
137
|
+
|
|
138
|
+
if console:
|
|
139
|
+
c_handler = ConsoleActionHandler()
|
|
140
|
+
c_handler.setLevel(level)
|
|
141
|
+
logger.addHandler(c_handler)
|
|
142
|
+
|
|
143
|
+
if log_file:
|
|
144
|
+
p = Path(log_file)
|
|
145
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
146
|
+
if max_bytes > 0:
|
|
147
|
+
f_handler = RotatingFileHandler(
|
|
148
|
+
p, maxBytes=max_bytes, backupCount=backup_count, encoding="utf-8"
|
|
149
|
+
)
|
|
150
|
+
else:
|
|
151
|
+
f_handler = logging.FileHandler(p, mode=file_mode, encoding="utf-8")
|
|
152
|
+
f_handler.setLevel(level)
|
|
153
|
+
f_handler.setFormatter(FileActionFormatter(datefmt=datefmt))
|
|
154
|
+
logger.addHandler(f_handler)
|
|
155
|
+
|
|
156
|
+
return logger
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def log_action(
|
|
160
|
+
action_name: str,
|
|
161
|
+
action_message: str,
|
|
162
|
+
action_color: ActionColor = "white",
|
|
163
|
+
level: int = logging.INFO,
|
|
164
|
+
) -> None:
|
|
165
|
+
"""Log an action using the standard puild logger."""
|
|
166
|
+
if action_name in ("Failed", "Error"):
|
|
167
|
+
level = logging.ERROR
|
|
168
|
+
logger.log(
|
|
169
|
+
level,
|
|
170
|
+
action_message,
|
|
171
|
+
extra={"action": action_name, "color": action_color},
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def log_indented(
|
|
176
|
+
message: str,
|
|
177
|
+
indent: str | None = None,
|
|
178
|
+
stream: Any = sys.stdout,
|
|
179
|
+
level: int = logging.INFO,
|
|
180
|
+
) -> None:
|
|
181
|
+
"""Log multiline text with each line indented."""
|
|
182
|
+
if not message:
|
|
183
|
+
return
|
|
184
|
+
eff_indent = _DEFAULT_INDENT if indent is None else indent
|
|
185
|
+
if stream is not sys.stdout:
|
|
186
|
+
for line in message.splitlines():
|
|
187
|
+
if not line.strip():
|
|
188
|
+
stream.write("\n")
|
|
189
|
+
else:
|
|
190
|
+
stream.write(f"{eff_indent}{line}\n")
|
|
191
|
+
stream.flush()
|
|
192
|
+
return
|
|
193
|
+
|
|
194
|
+
lines: list[str] = []
|
|
195
|
+
for line in message.splitlines():
|
|
196
|
+
if not line.strip():
|
|
197
|
+
lines.append("")
|
|
198
|
+
else:
|
|
199
|
+
lines.append(f"{eff_indent}{line}")
|
|
200
|
+
formatted = "\n".join(lines)
|
|
201
|
+
logger.log(level, formatted, extra={"is_indented": True})
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def write_stream_to_file_handlers(text: str) -> None:
|
|
205
|
+
"""Write streaming command line output directly to registered FileHandlers."""
|
|
206
|
+
clean = strip_ansi(text)
|
|
207
|
+
for h in logger.handlers:
|
|
208
|
+
if isinstance(h, logging.FileHandler) and h.stream is not None:
|
|
209
|
+
with contextlib.suppress(OSError):
|
|
210
|
+
h.stream.write(clean)
|
|
211
|
+
h.stream.flush()
|
puild-0.1.2/src/puild/logging.py
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
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()
|