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

1#!/usr/bin/env python 

2""" 

3Command-line tool for running AST-based validation on openhcs codebase. 

4 

5This tool validates Python files for compliance with openhcs's architectural 

6principles using static AST-based analysis. 

7""" 

8 

9import argparse 

10import sys 

11from pathlib import Path 

12from typing import List, Optional, Set 

13 

14from openhcs.validation.ast_validator import ValidationViolation, validate_file 

15 

16 

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. 

20 

21 Args: 

22 directory: Directory to search. 

23 exclude_dirs: Set of directory names to exclude. 

24 

25 Returns: 

26 List of Python file paths sorted by depth (shallower first). 

27 """ 

28 from collections import deque 

29 

30 if exclude_dirs is None: 

31 exclude_dirs = set() 

32 

33 python_files = [] 

34 # Use deque for breadth-first traversal 

35 dirs_to_search = deque([(directory, 0)]) # (path, depth) 

36 

37 while dirs_to_search: 

38 current_dir, depth = dirs_to_search.popleft() 

39 

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 

50 

51 # Sort by depth first, then by path for consistent ordering 

52 python_files.sort(key=lambda x: (x[1], str(x[0]))) 

53 

54 # Return just the paths 

55 return [file_path for file_path, _ in python_files] 

56 

57 

58def validate_directory(directory: Path, exclude_dirs: Optional[Set[str]] = None) -> List[ValidationViolation]: 

59 """ 

60 Validate all Python files in a directory. 

61  

62 Args: 

63 directory: Directory to validate. 

64 exclude_dirs: Set of directory names to exclude. 

65  

66 Returns: 

67 List of validation violations. 

68 """ 

69 python_files = find_python_files(directory, exclude_dirs) 

70 violations = [] 

71 

72 for file_path in python_files: 

73 file_violations = validate_file(str(file_path)) 

74 violations.extend(file_violations) 

75 

76 return violations 

77 

78 

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 ) 

106 

107 args = parser.parse_args() 

108 

109 # Convert paths to Path objects 

110 paths = [Path(p) for p in args.paths] 

111 exclude_dirs = set(args.exclude) 

112 

113 # Collect all violations 

114 all_violations = [] 

115 

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) 

125 

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) 

132 

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) 

136 

137 # Output violations 

138 output_file = open(args.output, 'w') if args.output else sys.stdout 

139 

140 try: 

141 if all_violations: 

142 print(f"Found {len(all_violations)} validation violations:", file=output_file) 

143 

144 for file_path, violations in sorted(violations_by_file.items()): 

145 print(f"\n{file_path}:", file=output_file) 

146 

147 for violation in violations: 

148 print(f" Line {violation.line_number}: {violation.violation_type} - {violation.message}", 

149 file=output_file) 

150 

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() 

158 

159 

160if __name__ == '__main__': 

161 main()