feat: implement comprehensive asset shipping for md-render command
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 / code-quality (push) Has been cancelled
Test Suite / security-scan (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 / test-summary (push) Has been cancelled
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 / code-quality (push) Has been cancelled
Test Suite / security-scan (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 / test-summary (push) Has been cancelled
Add automatic asset copying when rendering markdown to different output directories with intelligent defaults and full user control. Key Features: - Environment variable support: MARKITECT_OUTPUT_DIR sets default output directory - Smart defaults: auto-ship assets for directory output, disabled for file output - CLI control flags: --ship-assets and --no-ship-assets for explicit control - Timestamp-based copying: only copies when source newer than destination - Path preservation: maintains relative directory structure in output - Graceful error handling: missing assets logged as warnings, not failures Technical Implementation: - Enhanced asset discovery in markitect/assets/discovery.py with discover_assets_from_markdown() - Added environment variable priority: CLI --output > MARKITECT_OUTPUT_DIR > input directory - Comprehensive asset shipping logic with _ship_assets() function - Directory vs file output detection for intelligent default behavior Examples and Testing: - Added image-assets example directory with 6 sample images and comprehensive README - Created comprehensive TDD test suite with 10 tests covering all functionality - Tests validate environment variables, CLI flags, asset discovery, shipping logic, timestamp handling, missing assets, path preservation, and default behaviors Usage: markitect md-render file.md -o /output/dir/ # Auto-ships assets markitect md-render file.md --no-ship-assets # Suppresses shipping MARKITECT_OUTPUT_DIR=/docs markitect md-render file.md # Uses env var 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
19
TODO.md
19
TODO.md
@@ -29,6 +29,25 @@ This section is for tasks currently being discussed with or worked on by the cod
|
||||
|
||||
## Completed Tasks
|
||||
|
||||
**Asset Shipping for md-render - COMPLETED ✅**:
|
||||
- ✅ Implemented automatic asset copying when rendering markdown to different output directories
|
||||
- ✅ Added asset discovery functionality parsing markdown for image/link references
|
||||
- ✅ Implemented timestamp-based asset copying (only copy if source newer than destination)
|
||||
- ✅ Added `--ship-assets` and `--no-ship-assets` CLI flags for explicit control
|
||||
- ✅ Added `MARKITECT_OUTPUT_DIR` environment variable support for default output directory
|
||||
- ✅ Smart defaults: assets ship automatically when output is directory, disabled for specific files
|
||||
- ✅ Preserved relative path structure in output directory maintaining markdown link compatibility
|
||||
- ✅ Graceful handling of missing assets with warning messages
|
||||
- ✅ Full backward compatibility with existing md-render workflows
|
||||
- ✅ Comprehensive TDD test suite covering all functionality and edge cases
|
||||
|
||||
**Feature Capabilities**:
|
||||
- Environment variable priority: CLI `--output` > `MARKITECT_OUTPUT_DIR` > input file directory
|
||||
- Automatic asset discovery from standard markdown syntax: `` and `[text](path)`
|
||||
- Timestamp-based incremental copying prevents unnecessary file operations
|
||||
- Directory structure preservation maintains working relative links in output HTML
|
||||
- Support for images, documents, and other asset types referenced in markdown
|
||||
|
||||
**CHANGELOG.md Enhancement - COMPLETED ✅**:
|
||||
- ✅ Added missing version entries for 0.1.0, 0.2.0, and 0.3.0
|
||||
- ✅ Added standard Keep a Changelog header with proper format
|
||||
|
||||
16
examples/image-assets/README.txt
Normal file
16
examples/image-assets/README.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
Image Asset Management Examples
|
||||
|
||||
This directory contains examples demonstrating MarkiTect's image asset management
|
||||
capabilities:
|
||||
|
||||
- project_documentation.md: Sample project documentation with embedded images
|
||||
showing how MarkiTect handles image assets in markdown documents
|
||||
- images/: Directory containing sample images used in the documentation examples
|
||||
|
||||
These examples showcase:
|
||||
- Image embedding in markdown documents
|
||||
- Asset deduplication and content-addressable storage
|
||||
- Relative path handling for images in MarkiTect projects
|
||||
- Best practices for organizing image assets in documentation
|
||||
|
||||
--worsch, 25-10-29
|
||||
BIN
examples/image-assets/images/architecture_diagram.png
Normal file
BIN
examples/image-assets/images/architecture_diagram.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
BIN
examples/image-assets/images/company_logo.png
Normal file
BIN
examples/image-assets/images/company_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
BIN
examples/image-assets/images/dashboard_screenshot.png
Normal file
BIN
examples/image-assets/images/dashboard_screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
examples/image-assets/images/performance_chart.png
Normal file
BIN
examples/image-assets/images/performance_chart.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
BIN
examples/image-assets/images/project_icon.png
Normal file
BIN
examples/image-assets/images/project_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 458 B |
BIN
examples/image-assets/images/settings_panel.png
Normal file
BIN
examples/image-assets/images/settings_panel.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.7 KiB |
71
examples/image-assets/project_documentation.md
Normal file
71
examples/image-assets/project_documentation.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Project Documentation Example
|
||||
|
||||
## Overview
|
||||
|
||||
This document demonstrates MarkiTect's image asset management capabilities by embedding various types of images commonly used in technical documentation.
|
||||
|
||||
## Architecture Diagram
|
||||
|
||||
The following diagram shows the overall system architecture:
|
||||
|
||||

|
||||
|
||||
*Figure 1: High-level system architecture showing component interactions*
|
||||
|
||||
## User Interface Screenshots
|
||||
|
||||
### Dashboard View
|
||||
|
||||
The main dashboard provides an overview of system status:
|
||||
|
||||

|
||||
|
||||
*Figure 2: Main dashboard interface with key metrics and navigation*
|
||||
|
||||
### Settings Panel
|
||||
|
||||
Users can configure system behavior through the settings panel:
|
||||
|
||||

|
||||
|
||||
*Figure 3: Configuration interface for system preferences*
|
||||
|
||||
## Logo and Branding
|
||||
|
||||
### Company Logo
|
||||
|
||||

|
||||
|
||||
### Project Icon
|
||||
|
||||
The project uses this icon throughout the interface:
|
||||
|
||||

|
||||
|
||||
## Asset Management Features
|
||||
|
||||
MarkiTect provides several key features for managing image assets:
|
||||
|
||||
1. **Content-Addressable Storage**: Images are stored using SHA-256 hashes to prevent duplication
|
||||
2. **Automatic Deduplication**: Identical images are only stored once, regardless of filename
|
||||
3. **Relative Path Resolution**: Images can be referenced using relative paths from the markdown file
|
||||
4. **Asset Tracking**: All referenced assets are tracked and validated during document processing
|
||||
|
||||
## Performance Metrics
|
||||
|
||||
The following chart shows system performance over time:
|
||||
|
||||

|
||||
|
||||
*Figure 4: System performance metrics showing response time and throughput*
|
||||
|
||||
## Conclusion
|
||||
|
||||
This example demonstrates how MarkiTect seamlessly handles multiple image assets within a single document, providing:
|
||||
|
||||
- Efficient storage through deduplication
|
||||
- Reliable asset resolution
|
||||
- Clean integration with markdown syntax
|
||||
- Support for various image formats (PNG, JPG, SVG, etc.)
|
||||
|
||||
All images in this document will be processed through MarkiTect's asset management system when the document is rendered or packaged.
|
||||
@@ -223,6 +223,45 @@ class MarkdownScanner:
|
||||
return len(lines)
|
||||
|
||||
|
||||
def discover_assets_from_markdown(markdown_content: str, base_path: Path) -> List[AssetReference]:
|
||||
"""
|
||||
Simple function to discover assets from markdown content for md-render.
|
||||
|
||||
Args:
|
||||
markdown_content: The markdown content to scan
|
||||
base_path: Base path for resolving relative asset paths
|
||||
|
||||
Returns:
|
||||
List of AssetReference objects found in the markdown
|
||||
"""
|
||||
scanner = MarkdownScanner()
|
||||
|
||||
# Create a temporary file to use the existing scan_file method
|
||||
import tempfile
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as temp_file:
|
||||
temp_file.write(markdown_content)
|
||||
temp_path = Path(temp_file.name)
|
||||
|
||||
try:
|
||||
references = scanner.scan_file(temp_path)
|
||||
# Update the source_file to the actual base_path for relative resolution
|
||||
for ref in references:
|
||||
ref.source_file = base_path
|
||||
# Resolve the asset path relative to base_path
|
||||
if not ref.asset_path.startswith(('http:', 'https:', 'mailto:', 'data:')):
|
||||
# Clean up relative path indicators
|
||||
clean_path = ref.asset_path.lstrip('./')
|
||||
resolved_path = base_path / clean_path
|
||||
if resolved_path.exists():
|
||||
ref.resolved_path = resolved_path
|
||||
else:
|
||||
ref.is_broken = True
|
||||
return references
|
||||
finally:
|
||||
# Clean up temporary file
|
||||
temp_path.unlink(missing_ok=True)
|
||||
|
||||
|
||||
class AssetDiscoveryEngine:
|
||||
"""Main engine for asset discovery and analysis."""
|
||||
|
||||
|
||||
@@ -1974,9 +1974,14 @@ def md_list_command(ctx, output_format, names_only):
|
||||
help='Don\'t use publication directory for output')
|
||||
@click.option('--nodogtag', is_flag=True,
|
||||
help='Don\'t add HTML generation dogtag at end of document')
|
||||
@click.option('--ship-assets', is_flag=True, default=None,
|
||||
help='Copy referenced assets to output directory')
|
||||
@click.option('--no-ship-assets', is_flag=True,
|
||||
help='Don\'t copy referenced assets to output directory')
|
||||
@click.pass_context
|
||||
def md_render_command(ctx, input_file, output, theme, css, edit, insert, editor_theme,
|
||||
keyboard_shortcuts, use_publication_dir, dont_use_publication_dir, nodogtag):
|
||||
keyboard_shortcuts, use_publication_dir, dont_use_publication_dir, nodogtag,
|
||||
ship_assets, no_ship_assets):
|
||||
"""
|
||||
Render a markdown file to HTML with basic templates and live preview capabilities.
|
||||
|
||||
@@ -2008,17 +2013,61 @@ def md_render_command(ctx, input_file, output, theme, css, edit, insert, editor_
|
||||
if edit and insert:
|
||||
raise click.BadParameter("Cannot use both --edit and --insert flags simultaneously. Choose one mode.")
|
||||
|
||||
# Determine output path
|
||||
# Validate asset shipping flags
|
||||
if ship_assets and no_ship_assets:
|
||||
raise click.BadParameter("Cannot use both --ship-assets and --no-ship-assets flags simultaneously.")
|
||||
|
||||
# Determine output path with environment variable support
|
||||
if output:
|
||||
output_path = Path(output)
|
||||
# If output is a directory, use canonical filename within that directory
|
||||
if output_path.is_dir() or (not output_path.suffix and not output_path.exists()):
|
||||
# Ensure the directory exists
|
||||
output_path.mkdir(parents=True, exist_ok=True)
|
||||
# Use canonical filename (input name + .html) in the specified directory
|
||||
canonical_filename = input_path.with_suffix('.html').name
|
||||
output_path = output_path / canonical_filename
|
||||
output_is_directory = True
|
||||
else:
|
||||
output_is_directory = False
|
||||
else:
|
||||
output_path = input_path.with_suffix('.html')
|
||||
# Check for environment variable
|
||||
import os
|
||||
env_output_dir = os.environ.get('MARKITECT_OUTPUT_DIR')
|
||||
if env_output_dir:
|
||||
output_path = Path(env_output_dir)
|
||||
output_path.mkdir(parents=True, exist_ok=True)
|
||||
canonical_filename = input_path.with_suffix('.html').name
|
||||
output_path = output_path / canonical_filename
|
||||
output_is_directory = True
|
||||
else:
|
||||
output_path = input_path.with_suffix('.html')
|
||||
output_is_directory = False
|
||||
|
||||
# Use publication directory if specified
|
||||
if use_publication_dir and not dont_use_publication_dir:
|
||||
pub_dir = get_publication_directory()
|
||||
ensure_publication_directory(pub_dir)
|
||||
output_path = pub_dir / get_output_filename(input_path)
|
||||
output_is_directory = True # Publication dir is always a directory output
|
||||
|
||||
# Determine if we should ship assets
|
||||
should_ship_assets = False
|
||||
if no_ship_assets:
|
||||
should_ship_assets = False
|
||||
elif ship_assets:
|
||||
should_ship_assets = True
|
||||
elif output_is_directory:
|
||||
# Default: ship assets when output is a directory
|
||||
should_ship_assets = True
|
||||
|
||||
|
||||
# Discover and ship assets if needed
|
||||
if should_ship_assets:
|
||||
if output_is_directory:
|
||||
# For directory output, ship to the same directory as the HTML file
|
||||
_ship_assets(input_path, output_path.parent, config.get('verbose', False))
|
||||
# For file output, we don't ship assets (shouldn't reach here anyway)
|
||||
|
||||
# Initialize clean document manager
|
||||
from markitect.clean_document_manager import CleanDocumentManager
|
||||
@@ -3433,3 +3482,76 @@ class FilenameDecoder:
|
||||
return [self.decode(filename) for filename in filenames]
|
||||
|
||||
|
||||
def _ship_assets(input_path: Path, output_dir: Path, verbose: bool = False):
|
||||
"""
|
||||
Ship (copy) assets referenced in markdown file to output directory.
|
||||
|
||||
Args:
|
||||
input_path: Path to the markdown file
|
||||
output_dir: Directory where assets should be copied
|
||||
verbose: Whether to print verbose output
|
||||
"""
|
||||
import shutil
|
||||
from markitect.assets.discovery import discover_assets_from_markdown
|
||||
|
||||
try:
|
||||
# Read the markdown content
|
||||
markdown_content = input_path.read_text(encoding='utf-8')
|
||||
|
||||
# Discover assets
|
||||
base_path = input_path.parent
|
||||
assets = discover_assets_from_markdown(markdown_content, base_path)
|
||||
|
||||
shipped_count = 0
|
||||
skipped_count = 0
|
||||
missing_count = 0
|
||||
|
||||
for asset_ref in assets:
|
||||
# Skip URLs and broken assets
|
||||
if asset_ref.asset_path.startswith(('http:', 'https:', 'mailto:', 'data:')):
|
||||
continue
|
||||
|
||||
if asset_ref.is_broken or not asset_ref.resolved_path:
|
||||
missing_count += 1
|
||||
if verbose:
|
||||
click.echo(f" ⚠ Missing asset: {asset_ref.asset_path}", err=True)
|
||||
continue
|
||||
|
||||
# Determine output path (preserve relative directory structure)
|
||||
clean_path = asset_ref.asset_path.lstrip('./')
|
||||
dest_path = output_dir / clean_path
|
||||
|
||||
# Create destination directory
|
||||
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Check if we need to copy (timestamp-based)
|
||||
should_copy = True
|
||||
if dest_path.exists():
|
||||
source_mtime = asset_ref.resolved_path.stat().st_mtime
|
||||
dest_mtime = dest_path.stat().st_mtime
|
||||
if source_mtime <= dest_mtime:
|
||||
should_copy = False
|
||||
skipped_count += 1
|
||||
|
||||
if should_copy:
|
||||
shutil.copy2(asset_ref.resolved_path, dest_path)
|
||||
shipped_count += 1
|
||||
if verbose:
|
||||
click.echo(f" ✓ Copied: {asset_ref.asset_path}")
|
||||
elif verbose:
|
||||
click.echo(f" → Skipped (up-to-date): {asset_ref.asset_path}")
|
||||
|
||||
# Summary
|
||||
if verbose or shipped_count > 0:
|
||||
if shipped_count > 0:
|
||||
click.echo(f"✓ Shipped {shipped_count} assets")
|
||||
if skipped_count > 0:
|
||||
click.echo(f" → Skipped {skipped_count} up-to-date assets")
|
||||
if missing_count > 0:
|
||||
click.echo(f" ⚠ {missing_count} assets not found", err=True)
|
||||
|
||||
except Exception as e:
|
||||
if verbose:
|
||||
click.echo(f"Error shipping assets: {e}", err=True)
|
||||
|
||||
|
||||
|
||||
240
tests/test_md_render_asset_shipping.py
Normal file
240
tests/test_md_render_asset_shipping.py
Normal file
@@ -0,0 +1,240 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
TDD tests for asset shipping in md-render command.
|
||||
|
||||
Tests the automatic copying of referenced assets when rendering markdown
|
||||
to different output directories.
|
||||
"""
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from markitect.plugins.builtin.markdown_commands import md_render_command
|
||||
from click.testing import CliRunner
|
||||
|
||||
|
||||
class TestAssetShippingMdRender:
|
||||
"""Test asset shipping functionality in md-render."""
|
||||
|
||||
def setup_method(self):
|
||||
"""Set up test environment."""
|
||||
self.runner = CliRunner()
|
||||
self.temp_dir = tempfile.mkdtemp()
|
||||
self.test_dir = Path(self.temp_dir)
|
||||
|
||||
# Create test markdown with image references
|
||||
self.markdown_content = """# Test Document
|
||||
|
||||
## Images
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
## Links
|
||||
|
||||
[Documentation](docs/readme.md)
|
||||
"""
|
||||
|
||||
# Create test file structure
|
||||
self.md_file = self.test_dir / "test.md"
|
||||
self.md_file.write_text(self.markdown_content)
|
||||
|
||||
# Create asset directories and files
|
||||
(self.test_dir / "images").mkdir()
|
||||
(self.test_dir / "assets").mkdir()
|
||||
(self.test_dir / "diagrams").mkdir()
|
||||
(self.test_dir / "docs").mkdir()
|
||||
|
||||
# Create sample asset files
|
||||
(self.test_dir / "images" / "arch.png").write_bytes(b"fake png data")
|
||||
(self.test_dir / "assets" / "logo.jpg").write_bytes(b"fake jpg data")
|
||||
(self.test_dir / "diagrams" / "flow.svg").write_text("<svg>fake svg</svg>")
|
||||
(self.test_dir / "docs" / "readme.md").write_text("# README")
|
||||
|
||||
def teardown_method(self):
|
||||
"""Clean up test environment."""
|
||||
import shutil
|
||||
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
||||
|
||||
def test_environment_variable_output_directory(self):
|
||||
"""Test that MARKITECT_OUTPUT_DIR is used when no --output is specified."""
|
||||
output_dir = self.test_dir / "env_output"
|
||||
output_dir.mkdir()
|
||||
|
||||
with patch.dict(os.environ, {'MARKITECT_OUTPUT_DIR': str(output_dir)}):
|
||||
result = self.runner.invoke(md_render_command, [str(self.md_file)])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert (output_dir / "test.html").exists()
|
||||
|
||||
def test_cli_output_overrides_environment_variable(self):
|
||||
"""Test that CLI --output parameter overrides environment variable."""
|
||||
env_output = self.test_dir / "env_output"
|
||||
cli_output = self.test_dir / "cli_output"
|
||||
env_output.mkdir()
|
||||
cli_output.mkdir()
|
||||
|
||||
with patch.dict(os.environ, {'MARKITECT_OUTPUT_DIR': str(env_output)}):
|
||||
result = self.runner.invoke(md_render_command, [
|
||||
str(self.md_file),
|
||||
'--output', str(cli_output)
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert (cli_output / "test.html").exists()
|
||||
assert not (env_output / "test.html").exists()
|
||||
|
||||
def test_asset_shipping_enabled_by_default_for_directory_output(self):
|
||||
"""Test that assets are shipped automatically when output is a directory."""
|
||||
output_dir = self.test_dir / "output"
|
||||
output_dir.mkdir()
|
||||
|
||||
result = self.runner.invoke(md_render_command, [
|
||||
str(self.md_file),
|
||||
'--output', str(output_dir)
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert (output_dir / "test.html").exists()
|
||||
|
||||
# Check that assets were copied
|
||||
assert (output_dir / "images" / "arch.png").exists()
|
||||
assert (output_dir / "assets" / "logo.jpg").exists()
|
||||
assert (output_dir / "diagrams" / "flow.svg").exists()
|
||||
assert (output_dir / "docs" / "readme.md").exists()
|
||||
|
||||
def test_no_ship_assets_flag_suppresses_asset_copying(self):
|
||||
"""Test that --no-ship-assets flag prevents asset copying."""
|
||||
output_dir = self.test_dir / "output"
|
||||
output_dir.mkdir()
|
||||
|
||||
result = self.runner.invoke(md_render_command, [
|
||||
str(self.md_file),
|
||||
'--output', str(output_dir),
|
||||
'--no-ship-assets'
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert (output_dir / "test.html").exists()
|
||||
|
||||
# Check that assets were NOT copied
|
||||
assert not (output_dir / "images").exists()
|
||||
assert not (output_dir / "assets").exists()
|
||||
assert not (output_dir / "diagrams").exists()
|
||||
|
||||
def test_timestamp_based_asset_copying(self):
|
||||
"""Test that assets are only copied if source is newer than destination."""
|
||||
output_dir = self.test_dir / "output"
|
||||
output_dir.mkdir()
|
||||
|
||||
# First render - assets should be copied
|
||||
result = self.runner.invoke(md_render_command, [
|
||||
str(self.md_file),
|
||||
'--output', str(output_dir)
|
||||
])
|
||||
assert result.exit_code == 0
|
||||
|
||||
# Mark output asset as newer
|
||||
output_asset = output_dir / "images" / "arch.png"
|
||||
original_mtime = output_asset.stat().st_mtime
|
||||
output_asset.touch() # Update timestamp
|
||||
|
||||
# Second render - asset should not be overwritten
|
||||
result = self.runner.invoke(md_render_command, [
|
||||
str(self.md_file),
|
||||
'--output', str(output_dir)
|
||||
])
|
||||
assert result.exit_code == 0
|
||||
|
||||
# Check that the timestamp wasn't changed (asset wasn't overwritten)
|
||||
assert output_asset.stat().st_mtime > original_mtime
|
||||
|
||||
def test_ship_assets_flag_explicit_enable(self):
|
||||
"""Test that --ship-assets flag explicitly enables asset shipping."""
|
||||
output_dir = self.test_dir / "output"
|
||||
output_dir.mkdir()
|
||||
|
||||
result = self.runner.invoke(md_render_command, [
|
||||
str(self.md_file),
|
||||
'--output', str(output_dir),
|
||||
'--ship-assets'
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert (output_dir / "test.html").exists()
|
||||
assert (output_dir / "images" / "arch.png").exists()
|
||||
|
||||
def test_missing_assets_handled_gracefully(self):
|
||||
"""Test that missing assets are handled with warnings, not errors."""
|
||||
# Remove one of the assets
|
||||
(self.test_dir / "images" / "arch.png").unlink()
|
||||
|
||||
output_dir = self.test_dir / "output"
|
||||
output_dir.mkdir()
|
||||
|
||||
result = self.runner.invoke(md_render_command, [
|
||||
str(self.md_file),
|
||||
'--output', str(output_dir)
|
||||
])
|
||||
|
||||
# Should succeed despite missing asset
|
||||
assert result.exit_code == 0
|
||||
assert (output_dir / "test.html").exists()
|
||||
|
||||
# Other assets should still be copied
|
||||
assert (output_dir / "assets" / "logo.jpg").exists()
|
||||
|
||||
def test_asset_discovery_from_markdown_content(self):
|
||||
"""Test discovery of assets from markdown content."""
|
||||
from markitect.assets.discovery import discover_assets_from_markdown
|
||||
|
||||
assets = discover_assets_from_markdown(self.markdown_content, self.test_dir)
|
||||
|
||||
# Should find all asset references
|
||||
asset_paths = [asset.asset_path for asset in assets]
|
||||
assert "images/arch.png" in asset_paths
|
||||
assert "assets/logo.jpg" in asset_paths
|
||||
assert "./diagrams/flow.svg" in asset_paths
|
||||
assert "docs/readme.md" in asset_paths
|
||||
|
||||
def test_relative_path_preservation(self):
|
||||
"""Test that relative path structure is preserved in output."""
|
||||
output_dir = self.test_dir / "output"
|
||||
output_dir.mkdir()
|
||||
|
||||
result = self.runner.invoke(md_render_command, [
|
||||
str(self.md_file),
|
||||
'--output', str(output_dir)
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
# Check that directory structure is preserved
|
||||
assert (output_dir / "images" / "arch.png").exists()
|
||||
assert (output_dir / "assets" / "logo.jpg").exists()
|
||||
assert (output_dir / "diagrams" / "flow.svg").exists()
|
||||
assert (output_dir / "docs" / "readme.md").exists()
|
||||
|
||||
def test_asset_shipping_disabled_for_file_output(self):
|
||||
"""Test that asset shipping is disabled when output is a specific file."""
|
||||
# Create a separate output directory
|
||||
output_dir = self.test_dir / "output_dir"
|
||||
output_dir.mkdir()
|
||||
output_file = output_dir / "specific_output.html"
|
||||
|
||||
result = self.runner.invoke(md_render_command, [
|
||||
str(self.md_file),
|
||||
'--output', str(output_file)
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert output_file.exists()
|
||||
|
||||
# Assets should NOT be copied when output is a specific file
|
||||
# (they should not exist in the output directory)
|
||||
assert not (output_dir / "images").exists()
|
||||
assert not (output_dir / "assets").exists()
|
||||
Reference in New Issue
Block a user