Coverage for src / rhiza / language_validators.py: 100%
76 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-02 07:04 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-02 07:04 +0000
1"""Language-specific validation for Rhiza projects.
3This module provides a framework for validating project structure based on
4the programming language. Different languages have different requirements
5(e.g., Python needs pyproject.toml, Go needs go.mod).
6"""
8from abc import ABC, abstractmethod
9from pathlib import Path
11from loguru import logger
14class LanguageValidator(ABC):
15 """Abstract base class for language-specific validators."""
17 @abstractmethod
18 def validate_project_structure(self, target: Path) -> bool:
19 """Validate language-specific project structure.
21 Args:
22 target: Path to the project root.
24 Returns:
25 True if validation passes, False otherwise.
26 """
27 ...
29 @abstractmethod
30 def get_language_name(self) -> str:
31 """Get the name of the language this validator handles.
33 Returns:
34 Language name (e.g., "python", "go").
35 """
36 ...
39class PythonValidator(LanguageValidator):
40 """Validator for Python projects."""
42 def get_language_name(self) -> str:
43 """Get the language name."""
44 return "python"
46 def validate_project_structure(self, target: Path) -> bool:
47 """Validate Python project structure.
49 Checks for:
50 - pyproject.toml (required)
51 - src directory (warning if missing)
52 - tests directory (warning if missing)
54 Args:
55 target: Path to the project root.
57 Returns:
58 True if validation passes, False otherwise.
59 """
60 validation_passed = True
62 # Check for pyproject.toml (required)
63 pyproject_file = target / "pyproject.toml"
64 if not pyproject_file.exists():
65 logger.error(f"pyproject.toml not found: {pyproject_file}")
66 logger.error("pyproject.toml is required for Python projects")
67 logger.info("Run 'rhiza init' to create a default pyproject.toml")
68 validation_passed = False
69 else:
70 logger.success(f"pyproject.toml exists: {pyproject_file}")
72 # Check for standard directories (warnings only)
73 src_dir = target / "src"
74 tests_dir = target / "tests"
76 if not src_dir.exists():
77 logger.warning(f"Standard 'src' folder not found: {src_dir}")
78 logger.warning("Consider creating a 'src' directory for source code")
79 else:
80 logger.success(f"'src' folder exists: {src_dir}")
82 if not tests_dir.exists():
83 logger.warning(f"Standard 'tests' folder not found: {tests_dir}")
84 logger.warning("Consider creating a 'tests' directory for test files")
85 else:
86 logger.success(f"'tests' folder exists: {tests_dir}")
88 return validation_passed
91class GoValidator(LanguageValidator):
92 """Validator for Go projects."""
94 def get_language_name(self) -> str:
95 """Get the language name."""
96 return "go"
98 def validate_project_structure(self, target: Path) -> bool:
99 """Validate Go project structure.
101 Checks for:
102 - go.mod (required)
103 - cmd directory (warning if missing)
104 - pkg or internal directory (warning if missing)
106 Args:
107 target: Path to the project root.
109 Returns:
110 True if validation passes, False otherwise.
111 """
112 validation_passed = True
114 # Check for go.mod (required)
115 go_mod_file = target / "go.mod"
116 if not go_mod_file.exists():
117 logger.error(f"go.mod not found: {go_mod_file}")
118 logger.error("go.mod is required for Go projects")
119 logger.info("Run 'go mod init <module-name>' to create go.mod")
120 validation_passed = False
121 else:
122 logger.success(f"go.mod exists: {go_mod_file}")
124 # Check for standard directories (warnings only)
125 cmd_dir = target / "cmd"
126 pkg_dir = target / "pkg"
127 internal_dir = target / "internal"
129 if not cmd_dir.exists():
130 logger.warning(f"Standard 'cmd' folder not found: {cmd_dir}")
131 logger.warning("Consider creating a 'cmd' directory for main applications")
132 else:
133 logger.success(f"'cmd' folder exists: {cmd_dir}")
135 if not pkg_dir.exists() and not internal_dir.exists():
136 logger.warning("Neither 'pkg' nor 'internal' folder found")
137 logger.warning("Consider creating 'pkg' for public libraries or 'internal' for private packages")
138 else:
139 if pkg_dir.exists():
140 logger.success(f"'pkg' folder exists: {pkg_dir}")
141 if internal_dir.exists():
142 logger.success(f"'internal' folder exists: {internal_dir}")
144 return validation_passed
147class LanguageValidatorRegistry:
148 """Registry for language validators."""
150 def __init__(self):
151 """Initialize the registry with default validators."""
152 self._validators: dict[str, LanguageValidator] = {}
153 self._register_defaults()
155 def _register_defaults(self) -> None:
156 """Register default language validators."""
157 self.register(PythonValidator())
158 self.register(GoValidator())
160 def register(self, validator: LanguageValidator) -> None:
161 """Register a language validator.
163 Args:
164 validator: The validator to register.
165 """
166 language_name = validator.get_language_name()
167 self._validators[language_name] = validator
168 logger.debug(f"Registered validator for language: {language_name}")
170 def get_validator(self, language: str) -> LanguageValidator | None:
171 """Get a validator for the specified language.
173 Args:
174 language: The language name (e.g., "python", "go").
176 Returns:
177 The validator for the language, or None if not found.
178 """
179 return self._validators.get(language.lower())
181 def get_supported_languages(self) -> list[str]:
182 """Get list of supported languages.
184 Returns:
185 List of supported language names.
186 """
187 return list(self._validators.keys())
190# Global registry instance
191_registry = LanguageValidatorRegistry()
194def get_validator_registry() -> LanguageValidatorRegistry:
195 """Get the global validator registry.
197 Returns:
198 The global validator registry instance.
199 """
200 return _registry