You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
116 lines
4.1 KiB
116 lines
4.1 KiB
#!/usr/bin/env python3
|
|
"""
|
|
Dynamic poster brief validator.
|
|
Validates the format and content of a poster brief file.
|
|
|
|
Usage:
|
|
python3 validate_poster_brief.py <brief_path>
|
|
"""
|
|
|
|
import argparse
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
def validate_brief(brief_path: str) -> tuple[bool, list[str]]:
|
|
"""Validate a dynamic poster brief file. Returns (passed, errors)."""
|
|
errors = []
|
|
warnings = []
|
|
path = Path(brief_path)
|
|
|
|
if not path.exists():
|
|
return False, [f"File not found: {brief_path}"]
|
|
|
|
content = path.read_text(encoding="utf-8")
|
|
|
|
# --- Required fields ---
|
|
required_fields = {
|
|
"Brand": r"Brand\s*[::]\s*\S+",
|
|
"Product Category": r"Product Category\s*[::]\s*\S+",
|
|
"Brand Colors": r"Brand Colors\s*[::]\s*\S+",
|
|
"Product Texture": r"Product Texture\s*[::]\s*\S+",
|
|
"Key Visual Elements": r"Key Visual Elements\s*[::]\s*\S+",
|
|
}
|
|
|
|
for field, pattern in required_fields.items():
|
|
if not re.search(pattern, content):
|
|
errors.append(f"Missing or empty required field: {field}")
|
|
|
|
# --- Brand Colors validation ---
|
|
color_match = re.search(r"Brand Colors\s*[::]\s*(.+)", content)
|
|
if color_match:
|
|
color_line = color_match.group(1)
|
|
hex_colors = re.findall(r"#[0-9A-Fa-f]{6}", color_line)
|
|
if not hex_colors:
|
|
errors.append(
|
|
"Brand Colors must include at least one hex color value (e.g., #2D5A3D)"
|
|
)
|
|
|
|
# --- Aspect Ratio validation ---
|
|
ratio_match = re.search(r"Aspect Ratio\s*[::]\s*(.+)", content)
|
|
if ratio_match:
|
|
ratio = ratio_match.group(1).strip()
|
|
valid_ratios = {"9:16", "16:9", "4:5", "1:1"}
|
|
if ratio not in valid_ratios:
|
|
warnings.append(
|
|
f"Aspect Ratio '{ratio}' not standard. Expected: {', '.join(sorted(valid_ratios))}"
|
|
)
|
|
|
|
# --- Logo fields (optional but validate if present) ---
|
|
logo_match = re.search(r"Logo Type\s*[::]\s*(.+)", content)
|
|
if logo_match:
|
|
logo_type = logo_match.group(1).strip().lower()
|
|
valid_logo_types = {"wordmark", "icon", "combo", "none"}
|
|
if not any(lt in logo_type for lt in valid_logo_types):
|
|
warnings.append(
|
|
f"Logo Type '{logo_match.group(1).strip()}' not recognized. "
|
|
f"Expected: {', '.join(sorted(valid_logo_types))}"
|
|
)
|
|
|
|
# --- Creative Concept validation (if present in same file) ---
|
|
has_concept = "# Creative Concept" in content or "# Selected Concept" in content
|
|
if has_concept:
|
|
if not re.search(r"Creative Dimension\s*[::]\s*\S+", content):
|
|
errors.append("Creative Concept found but missing 'Creative Dimension'")
|
|
if not re.search(r"The Impossible Thing\s*[::]\s*\S+", content):
|
|
errors.append("Creative Concept found but missing 'The Impossible Thing'")
|
|
if not re.search(r"Conflict", content):
|
|
warnings.append("Creative Concept missing conflict arc (Setup → Escalation → Payoff)")
|
|
|
|
passed = len(errors) == 0
|
|
return passed, errors + [f"WARNING: {w}" for w in warnings]
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Validate dynamic poster brief format")
|
|
parser.add_argument("brief_path", help="Path to the brief.md file")
|
|
args = parser.parse_args()
|
|
|
|
passed, messages = validate_brief(args.brief_path)
|
|
|
|
errors = [m for m in messages if not m.startswith("WARNING:")]
|
|
warnings = [m for m in messages if m.startswith("WARNING:")]
|
|
|
|
if passed:
|
|
print("PASSED: Brief validation successful")
|
|
print(f" File: {args.brief_path}")
|
|
if warnings:
|
|
print(f" Warnings: {len(warnings)}")
|
|
for w in warnings:
|
|
print(f" - {w}")
|
|
sys.exit(0)
|
|
else:
|
|
print(f"FAILED: Brief validation found {len(errors)} error(s)")
|
|
print(f" File: {args.brief_path}")
|
|
for i, err in enumerate(errors, 1):
|
|
print(f" {i}. {err}")
|
|
if warnings:
|
|
print(f" Warnings:")
|
|
for w in warnings:
|
|
print(f" - {w}")
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
|