puild 0.2.0__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.2.0 → puild-0.2.1}/PKG-INFO +27 -1
- {puild-0.2.0 → puild-0.2.1}/README.md +26 -0
- {puild-0.2.0 → puild-0.2.1}/pyproject.toml +1 -1
- {puild-0.2.0 → puild-0.2.1}/pyproject.toml.orig +1 -1
- {puild-0.2.0 → puild-0.2.1}/src/puild/__init__.py +4 -0
- {puild-0.2.0 → puild-0.2.1}/src/puild/command.py +29 -13
- puild-0.2.1/src/puild/logging.py +211 -0
- puild-0.2.0/src/puild/logging.py +0 -71
- {puild-0.2.0 → puild-0.2.1}/src/puild/fs.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: puild
|
|
3
|
-
Version: 0.2.
|
|
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,6 +14,7 @@ from puild.logging import (
|
|
|
14
14
|
get_default_indent,
|
|
15
15
|
log_action,
|
|
16
16
|
log_indented,
|
|
17
|
+
write_stream_to_file_handlers,
|
|
17
18
|
)
|
|
18
19
|
|
|
19
20
|
_DRY_RUN = False
|
|
@@ -261,6 +262,7 @@ class Command:
|
|
|
261
262
|
input=input,
|
|
262
263
|
stdout=f,
|
|
263
264
|
stderr=subprocess.PIPE if capture else None,
|
|
265
|
+
check=False,
|
|
264
266
|
)
|
|
265
267
|
stdout_res = empty_out
|
|
266
268
|
stderr_res = res.stderr if capture and res.stderr is not None else empty_out
|
|
@@ -274,6 +276,7 @@ class Command:
|
|
|
274
276
|
env=merged_env,
|
|
275
277
|
input=input,
|
|
276
278
|
capture_output=False,
|
|
279
|
+
check=False,
|
|
277
280
|
)
|
|
278
281
|
stdout_res = empty_out
|
|
279
282
|
stderr_res = empty_out
|
|
@@ -298,6 +301,7 @@ class Command:
|
|
|
298
301
|
env=merged_env,
|
|
299
302
|
input=input,
|
|
300
303
|
capture_output=True,
|
|
304
|
+
check=False,
|
|
301
305
|
)
|
|
302
306
|
stdout_res = res.stdout
|
|
303
307
|
stderr_res = res.stderr
|
|
@@ -365,9 +369,9 @@ class Command:
|
|
|
365
369
|
)
|
|
366
370
|
|
|
367
371
|
if input_data is not None and proc.stdin:
|
|
368
|
-
if text and isinstance(input_data, str)
|
|
369
|
-
|
|
370
|
-
|
|
372
|
+
if (text and isinstance(input_data, str)) or (
|
|
373
|
+
not text and isinstance(input_data, bytes)
|
|
374
|
+
):
|
|
371
375
|
proc.stdin.write(input_data)
|
|
372
376
|
proc.stdin.close()
|
|
373
377
|
|
|
@@ -384,8 +388,11 @@ class Command:
|
|
|
384
388
|
chunks.append(line)
|
|
385
389
|
if not line.strip():
|
|
386
390
|
dest_stream.write(line)
|
|
391
|
+
write_stream_to_file_handlers(line)
|
|
387
392
|
else:
|
|
388
|
-
|
|
393
|
+
formatted = f"{indent}{line}"
|
|
394
|
+
dest_stream.write(formatted)
|
|
395
|
+
write_stream_to_file_handlers(formatted)
|
|
389
396
|
dest_stream.flush()
|
|
390
397
|
else:
|
|
391
398
|
target = getattr(dest_stream, "buffer", dest_stream)
|
|
@@ -395,8 +402,11 @@ class Command:
|
|
|
395
402
|
chunks.append(line)
|
|
396
403
|
if not line.strip():
|
|
397
404
|
target.write(line)
|
|
405
|
+
write_stream_to_file_handlers(line.decode("utf-8", errors="replace"))
|
|
398
406
|
else:
|
|
399
|
-
|
|
407
|
+
formatted_bytes = indent_bytes + line
|
|
408
|
+
target.write(formatted_bytes)
|
|
409
|
+
write_stream_to_file_handlers(formatted_bytes.decode("utf-8", errors="replace"))
|
|
400
410
|
target.flush()
|
|
401
411
|
finally:
|
|
402
412
|
pipe.close()
|
|
@@ -526,8 +536,11 @@ class Pipeline:
|
|
|
526
536
|
chunks.append(line)
|
|
527
537
|
if not line.strip():
|
|
528
538
|
dest_stream.write(line)
|
|
539
|
+
write_stream_to_file_handlers(line)
|
|
529
540
|
else:
|
|
530
|
-
|
|
541
|
+
formatted = f"{effective_indent}{line}"
|
|
542
|
+
dest_stream.write(formatted)
|
|
543
|
+
write_stream_to_file_handlers(formatted)
|
|
531
544
|
dest_stream.flush()
|
|
532
545
|
else:
|
|
533
546
|
target = getattr(dest_stream, "buffer", dest_stream)
|
|
@@ -536,8 +549,11 @@ class Pipeline:
|
|
|
536
549
|
chunks.append(line)
|
|
537
550
|
if not line.strip():
|
|
538
551
|
target.write(line)
|
|
552
|
+
write_stream_to_file_handlers(line.decode("utf-8", errors="replace"))
|
|
539
553
|
else:
|
|
540
|
-
|
|
554
|
+
formatted_bytes = indent_bytes + line
|
|
555
|
+
target.write(formatted_bytes)
|
|
556
|
+
write_stream_to_file_handlers(formatted_bytes.decode("utf-8", errors="replace"))
|
|
541
557
|
target.flush()
|
|
542
558
|
finally:
|
|
543
559
|
pipe.close()
|
|
@@ -553,9 +569,9 @@ class Pipeline:
|
|
|
553
569
|
|
|
554
570
|
if input is not None and processes[0].stdin:
|
|
555
571
|
try:
|
|
556
|
-
if text and isinstance(input, str)
|
|
557
|
-
|
|
558
|
-
|
|
572
|
+
if (text and isinstance(input, str)) or (
|
|
573
|
+
not text and isinstance(input, bytes)
|
|
574
|
+
):
|
|
559
575
|
processes[0].stdin.write(input)
|
|
560
576
|
processes[0].stdin.close()
|
|
561
577
|
except BrokenPipeError:
|
|
@@ -574,9 +590,9 @@ class Pipeline:
|
|
|
574
590
|
)
|
|
575
591
|
if len(processes) > 1 and processes[0].stdin:
|
|
576
592
|
try:
|
|
577
|
-
if text and isinstance(input, str)
|
|
578
|
-
|
|
579
|
-
|
|
593
|
+
if (text and isinstance(input, str)) or (
|
|
594
|
+
not text and isinstance(input, bytes)
|
|
595
|
+
):
|
|
580
596
|
processes[0].stdin.write(input)
|
|
581
597
|
processes[0].stdin.close()
|
|
582
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.2.0/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()
|
|
File without changes
|