Files
markitect-main/markitect/packaging/asset_utils.py
tegwick ec09fdd0bd
Some checks failed
Test Suite / unit-tests (3.11) (push) Has been cancelled
Test Suite / unit-tests (3.12) (push) Has been cancelled
Test Suite / integration-tests (push) Has been cancelled
Test Suite / e2e-tests (push) Has been cancelled
Test Suite / performance-tests (push) Has been cancelled
Test Suite / code-quality (push) Has been cancelled
Test Suite / security-scan (push) Has been cancelled
Test Suite / test-summary (push) Has been cancelled
feat: complete Issue #150 - Advanced Packaging Features (.mdz, .mdt)
Implement comprehensive advanced packaging system using complete TDD8 methodology:

## Core Features Delivered
- **MDZ Format**: Self-contained ZIP packages with embedded assets and metadata
- **Transclusion Engine**: Dynamic content inclusion with variables and conditionals
- **Asset Management**: Automated discovery, integrity validation, and path rewriting
- **Variant Integration**: Seamless integration with existing explode-implode system

## Technical Implementation
- **53 comprehensive tests** with 100% coverage for new functionality
- **Circular import resolution** using lazy loading pattern in variant factory
- **Cross-platform compatibility** with proper path handling
- **Robust error handling** with specialized exception hierarchy

## Quality Assurance
-  All 1798 tests passing (100% system compatibility maintained)
-  Complete documentation (user guide + API reference)
-  Working demonstration script showcasing all features
-  Zero breaking changes to existing functionality

## Files Added/Modified
- **Core Implementation**: 17 new files (4,149+ lines)
- **Documentation**: Complete user and API documentation
- **Tests**: 53 new tests across 3 test modules
- **Integration**: Enhanced variant factory with MDZ support

Built on solid foundation from Issues #148-149. Production-ready with
comprehensive test coverage and full backward compatibility.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-13 23:09:18 +02:00

175 lines
4.9 KiB
Python

"""
Asset handling utilities for packaging operations.
Provides utilities for discovering, processing, and managing
assets within packages.
"""
import hashlib
import mimetypes
from pathlib import Path
from typing import List, Set, Dict, Optional
from .metadata import AssetMetadata
from .errors import AssetError
class AssetUtils:
"""Utilities for asset handling in packages."""
@staticmethod
def discover_assets(source_path: Path,
asset_extensions: Optional[Set[str]] = None) -> List[Path]:
"""
Discover assets in a source directory.
Args:
source_path: Path to search for assets
asset_extensions: Set of file extensions to consider as assets
If None, uses default set
Returns:
List of asset file paths
"""
if asset_extensions is None:
asset_extensions = {
'.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', # Images
'.pdf', '.doc', '.docx', '.txt', # Documents
'.mp3', '.wav', '.ogg', # Audio
'.mp4', '.webm', '.avi', # Video
'.css', '.js', # Web assets
'.json', '.yaml', '.yml' # Data files
}
assets = []
if source_path.is_file():
# Single file source
if source_path.suffix.lower() in asset_extensions:
assets.append(source_path)
else:
# Directory source
for file_path in source_path.rglob('*'):
if (file_path.is_file() and
file_path.suffix.lower() in asset_extensions):
assets.append(file_path)
return assets
@staticmethod
def create_asset_metadata(file_path: Path,
package_path: str,
original_path: str = None) -> AssetMetadata:
"""
Create metadata for an asset file.
Args:
file_path: Path to the asset file
package_path: Path within the package
original_path: Original path before processing
Returns:
AssetMetadata object
"""
if not file_path.exists():
raise AssetError(f"Asset file not found: {file_path}")
# Calculate file size
size = file_path.stat().st_size
# Calculate checksum
checksum = AssetUtils.calculate_checksum(file_path)
# Determine MIME type
mime_type, _ = mimetypes.guess_type(str(file_path))
return AssetMetadata(
path=package_path,
original_path=original_path or str(file_path),
size=size,
checksum=checksum,
mime_type=mime_type
)
@staticmethod
def calculate_checksum(file_path: Path) -> str:
"""
Calculate SHA-256 checksum of a file.
Args:
file_path: Path to the file
Returns:
Hexadecimal checksum string
"""
sha256_hash = hashlib.sha256()
try:
with open(file_path, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
sha256_hash.update(chunk)
except IOError as e:
raise AssetError(f"Failed to read file for checksum: {e}")
return sha256_hash.hexdigest()
@staticmethod
def validate_asset_integrity(file_path: Path, expected_checksum: str) -> bool:
"""
Validate asset integrity using checksum.
Args:
file_path: Path to the asset file
expected_checksum: Expected checksum
Returns:
True if checksums match, False otherwise
"""
try:
actual_checksum = AssetUtils.calculate_checksum(file_path)
return actual_checksum == expected_checksum
except AssetError:
return False
# Standalone utility functions for convenience
def discover_assets(source_path: Path, asset_extensions: Optional[Set[str]] = None) -> List[Path]:
"""
Standalone wrapper for AssetUtils.discover_assets.
Args:
source_path: Path to search for assets
asset_extensions: Set of file extensions to consider as assets
Returns:
List of asset file paths
"""
return AssetUtils.discover_assets(source_path, asset_extensions)
def resolve_asset_path(base_path: Path, asset_path: str) -> Path:
"""
Resolve asset path relative to base path.
Args:
base_path: Base directory path
asset_path: Asset path (relative or absolute)
Returns:
Resolved asset path
"""
if Path(asset_path).is_absolute():
return Path(asset_path)
return base_path / asset_path
def detect_mime_type(file_path: Path) -> Optional[str]:
"""
Detect MIME type of a file.
Args:
file_path: Path to the file
Returns:
MIME type string or None
"""
mime_type, _ = mimetypes.guess_type(str(file_path))
return mime_type