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
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:
@@ -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
|
||||
# ==============================================================================
|
||||
|
||||
Reference in New Issue
Block a user