Skip to main content
Glama
path_resolver.py10 kB
""" 路径和引用处理工具 提供导入路径解析、文件引用查找、路径更新等功能。 """ import re from pathlib import Path from typing import List, Dict, Any, Optional, Tuple from .exceptions import FileOperationError from .content_processor import read_file_safe, replace_content from .file_search import find_files def normalize_path(path: str | Path) -> Path: """ 规范化路径 Args: path: 路径字符串或 Path 对象 Returns: 规范化的 Path 对象 """ return Path(path).resolve() def resolve_import_path( import_statement: str, current_file: str | Path, project_root: Optional[str | Path] = None, ) -> Optional[Path]: """ 解析导入路径为实际文件路径 Args: import_statement: 导入语句(如 "from module import class" 或 "import module") current_file: 当前文件路径 project_root: 项目根目录(如果为 None 则从 current_file 推断) Returns: 解析后的文件路径,如果无法解析则返回 None """ current = Path(current_file).resolve() if project_root: root = Path(project_root).resolve() else: # 尝试从当前文件推断项目根目录 root = current.parent # 向上查找包含常见项目标识的目录 for parent in current.parents: if (parent / "pyproject.toml").exists() or \ (parent / "package.json").exists() or \ (parent / "pubspec.yaml").exists() or \ (parent / "build.gradle").exists(): root = parent break # 解析导入语句 # Python: "from package.module import class" 或 "import module" # JavaScript: "import module from './module'" 或 "const module = require('./module')" # Dart: "import 'package:app/module.dart'" # Kotlin: "import com.example.module" # 简化实现:提取模块名 module_match = re.search(r'from\s+["\']?([^"\']+)["\']?|import\s+["\']?([^"\']+)["\']?', import_statement) if not module_match: return None module_name = module_match.group(1) or module_match.group(2) if not module_name: return None # 尝试不同的路径解析策略 # 1. 相对路径(以 . 或 .. 开头) if module_name.startswith(".") or module_name.startswith("/"): if module_name.startswith("."): # 相对导入 parts = module_name.split(".") base_path = current.parent for part in parts: if part == "": continue elif part == "..": base_path = base_path.parent else: base_path = base_path / part # 尝试不同的扩展名 for ext in [".py", ".js", ".ts", ".dart", ".kt", ".java"]: file_path = base_path.with_suffix(ext) if file_path.exists(): return file_path # 尝试作为目录的 __init__.py 或 index.js if ext == ".py": init_file = base_path / "__init__.py" if init_file.exists(): return init_file elif ext in [".js", ".ts"]: index_file = base_path / f"index{ext}" if index_file.exists(): return index_file else: # 绝对路径 file_path = root / module_name.lstrip("/") if file_path.exists(): return file_path # 2. 包导入(如 package.module) else: # 将点分隔的模块名转换为路径 parts = module_name.split(".") base_path = root for part in parts: base_path = base_path / part # 尝试不同的扩展名 for ext in [".py", ".js", ".ts", ".dart", ".kt", ".java"]: file_path = base_path.with_suffix(ext) if file_path.exists(): return file_path # 尝试作为目录 if ext == ".py": init_file = base_path / "__init__.py" if init_file.exists(): return init_file elif ext in [".js", ".ts"]: index_file = base_path / f"index{ext}" if index_file.exists(): return index_file return None def find_file_references_by_path( target_file: str | Path, search_dir: str | Path, language: Optional[str] = None, ) -> List[Dict[str, Any]]: """ 查找文件引用(通过路径) Args: target_file: 目标文件路径 search_dir: 搜索目录 language: 语言类型(如果为 None 则自动检测) Returns: 引用信息列表,每个引用包含: - file: 引用文件路径 - line_number: 行号 - import_statement: 导入语句 """ from .dependency_analyzer import detect_language, parse_imports target = Path(target_file).resolve() search = Path(search_dir).resolve() if not target.exists(): raise FileNotFoundError(f"目标文件不存在: {target}") if language is None: language = detect_language(target) if language is None: return [] # 获取目标文件的相对路径(相对于搜索目录) try: target_relative = target.relative_to(search) except ValueError: # 目标文件不在搜索目录内 return [] # 查找所有可能的引用文件 if language == "python": extensions = [".py"] elif language == "javascript": extensions = [".js", ".jsx", ".ts", ".tsx"] elif language == "dart": extensions = [".dart"] elif language == "kotlin": extensions = [".kt"] elif language == "java": extensions = [".java"] else: return [] references = [] for ext in extensions: files = find_files(search, extension=ext, recursive=True) for file_path in files: if file_path == target: continue content = read_file_safe(file_path) lines = content.splitlines() # 检查每一行的导入语句 for line_num, line in enumerate(lines, 1): imports = parse_imports_from_line(line, language) for imp in imports: resolved = resolve_import_path(imp, file_path, search) if resolved and resolved.resolve() == target.resolve(): references.append({ "file": str(file_path), "line_number": line_num, "import_statement": imp, }) return references def parse_imports_from_line(line: str, language: str) -> List[str]: """从单行解析导入语句""" imports = [] if language == "python": if re.match(r'^\s*(import|from)\s+', line): imports.append(line.strip()) elif language == "javascript": if re.match(r'^\s*import\s+', line) or re.search(r'require\(["\']', line): imports.append(line.strip()) elif language == "dart": if re.match(r'^\s*import\s+', line): imports.append(line.strip()) elif language in ["kotlin", "java"]: if re.match(r'^\s*import\s+', line): imports.append(line.strip()) return imports def update_import_paths( file_path: str | Path, old_path: str | Path, new_path: str | Path, project_root: Optional[str | Path] = None, ) -> int: """ 更新文件中的导入路径 Args: file_path: 要更新的文件路径 old_path: 旧路径 new_path: 新路径 project_root: 项目根目录 Returns: 更新的导入语句数量 """ from .dependency_analyzer import detect_language path = Path(file_path) old = Path(old_path).resolve() new = Path(new_path).resolve() if not path.exists(): raise FileNotFoundError(f"文件不存在: {path}") language = detect_language(path) if language is None: return 0 content = read_file_safe(path) lines = content.splitlines(keepends=True) updated_count = 0 for i, line in enumerate(lines): imports = parse_imports_from_line(line, language) for imp in imports: resolved = resolve_import_path(imp, path, project_root) if resolved and resolved.resolve() == old.resolve(): # 计算新的相对路径 new_relative = new.relative_to(path.parent) # 更新导入语句(简化实现) # 实际应该更智能地处理不同语言的导入语法 new_import = _generate_new_import(imp, new_relative, language) lines[i] = line.replace(imp, new_import) updated_count += 1 if updated_count > 0: new_content = "".join(lines) from .safe_writer import SafeFileWriter writer = SafeFileWriter(path, backup=True) writer.write(new_content) return updated_count def _generate_new_import(old_import: str, new_relative: Path, language: str) -> str: """生成新的导入语句""" # 简化实现,实际应该更智能 if language == "python": # 将路径转换为点分隔的模块名 parts = new_relative.parts module_name = ".".join(parts).replace(".py", "").replace("/", ".") if old_import.startswith("from"): return f"from {module_name} import" else: return f"import {module_name}" else: # 其他语言类似处理 return old_import

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/yfcyfc123234/showdoc_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server