Coverage for lintro / ai / paths.py: 94%
33 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
1"""Shared path utilities for AI display and safety checks."""
3from __future__ import annotations
5import os
6from pathlib import Path
8OUTSIDE_WORKSPACE_SENTINEL = "<outside-workspace>"
9"""Sentinel returned by :func:`to_provider_path` for paths outside the workspace."""
12def relative_path(file_path: str) -> str:
13 """Convert a path to be relative to cwd for display.
15 Used by display, fix, and interactive modules to show short,
16 readable paths instead of absolute ones.
18 Args:
19 file_path: Absolute or relative file path.
21 Returns:
22 Relative path string, or the original if conversion fails.
23 """
24 try:
25 return os.path.relpath(file_path)
26 except ValueError:
27 return file_path
30def resolve_workspace_root(config_path: str | None = None) -> Path:
31 """Resolve the workspace root used for AI file operations.
33 Args:
34 config_path: Optional path to lintro config file.
36 Returns:
37 Absolute workspace root path.
38 """
39 if config_path:
40 return Path(config_path).resolve().parent
41 return Path.cwd().resolve()
44def resolve_workspace_file(file_path: str, workspace_root: Path) -> Path | None:
45 """Resolve a file path and ensure it stays within the workspace root.
47 Args:
48 file_path: Absolute or relative file path.
49 workspace_root: Absolute workspace root.
51 Returns:
52 Resolved path if inside workspace root, else None.
53 """
54 if not file_path:
55 return None
57 root = workspace_root.resolve()
58 candidate = Path(file_path)
60 try:
61 resolved = (
62 candidate.resolve()
63 if candidate.is_absolute()
64 else (root / candidate).resolve()
65 )
66 except OSError:
67 return None
69 try:
70 resolved.relative_to(root)
71 except ValueError:
72 return None
74 return resolved
77def to_provider_path(file_path: str, workspace_root: Path) -> str:
78 """Convert file paths to provider-safe workspace-relative form.
80 Args:
81 file_path: Absolute or relative file path.
82 workspace_root: Absolute workspace root.
84 Returns:
85 Workspace-relative POSIX path when under workspace_root,
86 or :data:`OUTSIDE_WORKSPACE_SENTINEL` for any path outside it.
87 """
88 resolved = resolve_workspace_file(file_path, workspace_root)
89 if resolved is None:
90 return OUTSIDE_WORKSPACE_SENTINEL
91 return resolved.relative_to(workspace_root.resolve()).as_posix()