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
This commit is contained in:
186
skills/homeassistant/scripts/validate_yaml.py
Executable file
186
skills/homeassistant/scripts/validate_yaml.py
Executable file
@@ -0,0 +1,186 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user