feat: complete Issue #151 - Phase 4: Integration and Documentation
Some checks failed
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 / unit-tests (3.11) (push) Has been cancelled
Test Suite / unit-tests (3.12) (push) Has been cancelled
Test Suite / test-summary (push) Has been cancelled

Implements comprehensive CLI integration and documentation for the
explode-implode system, completing both Issues #147 and #151.

Key Features Added:
- md-package CLI command (create/extract/info actions)
- md-transclude CLI command (process/validate actions)
- Complete user guide (556 lines) with tutorials and examples
- Technical API documentation (500 lines) for developers
- Migration guide (761 lines) with step-by-step procedures
- Cost analysis documenting ~85 hours of development value

Technical Implementation:
- Full MDZ packaging support with asset embedding
- Template-based transclusion with variable substitution
- Comprehensive error handling and verbose output modes
- Integration with existing MarkiTect CLI architecture

Documentation Suite:
- docs/user-guides/explode-implode-complete-guide.md
- docs/api/explode-variants.md
- docs/user-guides/migration-guide.md
- docs/cost-analysis/issues-147-151-implementation.md

This implementation transforms MarkiTect from a simple markdown
processor into a comprehensive document management platform with
sophisticated organizational capabilities.

Closes #147: Directory organization preservation fully implemented
Closes #151: CLI integration and documentation completed

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-14 11:11:51 +02:00
parent e8e0fbaec3
commit 6ddd4ea6e3
7 changed files with 2389 additions and 1 deletions

View File

