mdify-cli 3.1.1__tar.gz → 3.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.
- {mdify_cli-3.1.1 → mdify_cli-3.2.1}/PKG-INFO +1 -1
- {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify/__init__.py +1 -1
- {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify/cli.py +85 -51
- mdify_cli-3.2.1/mdify/formatting.py +138 -0
- {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify_cli.egg-info/PKG-INFO +1 -1
- {mdify_cli-3.1.1 → mdify_cli-3.2.1}/pyproject.toml +1 -1
- mdify_cli-3.1.1/mdify/formatting.py +0 -29
- {mdify_cli-3.1.1 → mdify_cli-3.2.1}/LICENSE +0 -0
- {mdify_cli-3.1.1 → mdify_cli-3.2.1}/README.md +0 -0
- {mdify_cli-3.1.1 → mdify_cli-3.2.1}/assets/mdify.png +0 -0
- {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify/__main__.py +0 -0
- {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify/container.py +0 -0
- {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify/docling_client.py +0 -0
- {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify/ssh/__init__.py +0 -0
- {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify/ssh/client.py +0 -0
- {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify/ssh/models.py +0 -0
- {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify/ssh/remote_container.py +0 -0
- {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify/ssh/transfer.py +0 -0
- {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify_cli.egg-info/SOURCES.txt +0 -0
- {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify_cli.egg-info/dependency_links.txt +0 -0
- {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify_cli.egg-info/entry_points.txt +0 -0
- {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify_cli.egg-info/requires.txt +0 -0
- {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify_cli.egg-info/top_level.txt +0 -0
- {mdify_cli-3.1.1 → mdify_cli-3.2.1}/setup.cfg +0 -0
- {mdify_cli-3.1.1 → mdify_cli-3.2.1}/tests/test_cli.py +0 -0
- {mdify_cli-3.1.1 → mdify_cli-3.2.1}/tests/test_container.py +0 -0
- {mdify_cli-3.1.1 → mdify_cli-3.2.1}/tests/test_docling_client.py +0 -0
- {mdify_cli-3.1.1 → mdify_cli-3.2.1}/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
|
|
@@ -1172,7 +1175,8 @@ def main_async_remote(args) -> int:
|
|
|
1172
1175
|
input_path = Path(args.input)
|
|
1173
1176
|
if not input_path.exists():
|
|
1174
1177
|
await ssh_client.disconnect()
|
|
1175
|
-
|
|
1178
|
+
color = Colorizer(sys.stderr)
|
|
1179
|
+
print(f"{color.error('✗ Error:')} Input file or directory not found: {args.input}", file=sys.stderr)
|
|
1176
1180
|
return 1
|
|
1177
1181
|
|
|
1178
1182
|
# Store resolved path as base for relative path calculations
|
|
@@ -1181,8 +1185,9 @@ def main_async_remote(args) -> int:
|
|
|
1181
1185
|
|
|
1182
1186
|
if not files_to_convert:
|
|
1183
1187
|
await ssh_client.disconnect()
|
|
1184
|
-
|
|
1185
|
-
print(f"
|
|
1188
|
+
color = Colorizer(sys.stderr)
|
|
1189
|
+
print(f"{color.error('✗ Error:')} No supported files found in {args.input}", file=sys.stderr)
|
|
1190
|
+
print(f" {color.dim_white(f'Supported formats: {', '.join(sorted(SUPPORTED_EXTENSIONS))}')} ", file=sys.stderr)
|
|
1186
1191
|
return 1
|
|
1187
1192
|
|
|
1188
1193
|
if not args.quiet:
|
|
@@ -1378,7 +1383,8 @@ def main_async_remote(args) -> int:
|
|
|
1378
1383
|
break
|
|
1379
1384
|
|
|
1380
1385
|
if not conversion_success:
|
|
1381
|
-
|
|
1386
|
+
color_error = Colorizer(sys.stderr)
|
|
1387
|
+
print(f" {color_error.error('✗ Failed:')} Conversion failed after {conversion_attempt} attempt(s)", file=sys.stderr)
|
|
1382
1388
|
failed += 1
|
|
1383
1389
|
break
|
|
1384
1390
|
|
|
@@ -1490,7 +1496,8 @@ def main_async_remote(args) -> int:
|
|
|
1490
1496
|
await ssh_client.connect()
|
|
1491
1497
|
continue
|
|
1492
1498
|
|
|
1493
|
-
|
|
1499
|
+
color_err = Colorizer(sys.stderr)
|
|
1500
|
+
print(f" {color_err.error('✗ Failed:')} {str(e)}", file=sys.stderr)
|
|
1494
1501
|
if DEBUG:
|
|
1495
1502
|
import traceback
|
|
1496
1503
|
traceback.print_exc(file=sys.stderr)
|
|
@@ -1524,13 +1531,21 @@ def main_async_remote(args) -> int:
|
|
|
1524
1531
|
|
|
1525
1532
|
# Print summary
|
|
1526
1533
|
print(color.cyan(f"\n{'='*60}"), file=sys.stderr)
|
|
1527
|
-
print(color.
|
|
1528
|
-
print(color.
|
|
1534
|
+
print(color.bold_cyan("📊 Remote Conversion Summary"), file=sys.stderr)
|
|
1535
|
+
print(color.cyan(f"{'='*60}"), file=sys.stderr)
|
|
1536
|
+
|
|
1537
|
+
# Format summary with emojis and colors
|
|
1538
|
+
total = len(files_to_convert)
|
|
1539
|
+
success_pct = f" ({successful}/{total})" if total > 0 else ""
|
|
1540
|
+
failed_pct = f" ({failed}/{total})" if total > 0 else ""
|
|
1541
|
+
skipped_pct = f" ({total - successful - failed}/{total})" if total > 0 else ""
|
|
1542
|
+
|
|
1543
|
+
print(f" {color.green('✓ Successful:')} {color.bold_green(str(successful))}{success_pct}", file=sys.stderr)
|
|
1529
1544
|
if failed > 0:
|
|
1530
|
-
print(color.
|
|
1531
|
-
|
|
1532
|
-
print(f"
|
|
1533
|
-
print(f" Total:
|
|
1545
|
+
print(f" {color.red('✗ Failed:')} {color.bold_red(str(failed))}{failed_pct}", file=sys.stderr)
|
|
1546
|
+
if (total - successful - failed) > 0:
|
|
1547
|
+
print(f" {color.yellow('⊘ Skipped:')} {color.bold_yellow(str(total - successful - failed))}{skipped_pct}", file=sys.stderr)
|
|
1548
|
+
print(f" {color.cyan('Total:')} {color.bold(str(total))}", file=sys.stderr)
|
|
1534
1549
|
print(color.cyan(f"{'='*60}"), file=sys.stderr)
|
|
1535
1550
|
|
|
1536
1551
|
return 0 if failed == 0 else 1
|
|
@@ -1578,7 +1593,9 @@ def main_async_remote(args) -> int:
|
|
|
1578
1593
|
|
|
1579
1594
|
def main() -> int:
|
|
1580
1595
|
"""Main entry point for the CLI."""
|
|
1581
|
-
|
|
1596
|
+
from mdify.formatting import Colorizer
|
|
1597
|
+
color = Colorizer(sys.stderr)
|
|
1598
|
+
print(color.bold_cyan(f"📄 mdify v{__version__}"), file=sys.stderr)
|
|
1582
1599
|
args = parse_args()
|
|
1583
1600
|
|
|
1584
1601
|
# Handle --check-update flag
|
|
@@ -1726,34 +1743,41 @@ def main() -> int:
|
|
|
1726
1743
|
|
|
1727
1744
|
# Validate input
|
|
1728
1745
|
if not input_path.exists():
|
|
1729
|
-
|
|
1746
|
+
color = Colorizer(sys.stderr)
|
|
1747
|
+
print(f"{color.error('✗ Error:')} Input path does not exist: {input_path}", file=sys.stderr)
|
|
1730
1748
|
return 1
|
|
1731
1749
|
|
|
1732
1750
|
# Get files to convert
|
|
1733
1751
|
try:
|
|
1734
1752
|
files_to_convert = get_files_to_convert(input_path, args.glob, args.recursive)
|
|
1735
1753
|
except Exception as e:
|
|
1736
|
-
|
|
1754
|
+
color = Colorizer(sys.stderr)
|
|
1755
|
+
print(f"{color.error('✗ Error:')} {e}", file=sys.stderr)
|
|
1737
1756
|
return 1
|
|
1738
1757
|
|
|
1739
1758
|
if not files_to_convert:
|
|
1740
|
-
|
|
1759
|
+
color = Colorizer(sys.stderr)
|
|
1760
|
+
print(f"{color.warning('⚠ Warning:')} No files found to convert in: {input_path}", file=sys.stderr)
|
|
1741
1761
|
return 1
|
|
1742
1762
|
|
|
1743
1763
|
total_files = len(files_to_convert)
|
|
1744
1764
|
total_size = sum(f.stat().st_size for f in files_to_convert)
|
|
1745
1765
|
|
|
1746
1766
|
if not args.quiet:
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
print(f"
|
|
1750
|
-
print(f"
|
|
1751
|
-
print(f"
|
|
1767
|
+
from mdify.formatting import Colorizer
|
|
1768
|
+
color_info = Colorizer(sys.stdout)
|
|
1769
|
+
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)})')}")
|
|
1770
|
+
print(f"{color_info.cyan('📁 Source:')} {color_info.bright_white(str(input_path.resolve()))}")
|
|
1771
|
+
print(f"{color_info.cyan('💾 Output:')} {color_info.bright_white(str(output_dir.resolve()))}")
|
|
1772
|
+
print(f"{color_info.cyan('🐳 Runtime:')} {color_info.bright_white(runtime)}")
|
|
1773
|
+
print(f"{color_info.cyan('🖼️ Image:')} {color_info.dim_white(image)}")
|
|
1752
1774
|
print()
|
|
1753
1775
|
|
|
1754
1776
|
if args.mask:
|
|
1777
|
+
from mdify.formatting import Colorizer
|
|
1778
|
+
color_warn = Colorizer(sys.stderr)
|
|
1755
1779
|
print(
|
|
1756
|
-
"
|
|
1780
|
+
color_warn.warning("⚠ --mask is not supported with docling-serve and will be ignored"),
|
|
1757
1781
|
file=sys.stderr,
|
|
1758
1782
|
)
|
|
1759
1783
|
|
|
@@ -1770,7 +1794,9 @@ def main() -> int:
|
|
|
1770
1794
|
|
|
1771
1795
|
try:
|
|
1772
1796
|
if not args.quiet:
|
|
1773
|
-
|
|
1797
|
+
from mdify.formatting import Colorizer
|
|
1798
|
+
color_start = Colorizer(sys.stdout)
|
|
1799
|
+
print(f"{color_start.bright_cyan('▶️ Starting')} {color_start.bright_white('docling-serve')} {color_start.bright_cyan('container')}...\n")
|
|
1774
1800
|
|
|
1775
1801
|
# Apply resource profile
|
|
1776
1802
|
profile = RESOURCE_PROFILES[args.profile]
|
|
@@ -2009,22 +2035,30 @@ def main() -> int:
|
|
|
2009
2035
|
|
|
2010
2036
|
# Print summary
|
|
2011
2037
|
if not args.quiet:
|
|
2038
|
+
from mdify.formatting import Colorizer
|
|
2039
|
+
color_out = Colorizer(sys.stdout)
|
|
2012
2040
|
print()
|
|
2013
|
-
print("=" *
|
|
2014
|
-
print("Conversion Summary
|
|
2015
|
-
print(
|
|
2016
|
-
print(f"
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2041
|
+
print(color_out.cyan("=" * 60))
|
|
2042
|
+
print(color_out.bold_cyan("📊 Local Conversion Summary"))
|
|
2043
|
+
print(color_out.cyan("=" * 60))
|
|
2044
|
+
print(f" {color_out.cyan('Total files:')} {color_out.bold(str(total_files))}")
|
|
2045
|
+
if success_count > 0:
|
|
2046
|
+
print(f" {color_out.green('✓ Successful:')} {color_out.bold_green(str(success_count))}")
|
|
2047
|
+
if skipped_count > 0:
|
|
2048
|
+
print(f" {color_out.yellow('⊘ Skipped:')} {color_out.bold_yellow(str(skipped_count))}")
|
|
2049
|
+
if failed_count > 0:
|
|
2050
|
+
print(f" {color_out.red('✗ Failed:')} {color_out.bold_red(str(failed_count))}")
|
|
2051
|
+
print(f" {color_out.cyan('Total time:')} {color_out.bright_cyan(format_duration(total_elapsed))}")
|
|
2052
|
+
print(color_out.cyan("=" * 60))
|
|
2021
2053
|
|
|
2022
2054
|
except KeyboardInterrupt:
|
|
2023
2055
|
if not args.quiet:
|
|
2024
|
-
|
|
2056
|
+
from mdify.formatting import Colorizer
|
|
2057
|
+
color_out = Colorizer(sys.stdout)
|
|
2058
|
+
print(f"\n\n{color_out.warning('⚠ Interrupted by user. Container stopped.')}")
|
|
2025
2059
|
if success_count > 0 or skipped_count > 0 or failed_count > 0:
|
|
2026
2060
|
print(
|
|
2027
|
-
f"Partial progress: {success_count} successful, {failed_count} failed, {skipped_count} skipped"
|
|
2061
|
+
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"
|
|
2028
2062
|
)
|
|
2029
2063
|
return 130
|
|
2030
2064
|
|
|
@@ -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
|