from __future__ import annotations
import os
import urllib.parse
from pathlib import Path
WORKSPACE_MARKERS = [
".git",
"pyproject.toml",
"setup.py",
"requirements.txt",
"Pipfile",
"poetry.lock",
"package.json",
"tsconfig.json",
"jsconfig.json",
"Cargo.toml",
"compile_commands.json",
"compile_flags.txt",
"go.mod",
]
def detect_workspace_root(file_path: str | Path, default_root: str | None = None) -> Path:
"""Best-effort workspace root detection.
Strategy:
- Start from the file's parent directory.
- Walk upward until we find a known marker.
- Fallback to default_root, then cwd.
"""
p = Path(file_path).expanduser().resolve()
start = p.parent if p.is_file() else p
for current in [start, *start.parents]:
for marker in WORKSPACE_MARKERS:
if (current / marker).exists():
return current
if default_root:
return Path(default_root).expanduser().resolve()
return Path.cwd().resolve()
def path_to_uri(path: str | Path) -> str:
"""Convert a local filesystem path to a file:// URI."""
return Path(path).expanduser().resolve().as_uri()
def uri_to_path(uri: str) -> Path:
"""Convert file:// URI to a local filesystem path.
Notes:
- For non-file URIs, returns a Path constructed from the URI path.
"""
parsed = urllib.parse.urlparse(uri)
if parsed.scheme and parsed.scheme != "file":
return Path(urllib.parse.unquote(parsed.path))
# file:// URIs can be file:///C:/... on Windows.
if os.name == "nt":
# urlparse('file:///C:/x') -> path='/C:/x'
path = parsed.path.lstrip("/")
return Path(urllib.parse.unquote(path))
return Path(urllib.parse.unquote(parsed.path))