返回 JoyAI-Echo
1 """Path abbreviation utilities for display."""
2
3 from __future__ import annotations
4
5 import os
6 import re
7 from urllib.parse import urlparse
8
9
10 def abbreviate_path(path: str, max_len: int = 40) -> str:
11 """Abbreviate a file path or URL, preserving basename and key directories.
12
13 Strategy:
14 1. Return as-is if short enough
15 2. Replace home directory with ~/
16 3. From right, keep basename + parent dirs until budget exhausted
17 4. Prefix with …/
18 """
19 if not path:
20 return path
21
22 # Handle URLs: preserve scheme://domain + filename
23 if re.match(r"https?://", path):
24 return _abbreviate_url(path, max_len)
25
26 # Normalize separators to /
27 normalized = path.replace("\\", "/")
28
29 # Replace home directory
30 home = os.path.expanduser("~").replace("\\", "/")
31 if normalized.startswith(home + "/"):
32 normalized = "~" + normalized[len(home):]
33 elif normalized == home:
34 normalized = "~"
35
36 # Return early only after normalization and home replacement
37 if len(normalized) <= max_len:
38 return normalized
39
40 # Split into segments
41 parts = normalized.rstrip("/").split("/")
42 if len(parts) <= 1:
43 return normalized[:max_len - 1] + "\u2026"
44
45 # Always keep the basename
46 basename = parts[-1]
47 # Budget: max_len minus "…/" prefix (2 chars) minus "/" separator minus basename
48 budget = max_len - len(basename) - 3 # -3 for "…/" + final "/"
49
50 # Walk backwards from parent, collecting segments
51 kept: list[str] = []
52 for seg in reversed(parts[:-1]):
53 needed = len(seg) + 1 # segment + "/"
54 if not kept and needed <= budget:
55 kept.append(seg)
56 budget -= needed
57 elif kept:
58 needed_with_sep = len(seg) + 1
59 if needed_with_sep <= budget:
60 kept.append(seg)
61 budget -= needed_with_sep
62 else:
63 break
64 else:
65 break
66
67 kept.reverse()
68 if kept:
69 return "\u2026/" + "/".join(kept) + "/" + basename
70 return "\u2026/" + basename
71
72
73 def _abbreviate_url(url: str, max_len: int = 40) -> str:
74 """Abbreviate a URL keeping domain and filename."""
75 if len(url) <= max_len:
76 return url
77
78 parsed = urlparse(url)
79 domain = parsed.netloc # e.g. "example.com"
80 path_part = parsed.path # e.g. "/api/v2/resource.json"
81
82 # Extract filename from path
83 segments = path_part.rstrip("/").split("/")
84 basename = segments[-1] if segments else ""
85
86 if not basename:
87 # No filename, truncate URL
88 return url[: max_len - 1] + "\u2026"
89
90 budget = max_len - len(domain) - len(basename) - 4 # "…/" + "/"
91 if budget < 0:
92 trunc = max_len - len(domain) - 5 # "…/" + "/"
93 return domain + "/\u2026/" + (basename[:trunc] if trunc > 0 else "")
94
95 # Build abbreviated path
96 kept: list[str] = []
97 for seg in reversed(segments[:-1]):
98 if len(seg) + 1 <= budget:
99 kept.append(seg)
100 budget -= len(seg) + 1
101 else:
102 break
103
104 kept.reverse()
105 if kept:
106 return domain + "/\u2026/" + "/".join(kept) + "/" + basename
107 return domain + "/\u2026/" + basename
108
108 lines PYTHON