| """ |
| utils.py |
| Common helper functions |
| """ |
|
|
| from __future__ import annotations |
|
|
| import hashlib |
| from pathlib import Path |
| from typing import Iterable |
|
|
| from config import ( |
| CHARS_PER_TOKEN, |
| LANGUAGE_MAP, |
| ) |
|
|
|
|
| def estimate_tokens(text: str) -> int: |
| """ |
| Rough token estimation. |
| """ |
| return max(1, int(len(text) / CHARS_PER_TOKEN)) |
|
|
|
|
| def short_sha256(text: str) -> str: |
| """ |
| First 12 chars of sha256. |
| """ |
| return hashlib.sha256( |
| text.encode("utf-8", errors="ignore") |
| ).hexdigest()[:12] |
|
|
|
|
| def xml_safe(text: str) -> str: |
| """ |
| Prevent CDATA termination. |
| """ |
| return text.replace("]]>", "]]]]><![CDATA[>") |
|
|
|
|
| def language_from_extension(ext: str) -> str: |
| return LANGUAGE_MAP.get( |
| ext.lower(), |
| ext.lower() if ext else "Unknown", |
| ) |
|
|
|
|
| def human_size(num_bytes: int) -> str: |
| value = float(num_bytes) |
|
|
| for unit in ("B", "KB", "MB", "GB", "TB"): |
| if value < 1024: |
| return f"{value:.2f} {unit}" |
| value /= 1024 |
|
|
| return f"{value:.2f} PB" |
|
|
|
|
| def count_lines(text: str) -> int: |
| if not text: |
| return 0 |
| return text.count("\n") + 1 |
|
|
|
|
| def unique_languages(entries: Iterable[dict]) -> list[str]: |
| langs = { |
| language_from_extension( |
| e.get("ext", "") |
| ) |
| for e in entries |
| } |
| return sorted(langs) |
|
|
|
|
| def safe_relative(path: Path, root: Path) -> str: |
| return path.relative_to(root).as_posix() |
|
|
|
|
| def average_file_size(total_size: int, count: int) -> int: |
| if count == 0: |
| return 0 |
| return total_size // count |
|
|
|
|
| def project_name(root: Path) -> str: |
| return root.resolve().name |