Coverage for openhcs/validation/validate.py: 0.0%
69 statements
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-14 05:57 +0000
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-14 05:57 +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 os
11import sys
12from pathlib import Path
13from typing import List, Optional, Set
15from openhcs.validation.ast_validator import ValidationViolation, validate_file
18def find_python_files(directory: Path, exclude_dirs: Optional[Set[str]] = None) -> List[Path]:
19 """
20 Find all Python files in a directory recursively using breadth-first traversal.
22 Args:
23 directory: Directory to search.
24 exclude_dirs: Set of directory names to exclude.
26 Returns:
27 List of Python file paths sorted by depth (shallower first).
28 """
29 from collections import deque
31 if exclude_dirs is None:
32 exclude_dirs = set()
34 python_files = []
35 # Use deque for breadth-first traversal
36 dirs_to_search = deque([(directory, 0)]) # (path, depth)
38 while dirs_to_search:
39 current_dir, depth = dirs_to_search.popleft()
41 try:
42 for entry in current_dir.iterdir():
43 if entry.is_file() and entry.suffix == '.py':
44 python_files.append((entry, depth))
45 elif entry.is_dir() and entry.name not in exclude_dirs:
46 # Add subdirectory to queue for later processing
47 dirs_to_search.append((entry, depth + 1))
48 except (PermissionError, OSError):
49 # Skip directories we can't read
50 continue
52 # Sort by depth first, then by path for consistent ordering
53 python_files.sort(key=lambda x: (x[1], str(x[0])))
55 # Return just the paths
56 return [file_path for file_path, _ in python_files]
59def validate_directory(directory: Path, exclude_dirs: Optional[Set[str]] = None) -> List[ValidationViolation]:
60 """
61 Validate all Python files in a directory.
63 Args:
64 directory: Directory to validate.
65 exclude_dirs: Set of directory names to exclude.
67 Returns:
68 List of validation violations.
69 """
70 python_files = find_python_files(directory, exclude_dirs)
71 violations = []
73 for file_path in python_files:
74 file_violations = validate_file(str(file_path))
75 violations.extend(file_violations)
77 return violations
80def main():
81 """Main entry point for the validation tool."""
82 parser = argparse.ArgumentParser(
83 description='AST-based validation for openhcs codebase'
84 )
85 parser.add_argument(
86 'paths',
87 nargs='+',
88 type=str,
89 help='Files or directories to validate'
90 )
91 parser.add_argument(
92 '--exclude',
93 nargs='+',
94 default=['__pycache__', '.git', 'venv', 'env', '.venv', '.env'],
95 help='Directories to exclude from validation'
96 )
97 parser.add_argument(
98 '--output',
99 type=str,
100 help='Output file for validation results (default: stdout)'
101 )
102 parser.add_argument(
103 '--fail-on-error',
104 action='store_true',
105 help='Exit with non-zero status if violations are found'
106 )
108 args = parser.parse_args()
110 # Convert paths to Path objects
111 paths = [Path(p) for p in args.paths]
112 exclude_dirs = set(args.exclude)
114 # Collect all violations
115 all_violations = []
117 for path in paths:
118 if path.is_file() and path.suffix == '.py':
119 file_violations = validate_file(str(path))
120 all_violations.extend(file_violations)
121 elif path.is_dir():
122 dir_violations = validate_directory(path, exclude_dirs)
123 all_violations.extend(dir_violations)
124 else:
125 print(f"Warning: {path} is not a Python file or directory", file=sys.stderr)
127 # Group violations by file
128 violations_by_file = {}
129 for violation in all_violations:
130 if violation.file_path not in violations_by_file:
131 violations_by_file[violation.file_path] = []
132 violations_by_file[violation.file_path].append(violation)
134 # Sort violations by file and line number
135 for file_path in violations_by_file:
136 violations_by_file[file_path].sort(key=lambda v: v.line_number)
138 # Output violations
139 output_file = open(args.output, 'w') if args.output else sys.stdout
141 try:
142 if all_violations:
143 print(f"Found {len(all_violations)} validation violations:", file=output_file)
145 for file_path, violations in sorted(violations_by_file.items()):
146 print(f"\n{file_path}:", file=output_file)
148 for violation in violations:
149 print(f" Line {violation.line_number}: {violation.violation_type} - {violation.message}",
150 file=output_file)
152 if args.fail_on_error:
153 sys.exit(1)
154 else:
155 print("No validation violations found.", file=output_file)
156 finally:
157 if args.output:
158 output_file.close()
161if __name__ == '__main__':
162 main()