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

1"""Shared path utilities for AI display and safety checks.""" 

2 

3from __future__ import annotations 

4 

5import os 

6from pathlib import Path 

7 

8OUTSIDE_WORKSPACE_SENTINEL = "<outside-workspace>" 

9"""Sentinel returned by :func:`to_provider_path` for paths outside the workspace.""" 

10 

11 

12def relative_path(file_path: str) -> str: 

13 """Convert a path to be relative to cwd for display. 

14 

15 Used by display, fix, and interactive modules to show short, 

16 readable paths instead of absolute ones. 

17 

18 Args: 

19 file_path: Absolute or relative file path. 

20 

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 

28 

29 

30def resolve_workspace_root(config_path: str | None = None) -> Path: 

31 """Resolve the workspace root used for AI file operations. 

32 

33 Args: 

34 config_path: Optional path to lintro config file. 

35 

36 Returns: 

37 Absolute workspace root path. 

38 """ 

39 if config_path: 

40 return Path(config_path).resolve().parent 

41 return Path.cwd().resolve() 

42 

43 

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. 

46 

47 Args: 

48 file_path: Absolute or relative file path. 

49 workspace_root: Absolute workspace root. 

50 

51 Returns: 

52 Resolved path if inside workspace root, else None. 

53 """ 

54 if not file_path: 

55 return None 

56 

57 root = workspace_root.resolve() 

58 candidate = Path(file_path) 

59 

60 try: 

61 resolved = ( 

62 candidate.resolve() 

63 if candidate.is_absolute() 

64 else (root / candidate).resolve() 

65 ) 

66 except OSError: 

67 return None 

68 

69 try: 

70 resolved.relative_to(root) 

71 except ValueError: 

72 return None 

73 

74 return resolved 

75 

76 

77def to_provider_path(file_path: str, workspace_root: Path) -> str: 

78 """Convert file paths to provider-safe workspace-relative form. 

79 

80 Args: 

81 file_path: Absolute or relative file path. 

82 workspace_root: Absolute workspace root. 

83 

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()