mdify-cli 3.1.0__tar.gz → 3.2.0__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.
- {mdify_cli-3.1.0 → mdify_cli-3.2.0}/PKG-INFO +1 -1
- {mdify_cli-3.1.0 → mdify_cli-3.2.0}/mdify/__init__.py +1 -1
- {mdify_cli-3.1.0 → mdify_cli-3.2.0}/mdify/cli.py +76 -56
- mdify_cli-3.2.0/mdify/formatting.py +138 -0
- {mdify_cli-3.1.0 → mdify_cli-3.2.0}/mdify_cli.egg-info/PKG-INFO +1 -1
- {mdify_cli-3.1.0 → mdify_cli-3.2.0}/pyproject.toml +1 -1
- mdify_cli-3.1.0/mdify/formatting.py +0 -29
- {mdify_cli-3.1.0 → mdify_cli-3.2.0}/LICENSE +0 -0
- {mdify_cli-3.1.0 → mdify_cli-3.2.0}/README.md +0 -0
- {mdify_cli-3.1.0 → mdify_cli-3.2.0}/assets/mdify.png +0 -0
- {mdify_cli-3.1.0 → mdify_cli-3.2.0}/mdify/__main__.py +0 -0
- {mdify_cli-3.1.0 → mdify_cli-3.2.0}/mdify/container.py +0 -0
- {mdify_cli-3.1.0 → mdify_cli-3.2.0}/mdify/docling_client.py +0 -0
- {mdify_cli-3.1.0 → mdify_cli-3.2.0}/mdify/ssh/__init__.py +0 -0
- {mdify_cli-3.1.0 → mdify_cli-3.2.0}/mdify/ssh/client.py +0 -0
- {mdify_cli-3.1.0 → mdify_cli-3.2.0}/mdify/ssh/models.py +0 -0
- {mdify_cli-3.1.0 → mdify_cli-3.2.0}/mdify/ssh/remote_container.py +0 -0
- {mdify_cli-3.1.0 → mdify_cli-3.2.0}/mdify/ssh/transfer.py +0 -0
- {mdify_cli-3.1.0 → mdify_cli-3.2.0}/mdify_cli.egg-info/SOURCES.txt +0 -0
- {mdify_cli-3.1.0 → mdify_cli-3.2.0}/mdify_cli.egg-info/dependency_links.txt +0 -0
- {mdify_cli-3.1.0 → mdify_cli-3.2.0}/mdify_cli.egg-info/entry_points.txt +0 -0
- {mdify_cli-3.1.0 → mdify_cli-3.2.0}/mdify_cli.egg-info/requires.txt +0 -0
- {mdify_cli-3.1.0 → mdify_cli-3.2.0}/mdify_cli.egg-info/top_level.txt +0 -0
- {mdify_cli-3.1.0 → mdify_cli-3.2.0}/setup.cfg +0 -0
- {mdify_cli-3.1.0 → mdify_cli-3.2.0}/tests/test_cli.py +0 -0
- {mdify_cli-3.1.0 → mdify_cli-3.2.0}/tests/test_container.py +0 -0
- {mdify_cli-3.1.0 → mdify_cli-3.2.0}/tests/test_docling_client.py +0 -0
- {mdify_cli-3.1.0 → mdify_cli-3.2.0}/tests/test_ssh_client.py +0 -0
|
@@ -275,17 +275,20 @@ def check_for_update(force: bool = False) -> None:
|
|
|
275
275
|
|
|
276
276
|
if not _compare_versions(__version__, remote_version):
|
|
277
277
|
if force:
|
|
278
|
-
|
|
278
|
+
from mdify.formatting import Colorizer
|
|
279
|
+
color = Colorizer(sys.stdout)
|
|
280
|
+
print(color.success(f"✓ mdify is up to date (v{__version__})"))
|
|
279
281
|
return
|
|
280
282
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
print(f"
|
|
284
|
-
print(
|
|
285
|
-
print(f"{'
|
|
286
|
-
print(f"
|
|
287
|
-
print(f"
|
|
288
|
-
print(f"
|
|
283
|
+
from mdify.formatting import Colorizer
|
|
284
|
+
color = Colorizer(sys.stdout)
|
|
285
|
+
print(f"\n{color.bright_yellow('=' * 60)}")
|
|
286
|
+
print(color.bold_yellow("🎉 A new version of mdify-cli is available!"))
|
|
287
|
+
print(f"{color.dim_white(' Current:')} {__version__} → {color.bright_green(remote_version)}")
|
|
288
|
+
print(f"{color.bright_yellow('=' * 60)}")
|
|
289
|
+
print(f"\n{color.cyan('To upgrade, run:')}")
|
|
290
|
+
print(f" {color.bold('pipx upgrade mdify-cli')}")
|
|
291
|
+
print(f" {color.dim_white('# or: pip install --upgrade mdify-cli')}\n")
|
|
289
292
|
|
|
290
293
|
|
|
291
294
|
# =============================================================================
|
|
@@ -1113,57 +1116,57 @@ def main_async_remote(args) -> int:
|
|
|
1113
1116
|
|
|
1114
1117
|
# Connect to remote server
|
|
1115
1118
|
if not args.quiet:
|
|
1116
|
-
print(color.
|
|
1119
|
+
print(color.bright_cyan(f"🔗 Connecting to {color.bold(ssh_config.host)}:{ssh_config.port}..."), file=sys.stderr)
|
|
1117
1120
|
|
|
1118
1121
|
await ssh_client.connect()
|
|
1119
1122
|
|
|
1120
1123
|
if not args.quiet:
|
|
1121
|
-
print(color.
|
|
1124
|
+
print(color.success(f"✓ Connected to {ssh_config.host}"), file=sys.stderr)
|
|
1122
1125
|
|
|
1123
1126
|
# Validate remote resources if not skipped
|
|
1124
1127
|
if not args.remote_skip_validation:
|
|
1125
1128
|
if not args.quiet:
|
|
1126
|
-
print(color.cyan("Validating remote resources..."), file=sys.stderr)
|
|
1129
|
+
print(color.cyan("🔍 Validating remote resources..."), file=sys.stderr)
|
|
1127
1130
|
|
|
1128
1131
|
validation_result = await ssh_client.validate_remote_resources()
|
|
1129
1132
|
|
|
1130
1133
|
if not validation_result.get("can_connect"):
|
|
1131
1134
|
await ssh_client.disconnect()
|
|
1132
|
-
print("
|
|
1135
|
+
print(color.error("✗ Cannot connect to remote server"), file=sys.stderr)
|
|
1133
1136
|
return 1
|
|
1134
1137
|
|
|
1135
1138
|
if not validation_result.get("work_dir_writable"):
|
|
1136
1139
|
await ssh_client.disconnect()
|
|
1137
|
-
print(f"
|
|
1140
|
+
print(color.error(f"✗ Work directory not writable: {ssh_config.work_dir}"), file=sys.stderr)
|
|
1138
1141
|
return 1
|
|
1139
1142
|
|
|
1140
1143
|
if not validation_result.get("container_runtime_available"):
|
|
1141
1144
|
await ssh_client.disconnect()
|
|
1142
1145
|
runtime_str = ssh_config.container_runtime or "docker/podman"
|
|
1143
|
-
print(f"
|
|
1146
|
+
print(color.error(f"✗ Container runtime not available: {runtime_str}"), file=sys.stderr)
|
|
1144
1147
|
return 1
|
|
1145
1148
|
|
|
1146
1149
|
if not validation_result.get("disk_space_min_5gb"):
|
|
1147
|
-
print(f"
|
|
1150
|
+
print(color.warning(f"⚠ Less than 5GB available on remote"), file=sys.stderr)
|
|
1148
1151
|
if not args.yes and sys.stdin.isatty():
|
|
1149
1152
|
if not confirm_proceed("Continue anyway?"):
|
|
1150
1153
|
await ssh_client.disconnect()
|
|
1151
1154
|
return 130
|
|
1152
1155
|
|
|
1153
1156
|
if not validation_result.get("memory_min_2gb"):
|
|
1154
|
-
print(f"
|
|
1157
|
+
print(color.warning(f"⚠ Less than 2GB available memory on remote"), file=sys.stderr)
|
|
1155
1158
|
if not args.yes and sys.stdin.isatty():
|
|
1156
1159
|
if not confirm_proceed("Continue anyway?"):
|
|
1157
1160
|
await ssh_client.disconnect()
|
|
1158
1161
|
return 130
|
|
1159
1162
|
|
|
1160
1163
|
if not args.quiet:
|
|
1161
|
-
print(color.
|
|
1164
|
+
print(color.success("✓ All remote resources validated"), file=sys.stderr)
|
|
1162
1165
|
|
|
1163
1166
|
# If --remote-validate-only, exit here
|
|
1164
1167
|
if args.remote_validate_only:
|
|
1165
1168
|
await ssh_client.disconnect()
|
|
1166
|
-
print("Remote validation successful", file=sys.stderr)
|
|
1169
|
+
print(color.success("✓ Remote validation successful"), file=sys.stderr)
|
|
1167
1170
|
return 0
|
|
1168
1171
|
|
|
1169
1172
|
# Phase 2.4.2: File upload, remote conversion, and download
|
|
@@ -1175,7 +1178,9 @@ def main_async_remote(args) -> int:
|
|
|
1175
1178
|
print(f"Error: Input file or directory not found: {args.input}", file=sys.stderr)
|
|
1176
1179
|
return 1
|
|
1177
1180
|
|
|
1178
|
-
|
|
1181
|
+
# Store resolved path as base for relative path calculations
|
|
1182
|
+
input_base = input_path.resolve()
|
|
1183
|
+
files_to_convert = get_files_to_convert(input_base, args.glob, args.recursive)
|
|
1179
1184
|
|
|
1180
1185
|
if not files_to_convert:
|
|
1181
1186
|
await ssh_client.disconnect()
|
|
@@ -1285,19 +1290,10 @@ def main_async_remote(args) -> int:
|
|
|
1285
1290
|
|
|
1286
1291
|
# Determine output path
|
|
1287
1292
|
output_dir = Path(args.out_dir)
|
|
1293
|
+
output_file = get_output_path(input_file, input_base, output_dir, args.flat)
|
|
1288
1294
|
|
|
1289
|
-
#
|
|
1290
|
-
|
|
1291
|
-
try:
|
|
1292
|
-
rel_path = input_file.relative_to(input_path)
|
|
1293
|
-
output_subdir = output_dir / rel_path.parent
|
|
1294
|
-
except ValueError:
|
|
1295
|
-
output_subdir = output_dir
|
|
1296
|
-
else:
|
|
1297
|
-
output_subdir = output_dir
|
|
1298
|
-
|
|
1299
|
-
output_subdir.mkdir(parents=True, exist_ok=True)
|
|
1300
|
-
output_file = output_subdir / f"{input_file.stem}.md"
|
|
1295
|
+
# Ensure output directory exists
|
|
1296
|
+
output_file.parent.mkdir(parents=True, exist_ok=True)
|
|
1301
1297
|
|
|
1302
1298
|
# Check if output exists and skip if not overwrite
|
|
1303
1299
|
if output_file.exists() and not args.overwrite:
|
|
@@ -1531,13 +1527,21 @@ def main_async_remote(args) -> int:
|
|
|
1531
1527
|
|
|
1532
1528
|
# Print summary
|
|
1533
1529
|
print(color.cyan(f"\n{'='*60}"), file=sys.stderr)
|
|
1534
|
-
print(color.
|
|
1535
|
-
print(color.
|
|
1530
|
+
print(color.bold_cyan("📊 Remote Conversion Summary"), file=sys.stderr)
|
|
1531
|
+
print(color.cyan(f"{'='*60}"), file=sys.stderr)
|
|
1532
|
+
|
|
1533
|
+
# Format summary with emojis and colors
|
|
1534
|
+
total = len(files_to_convert)
|
|
1535
|
+
success_pct = f" ({successful}/{total})" if total > 0 else ""
|
|
1536
|
+
failed_pct = f" ({failed}/{total})" if total > 0 else ""
|
|
1537
|
+
skipped_pct = f" ({total - successful - failed}/{total})" if total > 0 else ""
|
|
1538
|
+
|
|
1539
|
+
print(f" {color.green('✓ Successful:')} {color.bold_green(str(successful))}{success_pct}", file=sys.stderr)
|
|
1536
1540
|
if failed > 0:
|
|
1537
|
-
print(color.
|
|
1538
|
-
|
|
1539
|
-
print(f"
|
|
1540
|
-
print(f" Total:
|
|
1541
|
+
print(f" {color.red('✗ Failed:')} {color.bold_red(str(failed))}{failed_pct}", file=sys.stderr)
|
|
1542
|
+
if (total - successful - failed) > 0:
|
|
1543
|
+
print(f" {color.yellow('⊘ Skipped:')} {color.bold_yellow(str(total - successful - failed))}{skipped_pct}", file=sys.stderr)
|
|
1544
|
+
print(f" {color.cyan('Total:')} {color.bold(str(total))}", file=sys.stderr)
|
|
1541
1545
|
print(color.cyan(f"{'='*60}"), file=sys.stderr)
|
|
1542
1546
|
|
|
1543
1547
|
return 0 if failed == 0 else 1
|
|
@@ -1585,7 +1589,9 @@ def main_async_remote(args) -> int:
|
|
|
1585
1589
|
|
|
1586
1590
|
def main() -> int:
|
|
1587
1591
|
"""Main entry point for the CLI."""
|
|
1588
|
-
|
|
1592
|
+
from mdify.formatting import Colorizer
|
|
1593
|
+
color = Colorizer(sys.stderr)
|
|
1594
|
+
print(color.bold_cyan(f"📄 mdify v{__version__}"), file=sys.stderr)
|
|
1589
1595
|
args = parse_args()
|
|
1590
1596
|
|
|
1591
1597
|
# Handle --check-update flag
|
|
@@ -1751,16 +1757,20 @@ def main() -> int:
|
|
|
1751
1757
|
total_size = sum(f.stat().st_size for f in files_to_convert)
|
|
1752
1758
|
|
|
1753
1759
|
if not args.quiet:
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
print(f"
|
|
1757
|
-
print(f"
|
|
1758
|
-
print(f"
|
|
1760
|
+
from mdify.formatting import Colorizer
|
|
1761
|
+
color_info = Colorizer(sys.stdout)
|
|
1762
|
+
print(f"{color_info.bright_cyan('📦 Found')} {color_info.bold(str(total_files))} {color_info.bright_cyan('file(s)')} {color_info.dim_white(f'({format_size(total_size)})')}")
|
|
1763
|
+
print(f"{color_info.cyan('📁 Source:')} {color_info.bright_white(str(input_path.resolve()))}")
|
|
1764
|
+
print(f"{color_info.cyan('💾 Output:')} {color_info.bright_white(str(output_dir.resolve()))}")
|
|
1765
|
+
print(f"{color_info.cyan('🐳 Runtime:')} {color_info.bright_white(runtime)}")
|
|
1766
|
+
print(f"{color_info.cyan('🖼️ Image:')} {color_info.dim_white(image)}")
|
|
1759
1767
|
print()
|
|
1760
1768
|
|
|
1761
1769
|
if args.mask:
|
|
1770
|
+
from mdify.formatting import Colorizer
|
|
1771
|
+
color_warn = Colorizer(sys.stderr)
|
|
1762
1772
|
print(
|
|
1763
|
-
"
|
|
1773
|
+
color_warn.warning("⚠ --mask is not supported with docling-serve and will be ignored"),
|
|
1764
1774
|
file=sys.stderr,
|
|
1765
1775
|
)
|
|
1766
1776
|
|
|
@@ -1777,7 +1787,9 @@ def main() -> int:
|
|
|
1777
1787
|
|
|
1778
1788
|
try:
|
|
1779
1789
|
if not args.quiet:
|
|
1780
|
-
|
|
1790
|
+
from mdify.formatting import Colorizer
|
|
1791
|
+
color_start = Colorizer(sys.stdout)
|
|
1792
|
+
print(f"{color_start.bright_cyan('▶️ Starting')} {color_start.bright_white('docling-serve')} {color_start.bright_cyan('container')}...\n")
|
|
1781
1793
|
|
|
1782
1794
|
# Apply resource profile
|
|
1783
1795
|
profile = RESOURCE_PROFILES[args.profile]
|
|
@@ -2016,22 +2028,30 @@ def main() -> int:
|
|
|
2016
2028
|
|
|
2017
2029
|
# Print summary
|
|
2018
2030
|
if not args.quiet:
|
|
2031
|
+
from mdify.formatting import Colorizer
|
|
2032
|
+
color_out = Colorizer(sys.stdout)
|
|
2019
2033
|
print()
|
|
2020
|
-
print("=" *
|
|
2021
|
-
print("Conversion Summary
|
|
2022
|
-
print(
|
|
2023
|
-
print(f"
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2034
|
+
print(color_out.cyan("=" * 60))
|
|
2035
|
+
print(color_out.bold_cyan("📊 Local Conversion Summary"))
|
|
2036
|
+
print(color_out.cyan("=" * 60))
|
|
2037
|
+
print(f" {color_out.cyan('Total files:')} {color_out.bold(str(total_files))}")
|
|
2038
|
+
if success_count > 0:
|
|
2039
|
+
print(f" {color_out.green('✓ Successful:')} {color_out.bold_green(str(success_count))}")
|
|
2040
|
+
if skipped_count > 0:
|
|
2041
|
+
print(f" {color_out.yellow('⊘ Skipped:')} {color_out.bold_yellow(str(skipped_count))}")
|
|
2042
|
+
if failed_count > 0:
|
|
2043
|
+
print(f" {color_out.red('✗ Failed:')} {color_out.bold_red(str(failed_count))}")
|
|
2044
|
+
print(f" {color_out.cyan('Total time:')} {color_out.bright_cyan(format_duration(total_elapsed))}")
|
|
2045
|
+
print(color_out.cyan("=" * 60))
|
|
2028
2046
|
|
|
2029
2047
|
except KeyboardInterrupt:
|
|
2030
2048
|
if not args.quiet:
|
|
2031
|
-
|
|
2049
|
+
from mdify.formatting import Colorizer
|
|
2050
|
+
color_out = Colorizer(sys.stdout)
|
|
2051
|
+
print(f"\n\n{color_out.warning('⚠ Interrupted by user. Container stopped.')}")
|
|
2032
2052
|
if success_count > 0 or skipped_count > 0 or failed_count > 0:
|
|
2033
2053
|
print(
|
|
2034
|
-
f"Partial progress: {success_count} successful, {failed_count} failed, {skipped_count} skipped"
|
|
2054
|
+
f"{color_out.dim_white('Partial progress:')} {color_out.green(str(success_count))} successful, {color_out.red(str(failed_count))} failed, {color_out.yellow(str(skipped_count))} skipped"
|
|
2035
2055
|
)
|
|
2036
2056
|
return 130
|
|
2037
2057
|
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Formatting helpers for CLI output."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import TextIO
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Colorizer:
|
|
10
|
+
"""ANSI color and style helper for terminal output."""
|
|
11
|
+
|
|
12
|
+
# ANSI color codes
|
|
13
|
+
RESET = "0"
|
|
14
|
+
BOLD = "1"
|
|
15
|
+
DIM = "2"
|
|
16
|
+
ITALIC = "3"
|
|
17
|
+
UNDERLINE = "4"
|
|
18
|
+
|
|
19
|
+
# Colors
|
|
20
|
+
BLACK = "30"
|
|
21
|
+
RED = "31"
|
|
22
|
+
GREEN = "32"
|
|
23
|
+
YELLOW = "33"
|
|
24
|
+
BLUE = "34"
|
|
25
|
+
MAGENTA = "35"
|
|
26
|
+
CYAN = "36"
|
|
27
|
+
WHITE = "37"
|
|
28
|
+
BRIGHT_BLACK = "90"
|
|
29
|
+
BRIGHT_RED = "91"
|
|
30
|
+
BRIGHT_GREEN = "92"
|
|
31
|
+
BRIGHT_YELLOW = "93"
|
|
32
|
+
BRIGHT_BLUE = "94"
|
|
33
|
+
BRIGHT_MAGENTA = "95"
|
|
34
|
+
BRIGHT_CYAN = "96"
|
|
35
|
+
BRIGHT_WHITE = "97"
|
|
36
|
+
|
|
37
|
+
def __init__(self, stream: TextIO) -> None:
|
|
38
|
+
force_color = os.environ.get("FORCE_COLOR")
|
|
39
|
+
no_color = os.environ.get("NO_COLOR")
|
|
40
|
+
self.enabled = bool(force_color) or (stream.isatty() and not no_color)
|
|
41
|
+
|
|
42
|
+
def _apply(self, text: str, *codes: str) -> str:
|
|
43
|
+
"""Apply ANSI codes to text. Supports multiple codes."""
|
|
44
|
+
if not self.enabled:
|
|
45
|
+
return text
|
|
46
|
+
code_str = ";".join(codes)
|
|
47
|
+
return f"\033[{code_str}m{text}\033[0m"
|
|
48
|
+
|
|
49
|
+
def color(self, text: str, code: str) -> str:
|
|
50
|
+
"""Apply a single color code."""
|
|
51
|
+
return self._apply(text, code)
|
|
52
|
+
|
|
53
|
+
# Basic colors
|
|
54
|
+
def green(self, text: str) -> str:
|
|
55
|
+
return self._apply(text, self.GREEN)
|
|
56
|
+
|
|
57
|
+
def yellow(self, text: str) -> str:
|
|
58
|
+
return self._apply(text, self.YELLOW)
|
|
59
|
+
|
|
60
|
+
def cyan(self, text: str) -> str:
|
|
61
|
+
return self._apply(text, self.CYAN)
|
|
62
|
+
|
|
63
|
+
def red(self, text: str) -> str:
|
|
64
|
+
return self._apply(text, self.RED)
|
|
65
|
+
|
|
66
|
+
def blue(self, text: str) -> str:
|
|
67
|
+
return self._apply(text, self.BLUE)
|
|
68
|
+
|
|
69
|
+
def magenta(self, text: str) -> str:
|
|
70
|
+
return self._apply(text, self.MAGENTA)
|
|
71
|
+
|
|
72
|
+
def white(self, text: str) -> str:
|
|
73
|
+
return self._apply(text, self.WHITE)
|
|
74
|
+
|
|
75
|
+
# Bright colors
|
|
76
|
+
def bright_green(self, text: str) -> str:
|
|
77
|
+
return self._apply(text, self.BRIGHT_GREEN)
|
|
78
|
+
|
|
79
|
+
def bright_yellow(self, text: str) -> str:
|
|
80
|
+
return self._apply(text, self.BRIGHT_YELLOW)
|
|
81
|
+
|
|
82
|
+
def bright_cyan(self, text: str) -> str:
|
|
83
|
+
return self._apply(text, self.BRIGHT_CYAN)
|
|
84
|
+
|
|
85
|
+
def bright_red(self, text: str) -> str:
|
|
86
|
+
return self._apply(text, self.BRIGHT_RED)
|
|
87
|
+
|
|
88
|
+
def bright_white(self, text: str) -> str:
|
|
89
|
+
return self._apply(text, self.BRIGHT_WHITE)
|
|
90
|
+
|
|
91
|
+
# Styles
|
|
92
|
+
def bold(self, text: str) -> str:
|
|
93
|
+
return self._apply(text, self.BOLD)
|
|
94
|
+
|
|
95
|
+
def dim(self, text: str) -> str:
|
|
96
|
+
return self._apply(text, self.DIM)
|
|
97
|
+
|
|
98
|
+
def underline(self, text: str) -> str:
|
|
99
|
+
return self._apply(text, self.UNDERLINE)
|
|
100
|
+
|
|
101
|
+
# Combined styles
|
|
102
|
+
def bold_green(self, text: str) -> str:
|
|
103
|
+
return self._apply(text, self.BOLD, self.GREEN)
|
|
104
|
+
|
|
105
|
+
def bold_cyan(self, text: str) -> str:
|
|
106
|
+
return self._apply(text, self.BOLD, self.CYAN)
|
|
107
|
+
|
|
108
|
+
def bold_yellow(self, text: str) -> str:
|
|
109
|
+
return self._apply(text, self.BOLD, self.YELLOW)
|
|
110
|
+
|
|
111
|
+
def bold_red(self, text: str) -> str:
|
|
112
|
+
return self._apply(text, self.BOLD, self.RED)
|
|
113
|
+
|
|
114
|
+
def dim_cyan(self, text: str) -> str:
|
|
115
|
+
return self._apply(text, self.DIM, self.CYAN)
|
|
116
|
+
|
|
117
|
+
def dim_white(self, text: str) -> str:
|
|
118
|
+
return self._apply(text, self.DIM, self.WHITE)
|
|
119
|
+
|
|
120
|
+
def success(self, text: str) -> str:
|
|
121
|
+
"""Styled success message (bold green)."""
|
|
122
|
+
return self._apply(text, self.BOLD, self.GREEN)
|
|
123
|
+
|
|
124
|
+
def error(self, text: str) -> str:
|
|
125
|
+
"""Styled error message (bold red)."""
|
|
126
|
+
return self._apply(text, self.BOLD, self.RED)
|
|
127
|
+
|
|
128
|
+
def warning(self, text: str) -> str:
|
|
129
|
+
"""Styled warning message (bold yellow)."""
|
|
130
|
+
return self._apply(text, self.BOLD, self.YELLOW)
|
|
131
|
+
|
|
132
|
+
def info(self, text: str) -> str:
|
|
133
|
+
"""Styled info message (cyan)."""
|
|
134
|
+
return self._apply(text, self.CYAN)
|
|
135
|
+
|
|
136
|
+
def muted(self, text: str) -> str:
|
|
137
|
+
"""Muted text (dim white)."""
|
|
138
|
+
return self._apply(text, self.DIM, self.WHITE)
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
"""Formatting helpers for CLI output."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import os
|
|
6
|
-
from typing import TextIO
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class Colorizer:
|
|
10
|
-
"""ANSI color helper for terminal output."""
|
|
11
|
-
|
|
12
|
-
def __init__(self, stream: TextIO) -> None:
|
|
13
|
-
force_color = os.environ.get("FORCE_COLOR")
|
|
14
|
-
no_color = os.environ.get("NO_COLOR")
|
|
15
|
-
self.enabled = bool(force_color) or (stream.isatty() and not no_color)
|
|
16
|
-
|
|
17
|
-
def color(self, text: str, code: str) -> str:
|
|
18
|
-
if not self.enabled:
|
|
19
|
-
return text
|
|
20
|
-
return f"\033[{code}m{text}\033[0m"
|
|
21
|
-
|
|
22
|
-
def green(self, text: str) -> str:
|
|
23
|
-
return self.color(text, "32")
|
|
24
|
-
|
|
25
|
-
def yellow(self, text: str) -> str:
|
|
26
|
-
return self.color(text, "33")
|
|
27
|
-
|
|
28
|
-
def cyan(self, text: str) -> str:
|
|
29
|
-
return self.color(text, "36")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|