Files
RustPython/scripts/update_lib/path.py
Jeong, YunWon 58c804309b [update_lib] show deps (#6821)
* show_deps

* soft deps tree

* show deps CI
2026-01-21 01:25:30 +09:00

188 lines
5.8 KiB
Python

"""
Path utilities for update_lib.
This module provides functions for:
- Parsing and converting library paths
- Detecting test paths vs library paths
- Extracting test module names from paths
"""
import pathlib
def parse_lib_path(path: pathlib.Path | str) -> pathlib.Path:
"""
Extract the Lib/... portion from a path containing /Lib/.
Example:
parse_lib_path("cpython/Lib/test/foo.py") -> Path("Lib/test/foo.py")
"""
path_str = str(path).replace("\\", "/")
lib_marker = "/Lib/"
if lib_marker not in path_str:
raise ValueError(f"Path must contain '/Lib/' or '\\Lib\\' (got: {path})")
idx = path_str.index(lib_marker)
return pathlib.Path(path_str[idx + 1 :])
def is_lib_path(path: pathlib.Path) -> bool:
"""Check if path starts with Lib/"""
path_str = str(path).replace("\\", "/")
return path_str.startswith("Lib/") or path_str.startswith("./Lib/")
def is_test_path(path: pathlib.Path) -> bool:
"""Check if path is a test path (contains /Lib/test/ or starts with Lib/test/)"""
path_str = str(path).replace("\\", "/")
return "/Lib/test/" in path_str or path_str.startswith("Lib/test/")
def lib_to_test_path(src_path: pathlib.Path) -> pathlib.Path:
"""
Convert library path to test path.
Examples:
cpython/Lib/dataclasses.py -> cpython/Lib/test/test_dataclasses/ (if dir exists)
cpython/Lib/typing.py -> cpython/Lib/test/test_typing.py (if file exists)
cpython/Lib/json/ -> cpython/Lib/test/test_json/
cpython/Lib/json/__init__.py -> cpython/Lib/test/test_json/
Lib/dataclasses.py -> Lib/test/test_dataclasses/
"""
path_str = str(src_path).replace("\\", "/")
lib_marker = "/Lib/"
if lib_marker in path_str:
lib_path = parse_lib_path(src_path)
lib_name = lib_path.stem if lib_path.suffix == ".py" else lib_path.name
# Handle __init__.py: use parent directory name
if lib_name == "__init__":
lib_name = lib_path.parent.name
prefix = path_str[: path_str.index(lib_marker)]
# Try directory first, then file
dir_path = pathlib.Path(f"{prefix}/Lib/test/test_{lib_name}/")
if dir_path.exists():
return dir_path
file_path = pathlib.Path(f"{prefix}/Lib/test/test_{lib_name}.py")
if file_path.exists():
return file_path
# Default to directory (caller will handle non-existence)
return dir_path
else:
# Path starts with Lib/ - extract name directly
lib_name = src_path.stem if src_path.suffix == ".py" else src_path.name
# Handle __init__.py: use parent directory name
if lib_name == "__init__":
lib_name = src_path.parent.name
# Try directory first, then file
dir_path = pathlib.Path(f"Lib/test/test_{lib_name}/")
if dir_path.exists():
return dir_path
file_path = pathlib.Path(f"Lib/test/test_{lib_name}.py")
if file_path.exists():
return file_path
return dir_path
def get_test_files(path: pathlib.Path) -> list[pathlib.Path]:
"""Get all .py test files in a path (file or directory)."""
if path.is_file():
return [path]
return sorted(path.glob("**/*.py"))
def test_name_from_path(test_path: pathlib.Path) -> str:
"""
Extract test module name from a test file path.
Examples:
Lib/test/test_foo.py -> test_foo
Lib/test/test_ctypes/test_bar.py -> test_ctypes.test_bar
"""
test_path = pathlib.Path(test_path)
if test_path.parent.name.startswith("test_"):
return f"{test_path.parent.name}.{test_path.stem}"
return test_path.stem
# --- Utility functions for reducing duplication ---
def resolve_module_path(
name: str, prefix: str = "cpython", prefer: str = "file"
) -> pathlib.Path:
"""
Resolve module path, trying file or directory.
Args:
name: Module name (e.g., "dataclasses", "json")
prefix: CPython directory prefix
prefer: "file" to try .py first, "dir" to try directory first
Returns:
Path to the module (file or directory)
Examples:
resolve_module_path("dataclasses") -> cpython/Lib/dataclasses.py
resolve_module_path("json") -> cpython/Lib/json/
"""
file_path = pathlib.Path(f"{prefix}/Lib/{name}.py")
dir_path = pathlib.Path(f"{prefix}/Lib/{name}")
if prefer == "file":
if file_path.exists():
return file_path
if dir_path.exists():
return dir_path
return file_path # Default to file
else:
if dir_path.exists():
return dir_path
if file_path.exists():
return file_path
return dir_path # Default to dir
def construct_lib_path(prefix: str, *parts: str) -> pathlib.Path:
"""
Build a path under prefix/Lib/.
Args:
prefix: Directory prefix (e.g., "cpython")
*parts: Path components after Lib/
Returns:
Combined path
Examples:
construct_lib_path("cpython", "test", "test_foo.py")
-> cpython/Lib/test/test_foo.py
construct_lib_path("cpython", "dataclasses.py")
-> cpython/Lib/dataclasses.py
"""
return pathlib.Path(prefix) / "Lib" / pathlib.Path(*parts)
def get_module_name(path: pathlib.Path) -> str:
"""
Extract module name from path, handling __init__.py.
Args:
path: Path to a Python file or directory
Returns:
Module name
Examples:
get_module_name(Path("cpython/Lib/dataclasses.py")) -> "dataclasses"
get_module_name(Path("cpython/Lib/json/__init__.py")) -> "json"
get_module_name(Path("cpython/Lib/json/")) -> "json"
"""
if path.suffix == ".py":
name = path.stem
if name == "__init__":
return path.parent.name
return name
return path.name