- A1: Extract duplicated PATH discovery to utils.py (single source of truth) - A2: Convert metadata_reader dataclasses to Pydantic models in models.py - A3: Simplify get_wrapper() with module-level caching (removed fragile lifespan context) - A4: Document ILSpyWrapper design rationale (why class exists despite being stateless) - A5: Document MetadataReader as CPU-bound sync code with thread pool suggestion - A6: Create constants.py for all timeouts/limits (DECOMPILE_TIMEOUT_SECONDS, etc.) - A7: Add _compile_search_pattern() helper to deduplicate regex compilation - A8: Add LanguageVersion validation with helpful error listing valid options Tests pass, ruff clean.
51 lines
1.6 KiB
Python
51 lines
1.6 KiB
Python
"""Shared utility functions for mcilspy.
|
|
|
|
This module contains common utilities used across the codebase to avoid
|
|
code duplication and ensure consistent behavior.
|
|
"""
|
|
|
|
import logging
|
|
import os
|
|
import shutil
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def find_ilspycmd_path() -> str | None:
|
|
"""Find ilspycmd executable in PATH or common install locations.
|
|
|
|
This is the single source of truth for locating the ilspycmd binary.
|
|
It checks:
|
|
1. Standard PATH (via shutil.which)
|
|
2. ~/.dotnet/tools (default location for 'dotnet tool install --global')
|
|
3. Platform-specific locations (Windows %USERPROFILE%)
|
|
|
|
Returns:
|
|
Path to ilspycmd executable if found, None otherwise
|
|
"""
|
|
# Check PATH first (handles both ilspycmd and ilspycmd.exe)
|
|
for cmd_name in ["ilspycmd", "ilspycmd.exe"]:
|
|
path = shutil.which(cmd_name)
|
|
if path:
|
|
return path
|
|
|
|
# Check common dotnet tools locations (not always in PATH for MCP servers)
|
|
home = os.path.expanduser("~")
|
|
candidates = [
|
|
os.path.join(home, ".dotnet", "tools", "ilspycmd"),
|
|
os.path.join(home, ".dotnet", "tools", "ilspycmd.exe"),
|
|
]
|
|
|
|
# Windows-specific: also check USERPROFILE if different from ~
|
|
if os.name == "nt":
|
|
userprofile = os.environ.get("USERPROFILE", "")
|
|
if userprofile and userprofile != home:
|
|
candidates.append(os.path.join(userprofile, ".dotnet", "tools", "ilspycmd.exe"))
|
|
|
|
for candidate in candidates:
|
|
if os.path.isfile(candidate) and os.access(candidate, os.X_OK):
|
|
logger.info(f"Found ilspycmd at {candidate} (not in PATH)")
|
|
return candidate
|
|
|
|
return None
|