Refactor code structure and remove redundant changes

This commit is contained in:
catlog22
2026-01-24 14:47:47 +08:00
parent cf5fecd66d
commit f2b0a5bbc9
113 changed files with 43217 additions and 235 deletions

View File

@@ -0,0 +1,300 @@
"""Path mapping utilities for source paths and index paths.
This module provides bidirectional mapping between source code directories
and their corresponding index storage locations.
Storage Structure:
~/.codexlens/
├── registry.db # Global mapping table
└── indexes/
└── D/
└── Claude_dms3/
├── _index.db # Root directory index
└── src/
└── _index.db # src/ directory index
"""
import json
import os
import platform
from pathlib import Path
from typing import Optional
def _get_configured_index_root() -> Path:
"""Get the index root from environment or config file.
Priority order:
1. CODEXLENS_INDEX_DIR environment variable
2. index_dir from ~/.codexlens/config.json
3. Default: ~/.codexlens/indexes
"""
env_override = os.getenv("CODEXLENS_INDEX_DIR")
if env_override:
return Path(env_override).expanduser().resolve()
config_file = Path.home() / ".codexlens" / "config.json"
if config_file.exists():
try:
cfg = json.loads(config_file.read_text(encoding="utf-8"))
if "index_dir" in cfg:
return Path(cfg["index_dir"]).expanduser().resolve()
except (json.JSONDecodeError, OSError):
pass
return Path.home() / ".codexlens" / "indexes"
class PathMapper:
"""Bidirectional mapping tool for source paths ↔ index paths.
Handles cross-platform path normalization and conversion between
source code directories and their index storage locations.
Attributes:
DEFAULT_INDEX_ROOT: Default root directory for all indexes
INDEX_DB_NAME: Standard name for index database files
index_root: Configured index root directory
"""
DEFAULT_INDEX_ROOT = _get_configured_index_root()
INDEX_DB_NAME = "_index.db"
def __init__(self, index_root: Optional[Path] = None):
"""Initialize PathMapper with optional custom index root.
Args:
index_root: Custom index root directory. If None, uses DEFAULT_INDEX_ROOT.
"""
self.index_root = (index_root or self.DEFAULT_INDEX_ROOT).resolve()
def source_to_index_dir(self, source_path: Path) -> Path:
"""Convert source directory to its index directory path.
Maps a source code directory to where its index data should be stored.
The mapping preserves the directory structure but normalizes paths
for cross-platform compatibility.
Args:
source_path: Source directory path to map
Returns:
Index directory path under index_root
Examples:
>>> mapper = PathMapper()
>>> mapper.source_to_index_dir(Path("D:/Claude_dms3/src"))
PosixPath('/home/user/.codexlens/indexes/D/Claude_dms3/src')
>>> mapper.source_to_index_dir(Path("/home/user/project"))
PosixPath('/home/user/.codexlens/indexes/home/user/project')
"""
source_path = source_path.resolve()
normalized = self.normalize_path(source_path)
return self.index_root / normalized
def source_to_index_db(self, source_path: Path) -> Path:
"""Convert source directory to its index database file path.
Maps a source directory to the full path of its index database file,
including the standard INDEX_DB_NAME.
Args:
source_path: Source directory path to map
Returns:
Full path to the index database file
Examples:
>>> mapper = PathMapper()
>>> mapper.source_to_index_db(Path("D:/Claude_dms3/src"))
PosixPath('/home/user/.codexlens/indexes/D/Claude_dms3/src/_index.db')
"""
index_dir = self.source_to_index_dir(source_path)
return index_dir / self.INDEX_DB_NAME
def index_to_source(self, index_path: Path) -> Path:
"""Convert index path back to original source path.
Performs reverse mapping from an index storage location to the
original source directory. Handles both directory paths and
database file paths.
Args:
index_path: Index directory or database file path
Returns:
Original source directory path
Raises:
ValueError: If index_path is not under index_root
Examples:
>>> mapper = PathMapper()
>>> mapper.index_to_source(
... Path("~/.codexlens/indexes/D/Claude_dms3/src/_index.db")
... )
WindowsPath('D:/Claude_dms3/src')
>>> mapper.index_to_source(
... Path("~/.codexlens/indexes/D/Claude_dms3/src")
... )
WindowsPath('D:/Claude_dms3/src')
"""
index_path = index_path.resolve()
# Remove _index.db if present
if index_path.name == self.INDEX_DB_NAME:
index_path = index_path.parent
# Verify path is under index_root
try:
relative = index_path.relative_to(self.index_root)
except ValueError:
raise ValueError(
f"Index path {index_path} is not under index root {self.index_root}"
)
# Convert normalized path back to source path
normalized_str = str(relative).replace("\\", "/")
return self.denormalize_path(normalized_str)
def get_project_root(self, source_path: Path) -> Path:
"""Find the project root directory (topmost indexed directory).
Walks up the directory tree to find the highest-level directory
that has an index database.
Args:
source_path: Source directory to start from
Returns:
Project root directory path. Returns source_path itself if
no parent index is found.
Examples:
>>> mapper = PathMapper()
>>> mapper.get_project_root(Path("D:/Claude_dms3/src/codexlens"))
WindowsPath('D:/Claude_dms3')
"""
source_path = source_path.resolve()
current = source_path
project_root = source_path
# Walk up the tree
while current.parent != current: # Stop at filesystem root
parent_index_db = self.source_to_index_db(current.parent)
if parent_index_db.exists():
project_root = current.parent
current = current.parent
else:
break
return project_root
def get_relative_depth(self, source_path: Path, project_root: Path) -> int:
"""Calculate directory depth relative to project root.
Args:
source_path: Target directory path
project_root: Project root directory path
Returns:
Number of directory levels from project_root to source_path
Raises:
ValueError: If source_path is not under project_root
Examples:
>>> mapper = PathMapper()
>>> mapper.get_relative_depth(
... Path("D:/Claude_dms3/src/codexlens"),
... Path("D:/Claude_dms3")
... )
2
"""
source_path = source_path.resolve()
project_root = project_root.resolve()
try:
relative = source_path.relative_to(project_root)
# Count path components
return len(relative.parts)
except ValueError:
raise ValueError(
f"Source path {source_path} is not under project root {project_root}"
)
def normalize_path(self, path: Path) -> str:
"""Normalize path to cross-platform storage format.
Converts OS-specific paths to a standardized format for storage:
- Windows: Removes drive colons (D: → D)
- Unix: Removes leading slash
- Uses forward slashes throughout
Args:
path: Path to normalize
Returns:
Normalized path string
Examples:
>>> mapper = PathMapper()
>>> mapper.normalize_path(Path("D:/path/to/dir"))
'D/path/to/dir'
>>> mapper.normalize_path(Path("/home/user/path"))
'home/user/path'
"""
path = path.resolve()
path_str = str(path)
# Handle Windows paths with drive letters
if platform.system() == "Windows" and len(path.parts) > 0:
# Convert D:\path\to\dir → D/path/to/dir
drive = path.parts[0].replace(":", "") # D: → D
rest = Path(*path.parts[1:]) if len(path.parts) > 1 else Path()
normalized = f"{drive}/{rest}".replace("\\", "/")
return normalized.rstrip("/")
# Handle Unix paths
# /home/user/path → home/user/path
return path_str.lstrip("/").replace("\\", "/")
def denormalize_path(self, normalized: str) -> Path:
"""Convert normalized path back to OS-specific path.
Reverses the normalization process to restore OS-native path format:
- Windows: Adds drive colons (D → D:)
- Unix: Adds leading slash
Args:
normalized: Normalized path string
Returns:
OS-specific Path object
Examples:
>>> mapper = PathMapper()
>>> mapper.denormalize_path("D/path/to/dir") # On Windows
WindowsPath('D:/path/to/dir')
>>> mapper.denormalize_path("home/user/path") # On Unix
PosixPath('/home/user/path')
"""
parts = normalized.split("/")
# Handle Windows paths
if platform.system() == "Windows" and len(parts) > 0:
# Check if first part is a drive letter
if len(parts[0]) == 1 and parts[0].isalpha():
# D/path/to/dir → D:/path/to/dir
drive = f"{parts[0]}:"
if len(parts) > 1:
return Path(drive) / Path(*parts[1:])
return Path(drive)
# Handle Unix paths or relative paths
# home/user/path → /home/user/path
return Path("/") / Path(*parts)