utils.py•9.96 kB
import platform
from pathlib import Path
try:
from git import Repo
GIT_AVAILABLE = True
except ImportError:
GIT_AVAILABLE = False
def get_os_info() -> tuple[str, str, str]:
"""Get the operating system information.
Returns:
tuple: A tuple containing the system name, release, and version.
"""
system = platform.system() # noqa: F821
release = platform.release()
version = platform.version()
if system == "Darwin":
system = "MacOS"
elif system == "Linux":
try:
with open("/etc/os-release") as f:
for line in f:
if line.startswith("NAME="):
name = line.split("=")[1].strip().strip('"')
if "Ubuntu" in name:
system = "Ubuntu"
elif "Debian" in name:
system = "Debian"
elif "Fedora" in name:
system = "Fedora"
elif "CentOS" in name:
system = "CentOS"
elif "Arch Linux" in name:
system = "Arch Linux"
system = name
except FileNotFoundError:
dist = platform.freedesktop_os_release()
if dist and "NAME" in dist:
name = dist["NAME"]
if "Ubuntu" in name:
system = "Ubuntu"
system = name
system = "Linux"
elif system == "Java":
system = "Java"
return system, release, version
def get_directory_structure(
path: str, max_depth: int = 3, include_filtered: bool = False
) -> str:
"""Get a directory structure similar to directory_tree tool.
Args:
path: The directory path to scan
max_depth: Maximum depth to traverse (0 for unlimited)
include_filtered: Whether to include normally filtered directories
Returns:
Formatted directory structure as a string
"""
try:
dir_path = Path(path)
if not dir_path.exists() or not dir_path.is_dir():
return f"Error: {path} is not a valid directory"
# Define filtered directories (same as directory_tree.py)
FILTERED_DIRECTORIES = {
".git",
"node_modules",
".venv",
"venv",
"__pycache__",
".pytest_cache",
".idea",
".vs",
".vscode",
"dist",
"build",
"target",
".ruff_cache",
".llm-context",
}
def should_filter(current_path: Path) -> bool:
"""Check if a directory should be filtered."""
# Don't filter if it's the explicitly requested path
if str(current_path.absolute()) == str(dir_path.absolute()):
return False
# Filter based on directory name if filtering is enabled
return current_path.name in FILTERED_DIRECTORIES and not include_filtered
def build_tree(current_path: Path, current_depth: int = 0) -> list[dict]:
"""Build directory tree recursively."""
result = []
try:
# Sort entries: directories first, then files alphabetically
entries = sorted(
current_path.iterdir(), key=lambda x: (not x.is_dir(), x.name)
)
for entry in entries:
if entry.is_dir():
entry_data = {"name": entry.name, "type": "directory"}
# Check if we should filter this directory
if should_filter(entry):
entry_data["skipped"] = "filtered-directory"
result.append(entry_data)
continue
# Check depth limit (if enabled)
if max_depth > 0 and current_depth >= max_depth:
entry_data["skipped"] = "depth-limit"
result.append(entry_data)
continue
# Process children recursively
entry_data["children"] = build_tree(entry, current_depth + 1)
result.append(entry_data)
else:
# Add files only if within depth limit
if max_depth <= 0 or current_depth < max_depth:
result.append({"name": entry.name, "type": "file"})
except Exception:
# Skip directories we can't read
pass
return result
def format_tree(tree_data: list[dict], level: int = 0) -> list[str]:
"""Format tree data as indented strings."""
lines = []
for item in tree_data:
# Indentation based on level
indent = " " * level
# Format based on type
if item["type"] == "directory":
if "skipped" in item:
lines.append(
f"{indent}{item['name']}/ [skipped - {item['skipped']}]"
)
else:
lines.append(f"{indent}{item['name']}/")
# Add children with increased indentation if present
if "children" in item:
lines.extend(format_tree(item["children"], level + 1))
else:
# File
lines.append(f"{indent}{item['name']}")
return lines
# Build and format the tree
tree_data = build_tree(dir_path)
formatted_lines = format_tree(tree_data)
# Add the root directory path as a prefix
result = f"- {dir_path}/"
if formatted_lines:
result += "\n" + "\n".join(f" {line}" for line in formatted_lines)
return result
except Exception as e:
return f"Error generating directory structure: {str(e)}"
def get_git_info(path: str) -> dict[str, str | None]:
"""Get git information for a repository.
Args:
path: Path to the git repository
Returns:
Dictionary containing git information
"""
if not GIT_AVAILABLE:
return {
"current_branch": None,
"main_branch": None,
"git_status": "GitPython not available",
"recent_commits": "GitPython not available",
}
try:
repo = Repo(path)
# Get current branch
try:
current_branch = repo.active_branch.name
except Exception:
current_branch = "HEAD (detached)"
# Try to determine main branch
main_branch = "main" # default
try:
# Check if 'main' exists
if "origin/main" in [ref.name for ref in repo.refs]:
main_branch = "main"
elif "origin/master" in [ref.name for ref in repo.refs]:
main_branch = "master"
elif "main" in [ref.name for ref in repo.refs]:
main_branch = "main"
elif "master" in [ref.name for ref in repo.refs]:
main_branch = "master"
except Exception:
pass
# Get git status
try:
status_lines = []
# Check for staged changes
staged_files = list(repo.index.diff("HEAD"))
if staged_files:
for item in staged_files[:25]: # Limit to first 25
change_type = item.change_type
status_lines.append(f"{change_type[0].upper()} {item.a_path}")
if len(staged_files) > 25:
status_lines.append(
f"... and {len(staged_files) - 25} more staged files"
)
# Check for unstaged changes
unstaged_files = list(repo.index.diff(None))
if unstaged_files:
for item in unstaged_files[:25]: # Limit to first 25
status_lines.append(f"M {item.a_path}")
if len(unstaged_files) > 25:
status_lines.append(
f"... and {len(unstaged_files) - 25} more modified files"
)
# Check for untracked files
untracked_files = repo.untracked_files
if untracked_files:
for file in untracked_files[:25]: # Limit to first 25
status_lines.append(f"?? {file}")
if len(untracked_files) > 25:
status_lines.append(
f"... and {len(untracked_files) - 25} more untracked files"
)
git_status = (
"\n".join(status_lines) if status_lines else "Working tree clean"
)
except Exception:
git_status = "Unable to get git status"
# Get recent commits
try:
commits = []
for commit in repo.iter_commits(max_count=5):
short_hash = commit.hexsha[:7]
message = commit.message.split("\n")[0] # First line only
commits.append(f"{short_hash} {message}")
recent_commits = "\n".join(commits)
except Exception:
recent_commits = "Unable to get recent commits"
return {
"current_branch": current_branch,
"main_branch": main_branch,
"git_status": git_status,
"recent_commits": recent_commits,
}
except Exception as e:
return {
"current_branch": None,
"main_branch": None,
"git_status": f"Error: {str(e)}",
"recent_commits": f"Error: {str(e)}",
}