-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcommon.py
More file actions
86 lines (69 loc) · 2.92 KB
/
Copy pathcommon.py
File metadata and controls
86 lines (69 loc) · 2.92 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
"""
Common utility functions for asicode.
Eliminates duplicated utility functions across modules.
"""
from __future__ import annotations
def unique_keep_order(items: list) -> list:
"""Deduplicate items while preserving insertion order.
Previously duplicated in run_helpers.py and context_collector.py.
"""
seen: set = set()
out: list = []
for x in items or []:
if not x or x in seen:
continue
seen.add(x)
out.append(x)
return out
def safe_filename(name: str, max_len: int = 128) -> str:
"""Sanitize a string to be a valid filename.
Replaces characters invalid on Windows/Linux (\\ / : * ? " < > |) with underscore,
compresses consecutive underscores, strips leading/trailing spaces and dots,
truncates to max_len, and returns 'unnamed' if the result is empty.
"""
# Replace invalid characters with underscore
result = name.translate(str.maketrans({c: '_' for c in '\\/:*?"<>|'}))
# Compress consecutive underscores
while '__' in result:
result = result.replace('__', '_')
# Strip leading/trailing spaces and dots
result = result.strip(' .')
# Truncate to max_len
if len(result) > max_len:
result = result[:max_len].rstrip('_. ')
# Return 'unnamed' if empty
return result if result else 'unnamed'
def norm_ws(s: str) -> str:
"""Normalize whitespace: strip leading/trailing spaces."""
return (s or "").strip()
def ensure_trailing_newline(s: str) -> str:
"""Ensure the string ends with a newline, normalizing line endings."""
s = (s or "").replace("\r\n", "\n").replace("\r", "\n")
return s if s.endswith("\n") else (s + "\n")
def chunk_list(items: list, chunk_size: int) -> list[list]:
"""Split a list into chunks of the given size."""
if items is None:
return []
return [items[i:i + chunk_size] for i in range(0, len(items), chunk_size)]
def normalize_rel_path_fast(rel_path: str) -> str:
"""Quick normalization of repo-relative path.
- Strips surrounding whitespace
- Replaces backslashes with forward slashes
- Removes repeated leading ``./`` (loop, not lstrip — avoids eating ``.gitignore``)
- Removes leading ``/``
- Returns empty string for empty/None input
This is the canonical single-source helper for path normalization.
All previous callers that used the defective inline chain
``.strip().removeprefix("/").removeprefix("./")`` have been migrated to call
this function (or ``path_security.normalize_rel_path``, which additionally
strips quotes, removes ``a/``/``b/`` prefixes, and rejects traversal).
No defective chain remains in the repo as of 2026-07.
"""
p = (rel_path or "").strip()
p = p.replace("\\", "/")
# Use loop instead of lstrip to avoid character-set stripping
# (lstrip("./") would turn .gitignore into gitignore)
while p.startswith("./"):
p = p[2:]
p = p.lstrip("/")
return p