259 lines
No EOL
10 KiB
Python
Executable file
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) |