@@ -1499,7 +1499,9 @@ class MarkdownCommandsPlugin(CommandPlugin):
'md-render': md_render_command,
'md-index': md_index_command,
'md-explode': md_explode_command,
'md-implode': md_implode_command
'md-implode': md_implode_command,
'md-package': md_package_command,
'md-transclude': md_transclude_command
}
@@ -2069,6 +2071,320 @@ def md_implode_command(ctx, input_dir, output, force_variant, dry_run, verbose,
raise click.Abort()
# ==============================================================================
# Advanced Packaging Commands
# ==============================================================================
@click.command()
@click.argument('action', type=click.Choice(['create', 'extract', 'info']))
@click.argument('input_path', type=click.Path(exists=True))
@click.option('--output', '-o', type=click.Path(),
help='Output path for package or extraction')
@click.option('--format', '-f', type=click.Choice(['mdz', 'mdt']), default='mdz',
help='Package format (mdz for Markdown Zip, mdt for Markdown Transcluded)')
@click.option('--compression', '-c', type=click.IntRange(0, 9), default=6,
help='Compression level for MDZ packages (0-9)')
@click.option('--include-assets', is_flag=True, default=True,
help='Include assets when creating packages')
@click.option('--variables', type=click.Path(exists=True),
help='JSON file with variables for MDT processing')
@click.option('--dry-run', is_flag=True,
help='Show what would be done without making changes')
@click.option('--verbose', '-v', is_flag=True,
help='Enable verbose output')
@click.pass_context
def md_package_command(ctx, action, input_path, output, format, compression,
include_assets, variables, dry_run, verbose):
"""
Advanced package management for markdown documents.
Actions:
- create: Create MDZ/MDT package from source
- extract: Extract package contents
- info: Show package information
Examples:
markitect md-package create document.md --format mdz --output document.mdz
markitect md-package extract document.mdz --output extracted/
markitect md-package info document.mdz
"""
try:
input_path = Path(input_path)
if action == 'create':
# Import packaging modules
from markitect.packaging.mdz_variant import MdzVariant
from markitect.packaging.transclusion import TransclusionEngine
if not output:
if format == 'mdz':
output = input_path.with_suffix('.mdz')
else:
output = input_path.with_suffix('.mdt')
else:
output = Path(output)
if verbose:
click.echo(f"📦 Creating {format.upper()} package")
click.echo(f"📄 Source: {input_path}")
click.echo(f"📦 Output: {output}")
if dry_run:
click.echo("🔍 Dry run - no files would be created")
return
if format == 'mdz':
mdz = MdzVariant()
result = mdz.create_package(
source_path=input_path,
options={
'output_path': output,
'compression_level': compression
}
)
click.echo(f"✅ MDZ package created successfully")
click.echo(f"📦 Package: {result.get('package_path', output)}")
click.echo(f"📊 Assets embedded: {result.get('assets_embedded', 0)}")
click.echo(f"💾 Package size: {result.get('package_size', 0):,} bytes")
else: # mdt format
if not input_path.is_file():
click.echo("❌ MDT format requires a single markdown file", err=True)
raise click.Abort()
# For MDT, we just copy the file with transclusion processing
content = input_path.read_text(encoding='utf-8')
# Process with transclusion engine if variables provided
if variables:
variables_path = Path(variables)
if variables_path.exists():
import json
var_data = json.loads(variables_path.read_text())
engine = TransclusionEngine(
base_path=input_path.parent,
variables=var_data
)
content = engine.process_content(content)
output.write_text(content, encoding='utf-8')
click.echo(f"✅ MDT template created successfully")
click.echo(f"📄 Template: {output}")
elif action == 'extract':
from markitect.packaging.mdz_variant import MdzVariant
if not output:
output = input_path.parent / f"{input_path.stem}_extracted"
else:
output = Path(output)
if verbose:
click.echo(f"📂 Extracting package")
click.echo(f"📦 Source: {input_path}")
click.echo(f"📁 Output: {output}")
if dry_run:
click.echo("🔍 Dry run - no files would be extracted")
return
mdz = MdzVariant()
result = mdz.extract_package(
package_path=input_path,
options={'output_dir': output}
)
click.echo(f"✅ Package extracted successfully")
click.echo(f"📁 Output directory: {result['output_directory']}")
click.echo(f"📄 Files extracted: {result['files_extracted']}")
elif action == 'info':
from markitect.packaging.mdz_variant import MdzVariant
if verbose:
click.echo(f" Package information for: {input_path}")
mdz = MdzVariant()
metadata = mdz.get_package_metadata(input_path)
click.echo(f"📋 Package Format: {metadata.format}")
click.echo(f"🏷️ Format Version: {metadata.version}")
click.echo(f"⏰ Created: {metadata.created}")
click.echo(f"🛠️ MarkiTect Version: {metadata.markitect_version}")
click.echo(f"📊 Assets: {len(metadata.assets) if metadata.assets else 0}")
if verbose and metadata.assets:
click.echo("\n📁 Assets:")
for asset in metadata.assets:
click.echo(f" - {asset.path} ({asset.size:,} bytes)")
except Exception as e:
click.echo(f"❌ Error during package operation: {e}", err=True)
if ctx.obj and ctx.obj.get('debug'):
import traceback
traceback.print_exc()
raise click.Abort()
@click.command()
@click.argument('action', type=click.Choice(['process', 'validate']))
@click.argument('input_file', type=click.Path(exists=True))
@click.option('--output', '-o', type=click.Path(),
help='Output file for processed content')
@click.option('--variables', type=click.Path(exists=True),
help='JSON file containing template variables')
@click.option('--base-path', type=click.Path(exists=True),
help='Base path for resolving includes (defaults to input file directory)')
@click.option('--max-depth', type=int, default=10,
help='Maximum inclusion depth to prevent infinite recursion')
@click.option('--dry-run', is_flag=True,
help='Show what would be processed without creating output')
@click.option('--verbose', '-v', is_flag=True,
help='Enable verbose output with processing details')
@click.pass_context
def md_transclude_command(ctx, action, input_file, output, variables, base_path,
max_depth, dry_run, verbose):
"""
Process markdown files with transclusion directives.
Actions:
- process: Process transclusion directives and generate output
- validate: Check template for errors without processing
Transclusion directives supported:
- {{include "file.md"}} - Include another markdown file
- {{variable_name}} - Substitute variables
- {{if condition}} content {{endif}} - Conditional content
Examples:
markitect md-transclude process template.mdt --variables vars.json
markitect md-transclude validate template.mdt
markitect md-transclude process template.mdt --output result.md
"""
try:
from markitect.packaging.transclusion import TransclusionEngine
from markitect.packaging.errors import TransclusionError, CircularReferenceError
input_file = Path(input_file)
# Load variables if provided
var_data = {}
if variables:
variables_path = Path(variables)
if verbose:
click.echo(f"📋 Loading variables from: {variables_path}")
import json
var_data = json.loads(variables_path.read_text())
# Set base path
if base_path:
base_path = Path(base_path)
else:
base_path = input_file.parent
if verbose:
click.echo(f"📄 Processing template: {input_file}")
click.echo(f"📁 Base path: {base_path}")
click.echo(f"📋 Variables: {len(var_data)} loaded")
click.echo(f"🔢 Max depth: {max_depth}")
# Create transclusion engine
engine = TransclusionEngine(
base_path=base_path,
variables=var_data,
max_depth=max_depth
)
if action == 'validate':
# Validate template without full processing
try:
content = input_file.read_text(encoding='utf-8')
# Parse directives to check syntax
from markitect.packaging.transclusion.directives import DirectiveParser
directives = DirectiveParser.parse_directives(content)
click.echo(f"✅ Template validation successful")
click.echo(f"📊 Found {len(directives)} transclusion directives")
if verbose:
for directive in directives:
click.echo(f" - {directive.type}: {directive.args}")
# Check for potential circular references
file_includes = DirectiveParser.extract_file_includes(content)
if file_includes:
click.echo(f"📁 File includes: {len(file_includes)}")
if verbose:
for include in file_includes:
include_path = base_path / include
status = "" if include_path.exists() else ""
click.echo(f" {status} {include}")
except Exception as e:
click.echo(f"❌ Template validation failed: {e}", err=True)
raise click.Abort()
elif action == 'process':
if not output:
output = input_file.with_suffix('.processed.md')
else:
output = Path(output)
if verbose:
click.echo(f"🔄 Processing transclusion directives")
click.echo(f"📤 Output: {output}")
if dry_run:
click.echo("🔍 Dry run - no output file would be created")
try:
result = engine.process_file(input_file)
click.echo(f"✅ Template processed successfully ({len(result)} characters)")
except CircularReferenceError as e:
click.echo(f"❌ Circular reference detected: {e}", err=True)
raise click.Abort()
except TransclusionError as e:
click.echo(f"❌ Transclusion error: {e}", err=True)
raise click.Abort()
return
# Process the template
try:
result = engine.process_file(input_file)
# Write output
output.write_text(result, encoding='utf-8')
click.echo(f"✅ Transclusion processing completed")
click.echo(f"📄 Input: {input_file}")
click.echo(f"📄 Output: {output}")
click.echo(f"📊 Output size: {len(result):,} characters")
if verbose:
# Count lines for additional stats
lines = result.count('\n') + 1
click.echo(f"📊 Output lines: {lines:,}")
except CircularReferenceError as e:
click.echo(f"❌ Circular reference detected: {e}", err=True)
click.echo("💡 Check your include directives for loops", err=True)
raise click.Abort()
except TransclusionError as e:
click.echo(f"❌ Transclusion error: {e}", err=True)
raise click.Abort()
except Exception as e:
click.echo(f"❌ Error during transclusion: {e}", err=True)
if ctx.obj and ctx.obj.get('debug'):
import traceback
traceback.print_exc()
raise click.Abort()
# ==============================================================================
# Utility Functions
# ==============================================================================