| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- """
- Utility module.
- This module provides utility functions used across the coverage check scripts.
- """
- import os
- import sys
- import re
- import shlex
- import subprocess
- import traceback
- from typing import List, Tuple, Dict, Any, Optional, Union
- # List of allowed commands and their arguments
- ALLOWED_COMMANDS = {
- 'xvfb-run': ['-a'],
- 'npm': ['run', 'test:coverage', 'ci', 'install', '--no-save', '@vitest/coverage-v8', 'check-types', 'lint', 'format', 'compile'],
- 'cd': ['webview-ui'],
- 'python': ['-m', 'coverage_check'],
- 'git': ['fetch', 'checkout', 'origin'],
- }
- def is_safe_command(command: Union[str, List[str]]) -> bool:
- """
- Check if a command is safe to execute.
-
- Args:
- command: Command to check (string or list)
-
- Returns:
- True if command is safe, False otherwise
- """
- # Convert string command to list
- if isinstance(command, str):
- try:
- cmd_parts = shlex.split(command)
- except ValueError:
- return False
- else:
- cmd_parts = command
- if not cmd_parts:
- return False
- # Get base command
- base_cmd = os.path.basename(cmd_parts[0])
-
- # Check if command is in allowed list
- if base_cmd not in ALLOWED_COMMANDS:
- return False
-
- # For each argument, check for suspicious patterns
- for arg in cmd_parts[1:]:
- # Check for shell metacharacters
- if re.search(r'[;&|`$]', arg):
- return False
- # Check for path traversal
- if '..' in arg and not (base_cmd == 'npm' and arg.startswith('@')):
- return False
-
- return True
- def log(message: str) -> None:
- """
- Write a message to stdout and flush.
-
- Args:
- message: The message to write
- """
- sys.stdout.write(f"{message}\n")
- sys.stdout.flush()
- def file_exists(file_path: str) -> bool:
- """
- Check if a file exists.
-
- Args:
- file_path: Path to the file
-
- Returns:
- True if the file exists, False otherwise
- """
- return os.path.exists(file_path) and os.path.isfile(file_path)
- def get_file_size(file_path: str) -> int:
- """
- Get the size of a file in bytes.
-
- Args:
- file_path: Path to the file
-
- Returns:
- Size of the file in bytes, or 0 if the file doesn't exist
- """
- if file_exists(file_path):
- return os.path.getsize(file_path)
- return 0
- def list_directory(dir_path: str) -> List[Tuple[str, Union[int, str]]]:
- """
- List the contents of a directory.
-
- Args:
- dir_path: Path to the directory
-
- Returns:
- List of (name, size) tuples for each file/directory in the directory
- """
- if not os.path.exists(dir_path) or not os.path.isdir(dir_path):
- return []
-
- contents = []
- for item in os.listdir(dir_path):
- item_path = os.path.join(dir_path, item)
- if os.path.isfile(item_path):
- contents.append((item, os.path.getsize(item_path)))
- else:
- contents.append((item, "DIR"))
-
- return contents
- def read_file_content(file_path: str, default: str = "") -> str:
- """
- Read file content with error handling.
-
- Args:
- file_path: Path to the file
- default: Default value to return if file cannot be read
-
- Returns:
- File content or default value
- """
- if not file_exists(file_path):
- log(f"File does not exist: {file_path}")
- return default
-
- try:
- with open(file_path, 'r') as f:
- return f.read()
- except Exception as e:
- log(f"Error reading file {file_path}: {e}")
- return default
- def write_file_content(file_path: str, content: str) -> bool:
- """
- Write content to file with error handling.
-
- Args:
- file_path: Path to the file
- content: Content to write
-
- Returns:
- True if successful, False otherwise
- """
- try:
- # Create directory if it doesn't exist
- os.makedirs(os.path.dirname(file_path), exist_ok=True)
-
- with open(file_path, 'w') as f:
- f.write(content)
- return True
- except Exception as e:
- log(f"Error writing to file {file_path}: {e}")
- return False
- def run_command(command: Union[str, List[str]], capture_output: bool = True) -> Tuple[int, str, str]:
- """
- Run a command and return the result.
-
- Args:
- command: Command to run (string or list)
- capture_output: Whether to capture stdout/stderr
-
- Returns:
- Tuple of (returncode, stdout, stderr)
- """
- if not is_safe_command(command):
- error_msg = f"Unsafe command detected: {command}"
- log(error_msg)
- return 1, "", error_msg
-
- log(f"Running command: {command}")
- try:
- # Convert string command to list
- if isinstance(command, str):
- cmd_list = shlex.split(command)
- else:
- cmd_list = command
-
- result = subprocess.run(
- cmd_list,
- shell=False, # Never use shell=True for security
- capture_output=capture_output,
- text=True
- )
- log(f"Command exit code: {result.returncode}")
- return result.returncode, result.stdout, result.stderr
- except Exception as e:
- log(f"Error running command: {e}")
- log(traceback.format_exc())
- return 1, "", str(e)
- def find_pattern(content: str, pattern: str, group: int = 0,
- default: Optional[str] = None) -> Optional[str]:
- """
- Find a pattern in content and return the specified group.
-
- Args:
- content: Text content to search
- pattern: Regex pattern to search for
- group: Group number to return (default: 0 for entire match)
- default: Default value to return if pattern not found
-
- Returns:
- Matched text or default value
- """
- match = re.search(pattern, content, re.DOTALL)
- if match:
- return match.group(group)
- return default
- def get_env_var(name: str, default: Optional[str] = None) -> Optional[str]:
- """
- Get environment variable with default value.
-
- Args:
- name: Environment variable name
- default: Default value if not set
-
- Returns:
- Environment variable value or default
- """
- return os.environ.get(name, default)
- def format_exception(e: Exception) -> str:
- """
- Format an exception with traceback for logging.
-
- Args:
- e: Exception to format
-
- Returns:
- Formatted exception string
- """
- return f"{type(e).__name__}: {str(e)}\n{traceback.format_exc()}"
|