backup materials and knowledge-base docs
This commit is contained in:
333
文档润色流和知识库构建流/claude-scholar-upstream/utils/platform_utils.py
Normal file
333
文档润色流和知识库构建流/claude-scholar-upstream/utils/platform_utils.py
Normal file
@@ -0,0 +1,333 @@
|
||||
"""
|
||||
Cross-platform utility functions for Python projects
|
||||
Works on Windows, macOS, and Linux
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import platform
|
||||
import subprocess
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from typing import List, Optional, Dict, Any
|
||||
import glob as glob_module
|
||||
|
||||
# Platform detection
|
||||
IS_WINDOWS = platform.system() == 'Windows'
|
||||
IS_MACOS = platform.system() == 'Darwin'
|
||||
IS_LINUX = platform.system() == 'Linux'
|
||||
|
||||
|
||||
def get_home_dir() -> Path:
|
||||
"""Get the user's home directory (cross-platform)"""
|
||||
return Path.home()
|
||||
|
||||
|
||||
def get_claude_dir() -> Path:
|
||||
"""Get the Claude config directory"""
|
||||
return get_home_dir() / '.claude'
|
||||
|
||||
|
||||
def get_temp_dir() -> Path:
|
||||
"""Get the temp directory (cross-platform)"""
|
||||
return Path(os.tmpdir())
|
||||
|
||||
|
||||
def get_plan_dir() -> Path:
|
||||
"""Get the plan directory"""
|
||||
plan_dir = get_claude_dir() / 'plan'
|
||||
plan_dir.mkdir(parents=True, exist_ok=True)
|
||||
return plan_dir
|
||||
|
||||
|
||||
def ensure_dir(dir_path: Path) -> Path:
|
||||
"""Ensure a directory exists (create if not)"""
|
||||
dir_path.mkdir(parents=True, exist_ok=True)
|
||||
return dir_path
|
||||
|
||||
|
||||
def get_date_string() -> str:
|
||||
"""Get current date in YYYY-MM-DD format"""
|
||||
return datetime.now().strftime('%Y-%m-%d')
|
||||
|
||||
|
||||
def get_time_string() -> str:
|
||||
"""Get current time in HH:MM format"""
|
||||
return datetime.now().strftime('%H:%M')
|
||||
|
||||
|
||||
def get_datetime_string() -> str:
|
||||
"""Get current datetime in YYYY-MM-DD HH:MM:SS format"""
|
||||
return datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
|
||||
def find_files(dir_path: Path, pattern: str, max_age_days: Optional[int] = None, recursive: bool = False) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Find files matching a pattern in a directory (cross-platform)
|
||||
|
||||
Args:
|
||||
dir_path: Directory to search
|
||||
pattern: File pattern (e.g., "*.tmp", "*.md")
|
||||
max_age_days: Only return files modified within this many days
|
||||
recursive: Search subdirectories
|
||||
|
||||
Returns:
|
||||
List of dicts with 'path' and 'mtime' keys
|
||||
"""
|
||||
if not dir_path.exists():
|
||||
return []
|
||||
|
||||
results = []
|
||||
|
||||
# Use glob for pattern matching
|
||||
glob_pattern = str(dir_path / ('**' if recursive else '') / pattern)
|
||||
files = glob_module.glob(glob_pattern, recursive=recursive)
|
||||
|
||||
for file_path in files:
|
||||
file_path = Path(file_path)
|
||||
try:
|
||||
stat = file_path.stat()
|
||||
mtime = stat.st_mtime
|
||||
|
||||
if max_age_days is not None:
|
||||
import time
|
||||
age_in_days = (time.time() - mtime) / (86400)
|
||||
if age_in_days <= max_age_days:
|
||||
results.append({'path': str(file_path), 'mtime': mtime})
|
||||
else:
|
||||
results.append({'path': str(file_path), 'mtime': mtime})
|
||||
except (OSError, PermissionError):
|
||||
# Ignore permission errors
|
||||
pass
|
||||
|
||||
# Sort by modification time (newest first)
|
||||
results.sort(key=lambda x: x['mtime'], reverse=True)
|
||||
return results
|
||||
|
||||
|
||||
def command_exists(cmd: str) -> bool:
|
||||
"""
|
||||
Check if a command exists in PATH (cross-platform)
|
||||
Uses shutil.which for safety
|
||||
"""
|
||||
if not cmd or not isinstance(cmd, str):
|
||||
return False
|
||||
|
||||
# Basic validation - only allow alphanumeric, dash, underscore, dot
|
||||
if not cmd.replace('-', '').replace('_', '').replace('.', '').isalnum():
|
||||
return False
|
||||
|
||||
return shutil.which(cmd) is not None
|
||||
|
||||
|
||||
def run_command(cmd: List[str], cwd: Optional[Path] = None, capture: bool = True) -> Dict[str, Any]:
|
||||
"""
|
||||
Run a command and return output
|
||||
|
||||
SECURITY NOTE: This function executes shell commands. Only use with
|
||||
trusted, hardcoded commands. Never pass user-controlled input directly.
|
||||
|
||||
Args:
|
||||
cmd: Command and arguments as list (safer than string)
|
||||
cwd: Working directory
|
||||
capture: Whether to capture output
|
||||
|
||||
Returns:
|
||||
Dict with 'success', 'output', 'returncode' keys
|
||||
"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
cwd=cwd,
|
||||
capture_output=capture,
|
||||
text=True,
|
||||
check=False
|
||||
)
|
||||
return {
|
||||
'success': result.returncode == 0,
|
||||
'output': result.stdout.strip(),
|
||||
'stderr': result.stderr.strip(),
|
||||
'returncode': result.returncode
|
||||
}
|
||||
except (subprocess.SubprocessError, FileNotFoundError) as e:
|
||||
return {
|
||||
'success': False,
|
||||
'output': '',
|
||||
'stderr': str(e),
|
||||
'returncode': -1
|
||||
}
|
||||
|
||||
|
||||
def is_git_repo(cwd: Optional[Path] = None) -> bool:
|
||||
"""Check if current directory is a git repository"""
|
||||
result = run_command(['git', 'rev-parse', '--git-dir'], cwd=cwd)
|
||||
return result['success']
|
||||
|
||||
|
||||
def get_git_modified_files(patterns: Optional[List[str]] = None) -> List[str]:
|
||||
"""Get git modified files"""
|
||||
if not is_git_repo():
|
||||
return []
|
||||
|
||||
result = run_command(['git', 'diff', '--name-only', 'HEAD'])
|
||||
if not result['success']:
|
||||
return []
|
||||
|
||||
files = result['output'].split('\n') if result['output'] else []
|
||||
files = [f for f in files if f]
|
||||
|
||||
if patterns:
|
||||
import re
|
||||
files = [
|
||||
f for f in files
|
||||
if any(re.search(pattern, f) for pattern in patterns)
|
||||
]
|
||||
|
||||
return files
|
||||
|
||||
|
||||
def read_file(file_path: Path) -> Optional[str]:
|
||||
"""Read a text file safely"""
|
||||
try:
|
||||
return file_path.read_text(encoding='utf-8')
|
||||
except (FileNotFoundError, PermissionError, UnicodeDecodeError):
|
||||
return None
|
||||
|
||||
|
||||
def write_file(file_path: Path, content: str) -> bool:
|
||||
"""Write a text file"""
|
||||
try:
|
||||
ensure_dir(file_path.parent)
|
||||
file_path.write_text(content, encoding='utf-8')
|
||||
return True
|
||||
except (OSError, PermissionError):
|
||||
return False
|
||||
|
||||
|
||||
def append_file(file_path: Path, content: str) -> bool:
|
||||
"""Append to a text file"""
|
||||
try:
|
||||
ensure_dir(file_path.parent)
|
||||
with open(file_path, 'a', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
return True
|
||||
except (OSError, PermissionError):
|
||||
return False
|
||||
|
||||
|
||||
def replace_in_file(file_path: Path, search: str, replace: str) -> bool:
|
||||
"""Replace text in a file (cross-platform sed alternative)"""
|
||||
content = read_file(file_path)
|
||||
if content is None:
|
||||
return False
|
||||
|
||||
new_content = content.replace(search, replace)
|
||||
return write_file(file_path, new_content)
|
||||
|
||||
|
||||
def count_in_file(file_path: Path, pattern: str) -> int:
|
||||
"""Count occurrences of a pattern in a file"""
|
||||
content = read_file(file_path)
|
||||
if content is None:
|
||||
return 0
|
||||
|
||||
import re
|
||||
matches = re.findall(pattern, content)
|
||||
return len(matches)
|
||||
|
||||
|
||||
def grep_file(file_path: Path, pattern: str) -> List[Dict[str, Any]]:
|
||||
"""Search for pattern in file and return matching lines with line numbers"""
|
||||
content = read_file(file_path)
|
||||
if content is None:
|
||||
return []
|
||||
|
||||
import re
|
||||
lines = content.split('\n')
|
||||
results = []
|
||||
|
||||
for idx, line in enumerate(lines, 1):
|
||||
if re.search(pattern, line):
|
||||
results.append({'line_number': idx, 'content': line})
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def get_platform_info() -> Dict[str, Any]:
|
||||
"""Get platform information"""
|
||||
return {
|
||||
'system': platform.system(),
|
||||
'release': platform.release(),
|
||||
'version': platform.version(),
|
||||
'machine': platform.machine(),
|
||||
'processor': platform.processor(),
|
||||
'is_windows': IS_WINDOWS,
|
||||
'is_macos': IS_MACOS,
|
||||
'is_linux': IS_LINUX,
|
||||
'python_version': sys.version_info,
|
||||
'home_dir': str(get_home_dir()),
|
||||
'temp_dir': str(get_temp_dir()),
|
||||
}
|
||||
|
||||
|
||||
def log(message: str) -> None:
|
||||
"""Log to stderr (visible to user in Claude Code)"""
|
||||
print(message, file=sys.stderr)
|
||||
|
||||
|
||||
def output(data: Any) -> None:
|
||||
"""Output to stdout (returned to Claude)"""
|
||||
if isinstance(data, (dict, list)):
|
||||
import json
|
||||
print(json.dumps(data, ensure_ascii=False, indent=2))
|
||||
else:
|
||||
print(data)
|
||||
|
||||
|
||||
# Module exports
|
||||
__all__ = [
|
||||
# Platform info
|
||||
'IS_WINDOWS',
|
||||
'IS_MACOS',
|
||||
'IS_LINUX',
|
||||
'get_platform_info',
|
||||
|
||||
# Directories
|
||||
'get_home_dir',
|
||||
'get_claude_dir',
|
||||
'get_temp_dir',
|
||||
'get_plan_dir',
|
||||
'ensure_dir',
|
||||
|
||||
# Date/Time
|
||||
'get_date_string',
|
||||
'get_time_string',
|
||||
'get_datetime_string',
|
||||
|
||||
# File operations
|
||||
'find_files',
|
||||
'read_file',
|
||||
'write_file',
|
||||
'append_file',
|
||||
'replace_in_file',
|
||||
'count_in_file',
|
||||
'grep_file',
|
||||
|
||||
# System
|
||||
'command_exists',
|
||||
'run_command',
|
||||
'is_git_repo',
|
||||
'get_git_modified_files',
|
||||
|
||||
# I/O
|
||||
'log',
|
||||
'output',
|
||||
]
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Test platform detection
|
||||
print("Platform Info:")
|
||||
for key, value in get_platform_info().items():
|
||||
print(f" {key}: {value}")
|
||||
Reference in New Issue
Block a user