#!/usr/bin/env python3 """ Version synchronization script for Text Texture Generator. Updates version information from git tags across all template files. """ import os import sys import json import re import subprocess from pathlib import Path from typing import Dict, Tuple, Optional def get_project_root() -> Path: """Get the project root directory.""" return Path(__file__).parent.parent def load_config() -> Dict: """Load build configuration.""" config_path = get_project_root() / "build" / "config.json" with open(config_path, 'r') as f: return json.load(f) def get_latest_git_tag() -> Optional[str]: """Get the latest git tag that matches the version pattern.""" try: # Get all tags sorted by version result = subprocess.run(['git', 'tag', '-l', '--sort=-version:refname'], capture_output=True, text=True, check=True) tags = result.stdout.strip().split('\n') config = load_config() pattern = config['versions']['git_tag_pattern'] version_regex = re.compile(pattern) for tag in tags: if version_regex.match(tag): # Extract version number, removing 'v' prefix if present match = version_regex.match(tag) return match.group(1) if match else None return None except subprocess.CalledProcessError: print("Warning: Could not get git tags. Using fallback version.") return None def get_version_info(version_string: str) -> Tuple[str, int, int, int]: """Parse version string into components.""" parts = version_string.split('.') if len(parts) != 3: raise ValueError(f"Invalid version format: {version_string}") major, minor, patch = map(int, parts) return version_string, major, minor, patch def create_template_variables(config: Dict, version: str, version_type: str = "full") -> Dict[str, str]: """Create template variables for file generation.""" version_str, major, minor, patch = get_version_info(version) # Load exclusions configuration for version limits exclusions_path = get_project_root() / "build" / "file_exclusions.json" with open(exclusions_path, 'r') as f: exclusions_config = json.load(f) # Get version-specific configuration version_config = exclusions_config['version_configs'][version_type] # Define features based on version type if version_type == "free": enabled_features = ["basic", "preview", "image_overlays", "basic_utilities"] else: # full version enabled_features = ["basic", "preview", "image_overlays", "basic_utilities", "advanced_styling", "normal_maps", "presets", "batch_processing", "advanced_utilities"] # Parse Blender version blender_version = config['project']['blender_version_min'] blender_parts = blender_version.split('.') blender_major, blender_minor = int(blender_parts[0]), int(blender_parts[1]) blender_patch = int(blender_parts[2]) if len(blender_parts) > 2 else 0 # Format dependencies for TOML deps = [] for dep, version_spec in config['dependencies'].items(): deps.append(f'{dep} = "{version_spec}"') dependencies_str = '\n'.join(deps) variables = { 'PROJECT_ID': config['project']['id'], 'PROJECT_NAME': config['project']['name'], 'PROJECT_AUTHOR': config['project']['author'], 'PROJECT_DESCRIPTION': config['project']['description'], 'PROJECT_CATEGORY': config['project']['category'], 'PROJECT_LOCATION': config['project']['location'], 'PROJECT_SUPPORT': config['project']['support'], 'PROJECT_TAGS': json.dumps(config['project']['tags']), 'PROJECT_LICENSE': json.dumps(config['project']['license']), 'VERSION': version_str, 'VERSION_MAJOR': str(major), 'VERSION_MINOR': str(minor), 'VERSION_PATCH': str(patch), 'VERSION_TYPE': version_type, 'BLENDER_VERSION_MIN': blender_version, 'BLENDER_VERSION_MAJOR': str(blender_major), 'BLENDER_VERSION_MINOR': str(blender_minor), 'BLENDER_VERSION_PATCH': str(blender_patch), 'DEPENDENCIES': dependencies_str, 'ENABLED_FEATURES': json.dumps(enabled_features), 'MAX_TEXTURE_SIZE': str(version_config['max_texture_size']), 'MAX_CONCURRENT_OPERATIONS': str(version_config['max_concurrent_operations']) } return variables def process_template(template_path: Path, output_path: Path, variables: Dict[str, str]): """Process a template file and write the output.""" print(f"Processing template: {template_path} -> {output_path}") # Read template with open(template_path, 'r') as f: content = f.read() # Replace variables for key, value in variables.items(): content = content.replace(f'{{{{{key}}}}}', value) # Ensure output directory exists output_path.parent.mkdir(parents=True, exist_ok=True) # Write output with open(output_path, 'w') as f: f.write(content) print(f"Generated: {output_path}") def sync_version(version: Optional[str] = None, version_type: str = "full"): """Synchronize version across all template files.""" config = load_config() # Get version from git tag if not provided if version is None: version = get_latest_git_tag() if version is None: print("Warning: No valid git tag found. Using version from current blender_manifest.toml") # Fallback to current version manifest_path = get_project_root() / "src" / "blender_manifest.toml" if manifest_path.exists(): with open(manifest_path, 'r') as f: for line in f: if line.startswith('version = '): version = line.split('=')[1].strip().strip('"') break if version is None: version = "1.0.0" # Ultimate fallback print(f"Synchronizing version: {version} (type: {version_type})") # Create template variables variables = create_template_variables(config, version, version_type) # Process each template file templates_dir = get_project_root() / "build" / "templates" for template_file in templates_dir.glob("*.template"): # Determine output path relative_name = template_file.stem # Remove .template extension if relative_name == "blender_manifest.toml": output_path = get_project_root() / "src" / relative_name elif relative_name == "constants.py": output_path = get_project_root() / "src" / "utils" / relative_name else: # For other templates, maintain directory structure output_path = get_project_root() / "src" / relative_name process_template(template_file, output_path, variables) print(f"Version synchronization complete: {version}") if __name__ == "__main__": import argparse parser = argparse.ArgumentParser(description="Synchronize version information") parser.add_argument("--version", help="Version to set (defaults to latest git tag)") parser.add_argument("--type", choices=["free", "full"], default="full", help="Version type (free or full)") args = parser.parse_args() try: sync_version(args.version, args.type) except Exception as e: print(f"Error: {e}") sys.exit(1)