-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathutils.py
More file actions
149 lines (117 loc) · 4.74 KB
/
Copy pathutils.py
File metadata and controls
149 lines (117 loc) · 4.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
"""Вспомогательные утилиты.
Функции общего назначения:
- parse_ts() / format_ts() — работа с миллисекундными таймштампами
- summarize_val() — обрезка длинных строк
- parse_time_spec() — парсинг '30d', '6m', '1y'
- confirm() — запрос подтверждения
"""
import re
from datetime import UTC, datetime, timedelta
from typing import Literal, LiteralString
from i18n import _
def parse_ts(ts_ms) -> datetime:
"""Конвертирует Unix timestamp в миллисекундах в datetime (UTC)."""
return datetime.fromtimestamp(ts_ms / 1000, tz=UTC)
def format_ts(ts_ms, fmt="%Y-%m-%d %H:%M:%S") -> str:
"""Форматирует timestamp в строку."""
return parse_ts(ts_ms).strftime(fmt)
def format_ts_short(ts_ms) -> str:
"""Форматирует timestamp в компактный формат для имён файлов."""
return format_ts(ts_ms, "%Y-%m-%d_%H.%M.%S")
def summarize_val(v, max_len=120) -> str:
"""Обрезает длинное значение для компактного отображения."""
s = str(v)
if len(s) > max_len:
s = s[:max_len] + "..."
return s
def parse_date(date_str: str) -> datetime | None:
"""Парсит дату в формате ГГГГ-ММ-ДД."""
if not date_str or not isinstance(date_str, str):
return None
try:
parts = date_str.strip().split("-")
if len(parts) != 3:
return None
return datetime(int(parts[0]), int(parts[1]), int(parts[2]), tzinfo=UTC)
except (ValueError, IndexError):
return None
def parse_time_spec(spec) -> None | datetime:
"""Парсит спецификацию времени ('30d', '6m', '1y') в timedelta."""
if not spec or not isinstance(spec, str):
return None
spec = spec.strip().lower()
match = re.match(r"^(\d+)\s*(d|day|days|m|mo|mon|month|months|y|year|years)$", spec)
if not match:
return None
value = int(match.group(1))
unit = match.group(2)
now = datetime.now(UTC)
if unit == "d" or unit.startswith("day"):
delta = timedelta(days=value)
elif unit == "m" or unit.startswith("mon"):
delta = timedelta(days=value * 30.44)
elif unit == "y" or unit.startswith("year"):
delta = timedelta(days=value * 365.25)
else:
return None
return now - delta
def confirm(prompt=None, default=False) -> bool:
"""Запрашивает подтверждение y/N.
Args:
prompt: str, текст приглашения (если None — используется общий)
default: bool, что возвращать при пустом вводе
Returns:
bool
"""
if prompt is None:
prompt = _("yes_no_prompt")
suffix = _("yes_no_true") if default else _("yes_no_false")
while True:
try:
answer = input(prompt + suffix).strip().lower()
except (EOFError, KeyboardInterrupt):
print()
return False
if not answer:
return default
if answer in ("y", "yes", _("cli.yn_yes")):
return True
if answer in ("n", "no", "нет"):
return False
def build_help_epilog(cmd_name: str, examples: list[tuple[str, str]]) -> str:
"""Формирует epilog для argparse из списка примеров.
Args:
cmd_name: имя команды (для заголовка)
examples: список (флаги, ключ в i18n с описанием)
Returns:
str — готовый текст для epilog
"""
header = _("help.usage_header", cmd=cmd_name)
lines = [f"\n{header}"]
for flags, desc_key in examples:
desc = _(desc_key)
if flags:
lines.append(f" opencode-db {cmd_name} {flags:33s} # {desc}")
else:
lines.append(f" opencode-db {cmd_name:36s} # {desc}")
return "\n".join(lines)
def format_cost(cost_value) -> str:
"""Форматирует стоимость в долларах."""
if cost_value is None:
return "—"
if cost_value < 0.01:
return f"${cost_value:.4f}"
if cost_value < 1:
return f"${cost_value:.3f}"
return f"${cost_value:.2f}"
def format_tokens(count) -> LiteralString | Literal["—"]:
"""Форматирует количество токенов с разделителями."""
if count is None:
return "—"
s = str(int(count))
result = []
for i, ch in enumerate(reversed(s)):
if i > 0 and i % 3 == 0:
result.append("\u202f")
result.append(ch)
return "".join(reversed(result))