Files
Felix Zösch 9be64a0696 Add homeassistant skill in unpacked format
- Add complete homeassistant skill source to skills/ directory
- Includes all scripts, references, and automation templates
- Matches format of other skills in repository
2025-12-16 12:49:56 +01:00

187 lines
6.5 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Home Assistant YAML Configuration Validator
This script validates Home Assistant YAML configuration files for syntax errors
and common issues.
Usage:
# Validate a single file
python3 validate_yaml.py configuration.yaml
# Validate automation configuration
python3 validate_yaml.py automations.yaml --type automation
# Validate with verbose output
python3 validate_yaml.py configuration.yaml --verbose
"""
import argparse
import sys
import yaml
from pathlib import Path
from typing import Any, Dict, List, Optional
class HomeAssistantYAMLValidator:
"""Validator for Home Assistant YAML files."""
# Common required fields for different config types
AUTOMATION_REQUIRED = ['trigger']
AUTOMATION_OPTIONAL = ['alias', 'id', 'description', 'condition', 'action', 'mode', 'variables']
def __init__(self, verbose: bool = False):
self.verbose = verbose
self.errors: List[str] = []
self.warnings: List[str] = []
def validate_file(self, filepath: Path) -> bool:
"""Validate a YAML file."""
self.errors = []
self.warnings = []
if not filepath.exists():
self.errors.append(f"File not found: {filepath}")
return False
try:
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
# Check for tabs (Home Assistant doesn't allow tabs)
if '\t' in content:
self.errors.append("File contains tab characters. Use spaces for indentation.")
return False
# Parse YAML
data = yaml.safe_load(content)
if self.verbose:
print(f"✓ YAML syntax is valid")
return True
except yaml.YAMLError as e:
self.errors.append(f"YAML syntax error: {str(e)}")
return False
except Exception as e:
self.errors.append(f"Error reading file: {str(e)}")
return False
def validate_automation(self, filepath: Path) -> bool:
"""Validate an automation configuration file."""
if not self.validate_file(filepath):
return False
try:
with open(filepath, 'r', encoding='utf-8') as f:
data = yaml.safe_load(f)
if not isinstance(data, list):
# Single automation wrapped in automation: key
if isinstance(data, dict):
if 'automation' in data:
automations = data['automation']
if not isinstance(automations, list):
automations = [automations]
else:
automations = [data]
else:
self.errors.append("Automation file must contain a list or dict")
return False
else:
automations = data
# Validate each automation
for i, automation in enumerate(automations):
if not isinstance(automation, dict):
self.errors.append(f"Automation {i+1} is not a dictionary")
continue
# Check required fields
if 'trigger' not in automation:
self.errors.append(f"Automation {i+1}: Missing required field 'trigger'")
# Check for common issues
if 'alias' not in automation:
self.warnings.append(f"Automation {i+1}: No 'alias' field (recommended for readability)")
if 'id' not in automation:
self.warnings.append(f"Automation {i+1}: No 'id' field (required for UI editor)")
# Validate trigger structure
if 'trigger' in automation:
triggers = automation['trigger']
if not isinstance(triggers, list):
triggers = [triggers]
for j, trigger in enumerate(triggers):
if not isinstance(trigger, dict):
self.errors.append(f"Automation {i+1}, Trigger {j+1}: Must be a dictionary")
continue
if 'trigger' not in trigger and 'platform' not in trigger:
self.errors.append(f"Automation {i+1}, Trigger {j+1}: Missing 'trigger' or 'platform' field")
# Validate action structure if present
if 'action' in automation:
actions = automation['action']
if not isinstance(actions, list):
actions = [actions]
for j, action in enumerate(actions):
if not isinstance(action, dict):
self.errors.append(f"Automation {i+1}, Action {j+1}: Must be a dictionary")
if self.verbose and not self.errors:
print(f"✓ Validated {len(automations)} automation(s)")
return len(self.errors) == 0
except Exception as e:
self.errors.append(f"Error validating automation: {str(e)}")
return False
def print_results(self):
"""Print validation results."""
if self.errors:
print("\n❌ Errors found:")
for error in self.errors:
print(f"{error}")
if self.warnings:
print("\n⚠️ Warnings:")
for warning in self.warnings:
print(f"{warning}")
if not self.errors and not self.warnings:
print("✅ Validation passed with no errors or warnings")
elif not self.errors:
print("\n✅ Validation passed (with warnings)")
def main():
parser = argparse.ArgumentParser(description='Validate Home Assistant YAML files')
parser.add_argument('file', help='YAML file to validate')
parser.add_argument('--type', choices=['automation', 'general'],
default='general', help='Type of configuration to validate')
parser.add_argument('--verbose', '-v', action='store_true',
help='Verbose output')
args = parser.parse_args()
filepath = Path(args.file)
validator = HomeAssistantYAMLValidator(verbose=args.verbose)
if args.type == 'automation':
success = validator.validate_automation(filepath)
else:
success = validator.validate_file(filepath)
validator.print_results()
sys.exit(0 if success else 1)
if __name__ == '__main__':
main()