Files
Nexus/server/utils/unix_ls.py
T
Nexus Deploy a2ae74d582 release: BUG fixes batch 1-6, full bug scan docs, Ubuntu/Linux dev
- Backend: auth refresh reuse, schedule/retry/sync/agent/files fixes
- Frontend: push WS, files ETag browse, vite build assets
- Docs: audit/changelog, production deploy gate, bug scan registry B7-77
- Deploy: deploy-production.sh, prune assets, gate logs
2026-06-04 14:01:14 +08:00

116 lines
3.7 KiB
Python

"""Parse GNU `ls -la` permission strings and listing lines."""
from __future__ import annotations
import re
_ISO_DATE = re.compile(r"^\d{4}-\d{2}-\d{2}$")
_TZ_OFFSET = re.compile(r"^[+-]\d{4}$")
def modified_from_ls_parts(parts: list[str]) -> str:
"""Build display timestamp from ``ls`` fields (without filename)."""
if len(parts) < 6:
return ""
if len(parts) >= 7 and _ISO_DATE.match(parts[5]):
time_part = parts[6].split(".", 1)[0] if parts[6] else ""
return f"{parts[5]} {time_part}".strip()
if len(parts) >= 9:
return " ".join(parts[5:8])
return " ".join(parts[5:])
def parse_ls_la_line(line: str) -> dict | None:
"""Parse one ``ls -la`` line; returns None for total/skip lines.
Handles two timestamp formats produced by different ``ls`` implementations:
- Standard (3-token date): ``perms nlinks owner group size Mon DD HH:MM name`` → 9+ fields
- GNU long-iso (2-token): ``perms nlinks owner group size YYYY-MM-DD HH:MM name`` → 8+ fields
With ``--time-style=long-iso`` the date collapses from 3 tokens to 2, so
the total token count drops from 9 to 8. Directories and regular files were
silently dropped (only symlinks survived because their names contain
``" -> target"`` which pushed the count back to 9).
"""
stripped = line.strip()
if not stripped or stripped.startswith("total "):
return None
# Split into at most 9 pieces so filenames with spaces are kept intact.
parts = stripped.split(None, 8)
if len(parts) < 8:
return None
perms = parts[0]
is_dir = perms.startswith("d")
is_link = perms.startswith("l")
# Detect format by checking whether field[5] is an ISO date (YYYY-MM-DD).
# long-iso: [perms nlinks owner group size DATE TIME name] → name at index 7
# standard: [perms nlinks owner group size MON DAY TIME name] → name at index 8
if _ISO_DATE.match(parts[5]):
# long-iso: DATE TIME [TZ] name — GNU ls may append +0800 before filename
if len(parts) >= 9 and _TZ_OFFSET.match(parts[7]):
name = parts[8]
elif len(parts) >= 8:
name = parts[7]
else:
return None
modified = f"{parts[5]} {parts[6].split('.', 1)[0]}"
else:
# standard format — need at least 9 tokens for the name at index 8
if len(parts) < 9:
return None
name = parts[8]
modified = modified_from_ls_parts(parts)
link_target = None
if is_link and " -> " in name:
name, link_target = name.split(" -> ", 1)
if name in (".", ".."):
return None
return {
"name": name,
"is_dir": is_dir,
"is_link": is_link,
"link_target": link_target,
"size": parts[4],
"perms": perms,
"owner": parts[2],
"group": parts[3] if len(parts) > 3 else "",
"modified": modified,
}
def perms_to_mode_octal(perms: str) -> str | None:
"""Convert ``drwxr-xr-x`` / ``-rw-r--r--`` to ``755`` / ``644``."""
if not perms or len(perms) < 10:
return None
bits = perms[1:10]
if len(bits) != 9:
return None
def _tri(chunk: str) -> int:
value = 0
if chunk[0] == "r":
value += 4
if chunk[1] == "w":
value += 2
if chunk[2] in "xXsStT":
value += 1
return value
return f"{_tri(bits[0:3])}{_tri(bits[3:6])}{_tri(bits[6:9])}"
def format_owner_label(owner: str, group: str | None = None) -> str:
"""Primary list label: ``www`` or ``www:nogroup`` when group differs."""
o = (owner or "").strip()
g = (group or "").strip()
if not o:
return "—"
if not g or g == o:
return o
return f"{o}:{g}"