Panel/tools/validate_guides.py

259 lines
No EOL
10 KiB
Python
Executable file

#!/usr/bin/env python3
"""
Server Admin Guide Validator
Validates generated server admin guides against quality gates and requirements.
Ensures guides meet the "exhaustive" standard specified in the requirements.
"""
import os
import sys
import yaml
import json
from pathlib import Path
import re
class GuideValidator:
def __init__(self, docs_dir="docs/games", pdfs_dir="dist/pdfs", data_dir="data/games"):
self.docs_dir = Path(docs_dir)
self.pdfs_dir = Path(pdfs_dir)
self.data_dir = Path(data_dir)
self.errors = []
self.warnings = []
def validate_all(self):
"""Run all validation checks"""
print("=== Server Admin Guide Validation ===\n")
self.validate_directory_structure()
self.validate_yaml_data()
self.validate_markdown_guides()
self.validate_pdf_files()
self.validate_manifest()
self.validate_index_page()
return self.print_results()
def validate_directory_structure(self):
"""Validate required directories exist"""
print("Checking directory structure...")
required_dirs = [self.docs_dir, self.pdfs_dir, self.data_dir]
for directory in required_dirs:
if not directory.exists():
self.errors.append(f"Required directory missing: {directory}")
required_files = [
self.docs_dir / "_index.md",
self.pdfs_dir / "manifest.json"
]
for file_path in required_files:
if not file_path.exists():
self.errors.append(f"Required file missing: {file_path}")
print("✓ Directory structure validated\n")
def validate_yaml_data(self):
"""Validate YAML game data meets exhaustive requirements"""
print("Checking YAML game data...")
yaml_files = list(self.data_dir.glob("*.yml")) + list(self.data_dir.glob("*.yaml"))
for yaml_file in yaml_files:
try:
with open(yaml_file, 'r', encoding='utf-8') as f:
game_data = yaml.safe_load(f)
self.validate_single_game_yaml(game_data, yaml_file)
except Exception as e:
self.errors.append(f"Error reading {yaml_file}: {e}")
print("✓ YAML data validated\n")
def validate_single_game_yaml(self, game_data, filename):
"""Validate individual game YAML meets requirements"""
game_name = game_data.get('name', 'Unknown')
# Check required sections
required_sections = ['name', 'supports_workshop', 'startup', 'configs', 'troubleshooting']
for section in required_sections:
if section not in game_data:
self.errors.append(f"{filename}: Missing required section '{section}'")
# Validate startup parameters (minimum 10 flags)
flags = game_data.get('startup', {}).get('flags', [])
if len(flags) < 10:
self.warnings.append(f"{game_name}: Only {len(flags)} startup flags (minimum 10 recommended)")
# Validate config files (minimum 8 entries)
configs = game_data.get('configs', [])
if len(configs) < 8:
self.warnings.append(f"{game_name}: Only {len(configs)} config entries (minimum 8 recommended)")
# Validate port mapping
ports = game_data.get('startup', {}).get('ports', [])
if not ports:
self.errors.append(f"{game_name}: No port mapping defined")
else:
for port in ports:
required_port_fields = ['label', 'port', 'proto', 'relative']
for field in required_port_fields:
if field not in port:
self.errors.append(f"{game_name}: Port entry missing '{field}' field")
def validate_markdown_guides(self):
"""Validate generated Markdown guides"""
print("Checking Markdown guides...")
required_sections = [
"Quick Start",
"Full Port Map",
"Startup Parameters \\(EXHAUSTIVE\\)",
"Configuration Files & Paths \\(ALL\\)",
"Steam Workshop",
"Player & Server Management",
"Troubleshooting \\(Deep\\)",
"Appendices"
]
game_dirs = [d for d in self.docs_dir.iterdir() if d.is_dir()]
for game_dir in game_dirs:
md_file = game_dir / "index.md"
if not md_file.exists():
self.errors.append(f"Missing Markdown file: {md_file}")
continue
with open(md_file, 'r', encoding='utf-8') as f:
content = f.read()
# Check for all required sections
for section in required_sections:
pattern = f"## {section}"
if not re.search(pattern, content):
self.errors.append(f"{md_file}: Missing required section '{section}'")
# Check for startup parameters table
if "| Flag/Param | Default | Type/Range | Description | Example |" not in content:
self.errors.append(f"{md_file}: Missing startup parameters table")
# Check for port mapping table
if "| Feature | Port | Protocol | Relation | Notes |" not in content:
self.errors.append(f"{md_file}: Missing port mapping table")
# Check for no TBD or placeholder content
placeholders = ["TBD", "TODO", "coming soon", "placeholder"]
for placeholder in placeholders:
if placeholder.lower() in content.lower():
self.warnings.append(f"{md_file}: Contains placeholder text '{placeholder}'")
print("✓ Markdown guides validated\n")
def validate_pdf_files(self):
"""Validate PDF files exist and have reasonable size"""
print("Checking PDF files...")
game_dirs = [d for d in self.docs_dir.iterdir() if d.is_dir()]
for game_dir in game_dirs:
slug = game_dir.name
pdf_file = self.pdfs_dir / f"{slug}__Server_Admin_Guide_v1.pdf"
if not pdf_file.exists():
self.errors.append(f"Missing PDF file: {pdf_file}")
continue
# Check file size (should be at least 20KB for a comprehensive guide)
file_size = pdf_file.stat().st_size
if file_size < 20480: # 20KB
self.warnings.append(f"{pdf_file}: Small file size ({file_size} bytes) - may indicate incomplete content")
print("✓ PDF files validated\n")
def validate_manifest(self):
"""Validate manifest.json structure and content"""
print("Checking manifest...")
manifest_file = self.pdfs_dir / "manifest.json"
if not manifest_file.exists():
self.errors.append("Missing manifest.json file")
return
try:
with open(manifest_file, 'r', encoding='utf-8') as f:
manifest = json.load(f)
# Check required fields
required_fields = ["generated", "total_games", "games"]
for field in required_fields:
if field not in manifest:
self.errors.append(f"Manifest missing required field: {field}")
# Validate games entries
if "games" in manifest:
for game in manifest["games"]:
required_game_fields = ["title", "slug", "appid", "engine", "workshop_support", "ports", "config_files", "last_updated", "markdown_path", "pdf_path"]
for field in required_game_fields:
if field not in game:
self.errors.append(f"Manifest game entry missing field: {field}")
except json.JSONDecodeError as e:
self.errors.append(f"Invalid JSON in manifest: {e}")
print("✓ Manifest validated\n")
def validate_index_page(self):
"""Validate index page content"""
print("Checking index page...")
index_file = self.docs_dir / "_index.md"
if not index_file.exists():
self.errors.append("Missing index page")
return
with open(index_file, 'r', encoding='utf-8') as f:
content = f.read()
# Check for required sections
required_content = [
"# Game Server Admin Guides",
"## Available Guides",
"| Game | Engine | Workshop | AppID | Documentation | PDF Guide |",
"## Statistics"
]
for required in required_content:
if required not in content:
self.errors.append(f"Index page missing required content: {required}")
print("✓ Index page validated\n")
def print_results(self):
"""Print validation results"""
print("=== Validation Results ===\n")
if self.errors:
print(f"❌ ERRORS ({len(self.errors)}):")
for error in self.errors:
print(f"{error}")
print()
if self.warnings:
print(f"⚠️ WARNINGS ({len(self.warnings)}):")
for warning in self.warnings:
print(f"{warning}")
print()
if not self.errors and not self.warnings:
print("✅ All validation checks passed!")
elif not self.errors:
print("✅ No critical errors found (warnings can be addressed)")
else:
print(f"❌ Validation failed with {len(self.errors)} errors")
return len(self.errors) == 0
if __name__ == "__main__":
validator = GuideValidator()
success = validator.validate_all()
sys.exit(0 if success else 1)