feat: complete CLI integration with plugin system
**CLI Integration:** - Added --engine parameter to md-render command - Default engine selection: testdrive-jsui for edit/insert, standard for view - Graceful fallback to standard rendering when plugin unavailable - Engine validation and mode compatibility checking **Plugin Discovery:** - Enhanced RenderingEngineManager with builtin plugin registration - Automatic discovery and registration of testdrive-jsui engine - Support for both plugin system discovery and direct registration **Configuration Management:** - Production-ready RenderingConfig for CLI usage - Asset deployment to _markitect/plugins/ structure - Configurable asset base URLs and deployment strategies **Testing Infrastructure:** - Comprehensive test suite for plugin discovery - CLI integration testing without Click framework dependencies - Complete scenario testing (default, explicit, fallback, unknown engines) - Integration verification scripts **Documentation:** - Complete PLUGIN_SYSTEM.md documentation - Architecture overview and development workflows - JavaScript-first development guide - Asset management and deployment strategies - CLI usage examples and troubleshooting guide **Key Features:** - `markitect md-render --edit` now uses testdrive-jsui by default - `markitect md-render --engine testdrive-jsui --edit` for explicit selection - `markitect md-render --engine standard --edit` for legacy behavior - Automatic fallback with user-friendly error messages This completes the plugin infrastructure implementation, enabling independent JavaScript development with seamless CLI integration. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -2033,6 +2033,8 @@ def md_list_command(ctx, output_format, names_only):
|
||||
help='Open in interactive edit mode with stable section editing')
|
||||
@click.option('--insert', is_flag=True,
|
||||
help='Open in interactive insert mode with heading protection (levels 1-3 read-only)')
|
||||
@click.option('--engine', type=str, default=None,
|
||||
help='Rendering engine to use (default: testdrive-jsui for edit/insert, standard for view)')
|
||||
@click.option('--editor-theme', default='github',
|
||||
type=click.Choice(['github', 'monokai', 'tomorrow', 'dark']),
|
||||
help='Editor theme for live edit mode (default: github)')
|
||||
@@ -2057,7 +2059,7 @@ def md_list_command(ctx, output_format, names_only):
|
||||
@click.option('--image-max-height', type=str, default=None,
|
||||
help='Maximum height for images (default: 20cm, supports px, em, %, cm, in, etc.)')
|
||||
@click.pass_context
|
||||
def md_render_command(ctx, input_file, output, theme, css, edit, insert, editor_theme,
|
||||
def md_render_command(ctx, input_file, output, theme, css, edit, insert, engine, editor_theme,
|
||||
keyboard_shortcuts, use_publication_dir, dont_use_publication_dir, nodogtag,
|
||||
ship_assets, no_ship_assets, verbose, silent, image_max_width, image_max_height):
|
||||
"""
|
||||
@@ -2171,21 +2173,83 @@ def md_render_command(ctx, input_file, output, theme, css, edit, insert, editor_
|
||||
# For directory output, ship to the same directory as the HTML file
|
||||
_ship_assets(input_path, output_path.parent, verbose, silent)
|
||||
|
||||
# Initialize clean document manager
|
||||
from markitect.clean_document_manager import CleanDocumentManager
|
||||
doc_manager = CleanDocumentManager(config.get('db_manager'))
|
||||
# Determine rendering engine to use
|
||||
if engine is None:
|
||||
# Default engine selection
|
||||
if edit or insert:
|
||||
engine = 'testdrive-jsui' # Default to testdrive-jsui for interactive modes
|
||||
else:
|
||||
engine = 'standard' # Use standard CleanDocumentManager for non-interactive
|
||||
|
||||
# Use plugin system for rendering engines, fallback to standard
|
||||
if engine != 'standard':
|
||||
try:
|
||||
from markitect.plugins import PluginManager, RenderingEngineManager, RenderingConfig
|
||||
plugin_manager = PluginManager()
|
||||
rendering_manager = RenderingEngineManager(plugin_manager)
|
||||
|
||||
rendering_engine = rendering_manager.get_engine(engine)
|
||||
if rendering_engine is None:
|
||||
if not silent:
|
||||
click.echo(f"⚠️ Rendering engine '{engine}' not found, falling back to standard", err=True)
|
||||
engine = 'standard'
|
||||
elif not silent:
|
||||
modes = rendering_engine.get_supported_modes()
|
||||
current_mode = 'edit' if edit else ('insert' if insert else 'view')
|
||||
if not rendering_engine.validate_mode(current_mode):
|
||||
click.echo(f"⚠️ Engine '{engine}' doesn't support mode '{current_mode}', falling back to standard", err=True)
|
||||
engine = 'standard'
|
||||
else:
|
||||
click.echo(f"🎯 Using rendering engine: {engine} (supports: {', '.join(modes)})")
|
||||
|
||||
except ImportError as e:
|
||||
if not silent:
|
||||
click.echo(f"⚠️ Plugin system not available ({e}), using standard rendering", err=True)
|
||||
engine = 'standard'
|
||||
|
||||
# Initialize document manager or rendering engine
|
||||
if engine == 'standard':
|
||||
from markitect.clean_document_manager import CleanDocumentManager
|
||||
doc_manager = CleanDocumentManager(config.get('db_manager'))
|
||||
|
||||
# Render the file
|
||||
if edit:
|
||||
# Edit mode - generate HTML with editing capabilities
|
||||
result = doc_manager.render_file(input_file, str(output_path),
|
||||
template=theme, css=css,
|
||||
edit_mode=True,
|
||||
editor_theme=editor_theme,
|
||||
keyboard_shortcuts=keyboard_shortcuts,
|
||||
nodogtag=nodogtag,
|
||||
image_max_width=final_image_max_width,
|
||||
image_max_height=final_image_max_height)
|
||||
if engine != 'standard':
|
||||
# Plugin-based rendering for edit mode
|
||||
try:
|
||||
# Read markdown content
|
||||
content = input_path.read_text(encoding='utf-8')
|
||||
|
||||
# Configure rendering
|
||||
render_config = RenderingConfig(
|
||||
asset_base_url="_markitect",
|
||||
development_mode=False, # Production deployment for CLI usage
|
||||
output_directory=output_path.parent
|
||||
)
|
||||
|
||||
# Render using plugin
|
||||
html_content = rendering_engine.render_document(content, 'edit', render_config)
|
||||
|
||||
# Write output
|
||||
output_path.write_text(html_content, encoding='utf-8')
|
||||
result = True
|
||||
|
||||
except Exception as e:
|
||||
if not silent:
|
||||
click.echo(f"❌ Plugin rendering failed: {e}", err=True)
|
||||
click.echo(" Falling back to standard rendering...", err=True)
|
||||
engine = 'standard'
|
||||
|
||||
if engine == 'standard':
|
||||
# Standard edit mode - generate HTML with editing capabilities
|
||||
result = doc_manager.render_file(input_file, str(output_path),
|
||||
template=theme, css=css,
|
||||
edit_mode=True,
|
||||
editor_theme=editor_theme,
|
||||
keyboard_shortcuts=keyboard_shortcuts,
|
||||
nodogtag=nodogtag,
|
||||
image_max_width=final_image_max_width,
|
||||
image_max_height=final_image_max_height)
|
||||
|
||||
if not silent:
|
||||
click.echo(f"✅ Rendered with INTERACTIVE editing mode to: {output_path}")
|
||||
@@ -2197,15 +2261,48 @@ def md_render_command(ctx, input_file, output, theme, css, edit, insert, editor_
|
||||
click.echo(f"Theme: {theme or 'default'}")
|
||||
click.echo(f"CSS: {css or 'default'}")
|
||||
elif insert:
|
||||
# Insert mode - generate HTML with insert capabilities and heading protection
|
||||
result = doc_manager.render_file(input_file, str(output_path),
|
||||
template=theme, css=css,
|
||||
insert_mode=True,
|
||||
editor_theme=editor_theme,
|
||||
keyboard_shortcuts=keyboard_shortcuts,
|
||||
nodogtag=nodogtag,
|
||||
image_max_width=final_image_max_width,
|
||||
image_max_height=final_image_max_height)
|
||||
if engine != 'standard':
|
||||
# Plugin-based rendering for insert mode
|
||||
try:
|
||||
# Read markdown content
|
||||
content = input_path.read_text(encoding='utf-8')
|
||||
|
||||
# Configure rendering
|
||||
render_config = RenderingConfig(
|
||||
asset_base_url="_markitect",
|
||||
development_mode=False, # Production deployment for CLI usage
|
||||
output_directory=output_path.parent
|
||||
)
|
||||
|
||||
# Render using plugin (note: insert mode may not be supported by all plugins)
|
||||
if rendering_engine.validate_mode('insert'):
|
||||
html_content = rendering_engine.render_document(content, 'insert', render_config)
|
||||
else:
|
||||
# Fallback to edit mode if insert not supported
|
||||
html_content = rendering_engine.render_document(content, 'edit', render_config)
|
||||
if not silent:
|
||||
click.echo(f"ℹ️ Engine '{engine}' doesn't support insert mode, using edit mode instead")
|
||||
|
||||
# Write output
|
||||
output_path.write_text(html_content, encoding='utf-8')
|
||||
result = True
|
||||
|
||||
except Exception as e:
|
||||
if not silent:
|
||||
click.echo(f"❌ Plugin rendering failed: {e}", err=True)
|
||||
click.echo(" Falling back to standard rendering...", err=True)
|
||||
engine = 'standard'
|
||||
|
||||
if engine == 'standard':
|
||||
# Standard insert mode - generate HTML with insert capabilities and heading protection
|
||||
result = doc_manager.render_file(input_file, str(output_path),
|
||||
template=theme, css=css,
|
||||
insert_mode=True,
|
||||
editor_theme=editor_theme,
|
||||
keyboard_shortcuts=keyboard_shortcuts,
|
||||
nodogtag=nodogtag,
|
||||
image_max_width=final_image_max_width,
|
||||
image_max_height=final_image_max_height)
|
||||
|
||||
if not silent:
|
||||
click.echo(f"✅ Rendered with INTERACTIVE insert mode to: {output_path}")
|
||||
|
||||
@@ -183,7 +183,7 @@ class RenderingEngineManager:
|
||||
|
||||
def _discover_rendering_engines(self):
|
||||
"""Discover rendering engine plugins."""
|
||||
# Get all plugins from the main plugin manager
|
||||
# First, try to load plugins from main plugin manager
|
||||
all_plugins = self.plugin_manager.discover_plugins()
|
||||
|
||||
for plugin_name, plugin_info in all_plugins.items():
|
||||
@@ -197,6 +197,22 @@ class RenderingEngineManager:
|
||||
except Exception as e:
|
||||
print(f"⚠️ Failed to load rendering engine {plugin_name}: {e}")
|
||||
|
||||
# Additionally, try to directly import and register known rendering engines
|
||||
self._register_builtin_rendering_engines()
|
||||
|
||||
def _register_builtin_rendering_engines(self):
|
||||
"""Register built-in rendering engines directly."""
|
||||
try:
|
||||
# Import and register testdrive-jsui engine
|
||||
from .testdrive_jsui import TestDriveJSUIEngine
|
||||
engine = TestDriveJSUIEngine()
|
||||
self._engines[engine.metadata.name] = engine
|
||||
print(f"✅ Registered built-in rendering engine: {engine.metadata.name}")
|
||||
except ImportError as e:
|
||||
print(f"⚠️ Could not import testdrive-jsui engine: {e}")
|
||||
except Exception as e:
|
||||
print(f"⚠️ Failed to register testdrive-jsui engine: {e}")
|
||||
|
||||
def get_engine(self, name: str) -> Optional[RenderingEnginePlugin]:
|
||||
"""Get a rendering engine by name."""
|
||||
return self._engines.get(name)
|
||||
|
||||
Reference in New Issue
Block a user