Skip to main content
Glama
generate_help.py8.36 kB
#!/usr/bin/env python3 """ Generate help output for the Makefile by parsing targets and their comments. This script provides cross-platform compatibility for the make help command. """ import re import sys import os import signal from pathlib import Path from typing import Dict, List, Tuple # Enable ANSI colors on Windows if os.name == 'nt': try: # Enable ANSI escape sequence processing on Windows 10+ import subprocess subprocess.run([''], shell=True, check=True, capture_output=True) os.system('') except: pass class RegexTimeoutError(Exception): """Raised when regex processing takes too long.""" pass def timeout_handler(signum, frame): """Signal handler for regex timeout.""" raise RegexTimeoutError("Regex processing timed out") def safe_regex_findall(pattern: str, text: str, flags: int = 0, timeout: int = 5) -> List[Tuple[str, str]]: """ Safely execute regex findall with timeout protection. Args: pattern: The regex pattern to match text: The text to search in flags: Regex flags timeout: Maximum time in seconds to allow for regex processing Returns: List of matches as tuples Raises: RegexTimeoutError: If regex processing takes longer than timeout """ # Set up timeout handler (Unix-like systems only) if hasattr(signal, 'SIGALRM'): old_handler = signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(timeout) try: # Compile the regex first to catch any compilation errors compiled_pattern = re.compile(pattern, flags) matches = compiled_pattern.findall(text) return matches except RegexTimeoutError: print("Warning: Regex processing timed out, using fallback parsing", file=sys.stderr) return [] except re.error as e: print(f"Warning: Regex compilation error: {e}, using fallback parsing", file=sys.stderr) return [] finally: # Clean up timeout handler if hasattr(signal, 'SIGALRM'): signal.alarm(0) signal.signal(signal.SIGALRM, old_handler) def parse_makefile(makefile_path: Path) -> Dict[str, List[Tuple[str, str]]]: """Parse the Makefile and extract targets with help comments.""" categories = { "Setup & Installation": ["install", "setup", "check-tools"], "Code Quality": ["lint", "format", "type-check", "security"], "Testing": ["test", "benchmark"], "Data Management": ["download", "list", "clean"], "Build & Distribution": ["build", "publish"], "Utilities": ["check", "ci", "run", "help"] } # Initialize result dictionary result = {category: [] for category in categories.keys()} try: with open(makefile_path, 'r', encoding='utf-8') as f: content = f.read() except FileNotFoundError: print(f"Error: Makefile not found at {makefile_path}") sys.exit(1) # Find all targets with help comments using a safe, non-backtracking regex # This pattern avoids catastrophic backtracking by: # 1. Using possessive quantifiers where possible # 2. Being very specific about what can appear between target and comment # 3. Limiting the search scope with line-by-line processing matches = [] try: lines = content.split('\n') # Limit processing to reasonable number of lines to prevent DoS max_lines = 10000 if len(lines) > max_lines: print(f"Warning: Makefile has {len(lines)} lines, processing only first {max_lines}", file=sys.stderr) lines = lines[:max_lines] # Process line by line to avoid backtracking issues target_pattern = re.compile(r'^([a-zA-Z][a-zA-Z0-9_-]{0,50}):') # Limit target name length comment_pattern = re.compile(r'## (.{0,200})$') # Limit comment length for line_num, line in enumerate(lines, 1): # Skip overly long lines that might cause issues if len(line) > 1000: continue try: # First check if line starts with a valid target target_match = target_pattern.match(line) if target_match: # Then check if it has a help comment comment_match = comment_pattern.search(line) if comment_match: target_name = target_match.group(1) comment_text = comment_match.group(1).strip() # Additional validation if target_name and comment_text: matches.append((target_name, comment_text)) except Exception as e: print(f"Warning: Error processing line {line_num}: {e}", file=sys.stderr) continue except Exception as e: print(f"Error parsing Makefile: {e}", file=sys.stderr) return {category: [] for category in categories.keys()} # Categorize targets with more specific matching for target, description in matches: # Use more specific categorization rules if target in ["install", "install-dev", "install-hooks", "setup-dev", "check-tools"]: result["Setup & Installation"].append((target, description)) elif target in ["lint", "format", "type-check", "security"]: result["Code Quality"].append((target, description)) elif target.startswith("test") or target == "benchmark": result["Testing"].append((target, description)) elif target.startswith("download") or target.startswith("list") or target.startswith("clean"): result["Data Management"].append((target, description)) elif target.startswith("build") or target.startswith("publish"): result["Build & Distribution"].append((target, description)) else: result["Utilities"].append((target, description)) # Sort targets within each category for category in result: result[category].sort(key=lambda x: x[0]) return result def format_help_output(categories: Dict[str, List[Tuple[str, str]]]) -> str: """Format the help output with colors and proper spacing.""" output = [] # Header output.append("OpenZIM MCP Development Commands") output.append("=" * 32) output.append("") # Check if colors should be used use_colors = True # Check if we're in a terminal that supports colors if not sys.stdout.isatty(): use_colors = False elif os.name == 'nt': # On Windows, try to enable ANSI color support try: # Enable ANSI escape sequence processing on Windows 10+ import ctypes kernel32 = ctypes.windll.kernel32 handle = kernel32.GetStdHandle(-11) # STD_OUTPUT_HANDLE mode = ctypes.c_ulong() kernel32.GetConsoleMode(handle, ctypes.byref(mode)) kernel32.SetConsoleMode(handle, mode.value | 0x0004) # ENABLE_VIRTUAL_TERMINAL_PROCESSING except: # If that fails, disable colors for better compatibility use_colors = False # Color codes if use_colors: blue = "\033[1;34m" cyan = "\033[36m" reset = "\033[0m" else: blue = cyan = reset = "" # Generate output for each category for category, targets in categories.items(): if targets: # Only show categories that have targets output.append(f"{blue}{category}:{reset}") for target, description in targets: output.append(f" {cyan}{target:<20}{reset} {description}") output.append("") return "\n".join(output) def main(): """Main function to generate and display help.""" # Find the Makefile (should be in the parent directory of this script) script_dir = Path(__file__).parent makefile_path = script_dir.parent / "Makefile" if not makefile_path.exists(): print(f"Error: Makefile not found at {makefile_path}") sys.exit(1) # Parse the Makefile and generate help categories = parse_makefile(makefile_path) help_output = format_help_output(categories) print(help_output) if __name__ == "__main__": main()

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/cameronrye/openzim-mcp'

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