Coverage for openhcs/validation/validate.py: 0.0%
68 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-04 02:09 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-04 02:09 +0000
1#!/usr/bin/env python
2"""
3Command-line tool for running AST-based validation on openhcs codebase.
5This tool validates Python files for compliance with openhcs's architectural
6principles using static AST-based analysis.
7"""
9import argparse
10import sys
11from pathlib import Path
12from typing import List, Optional, Set
14from openhcs.validation.ast_validator import ValidationViolation, validate_file
17def find_python_files(directory: Path, exclude_dirs: Optional[Set[str]] = None) -> List[Path]:
18 """
19 Find all Python files in a directory recursively using breadth-first traversal.
21 Args:
22 directory: Directory to search.
23 exclude_dirs: Set of directory names to exclude.
25 Returns:
26 List of Python file paths sorted by depth (shallower first).
27 """
28 from collections import deque
30 if exclude_dirs is None:
31 exclude_dirs = set()
33 python_files = []
34 # Use deque for breadth-first traversal
35 dirs_to_search = deque([(directory, 0)]) # (path, depth)
37 while dirs_to_search:
38 current_dir, depth = dirs_to_search.popleft()
40 try:
41 for entry in current_dir.iterdir():
42 if entry.is_file() and entry.suffix == '.py':
43 python_files.append((entry, depth))
44 elif entry.is_dir() and entry.name not in exclude_dirs:
45 # Add subdirectory to queue for later processing
46 dirs_to_search.append((entry, depth + 1))
47 except (PermissionError, OSError):
48 # Skip directories we can't read
49 continue
51 # Sort by depth first, then by path for consistent ordering
52 python_files.sort(key=lambda x: (x[1], str(x[0])))
54 # Return just the paths
55 return [file_path for file_path, _ in python_files]
58def validate_directory(directory: Path, exclude_dirs: Optional[Set[str]] = None) -> List[ValidationViolation]:
59 """
60 Validate all Python files in a directory.
62 Args:
63 directory: Directory to validate.
64 exclude_dirs: Set of directory names to exclude.
66 Returns:
67 List of validation violations.
68 """
69 python_files = find_python_files(directory, exclude_dirs)
70 violations = []
72 for file_path in python_files:
73 file_violations = validate_file(str(file_path))
74 violations.extend(file_violations)
76 return violations
79def main():
80 """Main entry point for the validation tool."""
81 parser = argparse.ArgumentParser(
82 description='AST-based validation for openhcs codebase'
83 )
84 parser.add_argument(
85 'paths',
86 nargs='+',
87 type=str,
88 help='Files or directories to validate'
89 )
90 parser.add_argument(
91 '--exclude',
92 nargs='+',
93 default=['__pycache__', '.git', 'venv', 'env', '.venv', '.env'],
94 help='Directories to exclude from validation'
95 )
96 parser.add_argument(
97 '--output',
98 type=str,
99 help='Output file for validation results (default: stdout)'
100 )
101 parser.add_argument(
102 '--fail-on-error',
103 action='store_true',
104 help='Exit with non-zero status if violations are found'
105 )
107 args = parser.parse_args()
109 # Convert paths to Path objects
110 paths = [Path(p) for p in args.paths]
111 exclude_dirs = set(args.exclude)
113 # Collect all violations
114 all_violations = []
116 for path in paths:
117 if path.is_file() and path.suffix == '.py':
118 file_violations = validate_file(str(path))
119 all_violations.extend(file_violations)
120 elif path.is_dir():
121 dir_violations = validate_directory(path, exclude_dirs)
122 all_violations.extend(dir_violations)
123 else:
124 print(f"Warning: {path} is not a Python file or directory", file=sys.stderr)
126 # Group violations by file
127 violations_by_file = {}
128 for violation in all_violations:
129 if violation.file_path not in violations_by_file:
130 violations_by_file[violation.file_path] = []
131 violations_by_file[violation.file_path].append(violation)
133 # Sort violations by file and line number
134 for file_path in violations_by_file:
135 violations_by_file[file_path].sort(key=lambda v: v.line_number)
137 # Output violations
138 output_file = open(args.output, 'w') if args.output else sys.stdout
140 try:
141 if all_violations:
142 print(f"Found {len(all_violations)} validation violations:", file=output_file)
144 for file_path, violations in sorted(violations_by_file.items()):
145 print(f"\n{file_path}:", file=output_file)
147 for violation in violations:
148 print(f" Line {violation.line_number}: {violation.violation_type} - {violation.message}",
149 file=output_file)
151 if args.fail_on_error:
152 sys.exit(1)
153 else:
154 print("No validation violations found.", file=output_file)
155 finally:
156 if args.output:
157 output_file.close()
160if __name__ == '__main__':
161 main()