From 8f1cc0faf93a64c05555a43966e8585ff33bc5bb Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 14 Nov 2025 08:47:30 +0100 Subject: [PATCH] feat: complete CLI integration with plugin system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **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 --- docs/PLUGIN_SYSTEM.md | 452 ++++++++++++++++++ .../plugins/builtin/markdown_commands.py | 141 +++++- markitect/plugins/rendering.py | 18 +- markitect/templates/edit-mode.html | 131 +++++ test_cli_integration.py | 171 +++++++ test_cli_plugin.md | 25 + test_cli_simple.py | 121 +++++ test_complete_integration.py | 152 ++++++ test_plugin_discovery.py | 80 ++++ 9 files changed, 1268 insertions(+), 23 deletions(-) create mode 100644 docs/PLUGIN_SYSTEM.md create mode 100644 markitect/templates/edit-mode.html create mode 100644 test_cli_integration.py create mode 100644 test_cli_plugin.md create mode 100644 test_cli_simple.py create mode 100644 test_complete_integration.py create mode 100644 test_plugin_discovery.py diff --git a/docs/PLUGIN_SYSTEM.md b/docs/PLUGIN_SYSTEM.md new file mode 100644 index 00000000..c6e8e07a --- /dev/null +++ b/docs/PLUGIN_SYSTEM.md @@ -0,0 +1,452 @@ +# Markitect Plugin System Documentation + +## Overview + +The Markitect plugin system provides a modular architecture for extending rendering capabilities with independent JavaScript UI components. This system enables JavaScript-first development while maintaining clean integration with the Python ecosystem. + +## Architecture + +### Core Components + +1. **RenderingEnginePlugin**: Base class for UI rendering engines +2. **RenderingConfig**: Asset management and deployment configuration +3. **RenderingEngineManager**: Plugin discovery and lifecycle management +4. **PluginManager**: Integration with existing Markitect plugin system + +### Key Features + +- **Clean Separation**: JSON-based configuration interface (Python โ†” JavaScript) +- **Independent Development**: JavaScript components work standalone +- **Asset Management**: Configurable deployment strategies +- **Multiple Engines**: Support for different UI frameworks +- **Fallback Support**: Graceful degradation to standard rendering + +## Plugin Development + +### Creating a Rendering Engine Plugin + +1. **Extend RenderingEnginePlugin**: + +```python +from markitect.plugins.rendering import RenderingEnginePlugin, RenderingConfig +from markitect.plugins.base import PluginMetadata, PluginType + +class MyUIEngine(RenderingEnginePlugin): + def __init__(self): + super().__init__() + self._metadata = PluginMetadata( + name="my-ui-engine", + version="1.0.0", + description="Custom UI rendering engine", + author="Your Name", + plugin_type=PluginType.RENDERING + ) + + @property + def metadata(self) -> PluginMetadata: + return self._metadata + + def get_supported_modes(self) -> List[str]: + return ["edit", "view"] + + def get_required_assets(self) -> Dict[str, List[str]]: + return { + "js": ["static/js/main.js"], + "css": ["static/css/style.css"] + } + + def render_document(self, content: str, mode: str, config: RenderingConfig) -> str: + # Your rendering logic here + return html_output +``` + +2. **Directory Structure**: + +``` +my-ui-engine/ +โ”œโ”€โ”€ static/ +โ”‚ โ”œโ”€โ”€ js/ # JavaScript components +โ”‚ โ””โ”€โ”€ css/ # Stylesheets +โ”œโ”€โ”€ templates/ # HTML templates +โ”œโ”€โ”€ images/ # Icons and images +โ”œโ”€โ”€ test-documents/ # Sample markdown files +โ”œโ”€โ”€ package.json # Node.js configuration +โ””โ”€โ”€ README.md # Plugin documentation +``` + +3. **Asset Management**: + +Assets are automatically deployed based on configuration: +- **Development**: Served from plugin source directory +- **Production**: Copied to `_markitect/plugins/{plugin-name}/` + +## TestDrive JSUI Plugin + +### Overview + +The TestDrive JSUI plugin demonstrates the plugin architecture with a complete JavaScript UI for markdown editing. + +### Features + +- **Modular Components**: Clean separation of UI components +- **Compass Positioning**: NW, NE, E, SE control panel layout +- **Section Management**: Click-to-edit markdown sections +- **Debug System**: Built-in debugging and logging +- **Asset Pipeline**: Configurable asset deployment + +### Directory Structure + +``` +testdrive-jsui/ +โ”œโ”€โ”€ static/js/ +โ”‚ โ”œโ”€โ”€ core/ # Core systems +โ”‚ โ”‚ โ”œโ”€โ”€ debug-system.js +โ”‚ โ”‚ โ””โ”€โ”€ section-manager.js +โ”‚ โ”œโ”€โ”€ components/ # UI components +โ”‚ โ”‚ โ”œโ”€โ”€ debug-panel.js +โ”‚ โ”‚ โ”œโ”€โ”€ document-controls.js +โ”‚ โ”‚ โ””โ”€โ”€ dom-renderer.js +โ”‚ โ”œโ”€โ”€ controls/ # Control panels +โ”‚ โ”‚ โ”œโ”€โ”€ control-base.js +โ”‚ โ”‚ โ”œโ”€โ”€ contents-control.js # Northwest +โ”‚ โ”‚ โ”œโ”€โ”€ status-control.js # East +โ”‚ โ”‚ โ”œโ”€โ”€ debug-control.js # Southeast +โ”‚ โ”‚ โ””โ”€โ”€ edit-control.js # Northeast +โ”‚ โ”œโ”€โ”€ config-loader.js # Configuration interface +โ”‚ โ””โ”€โ”€ main-updated.js # Application entry point +โ”œโ”€โ”€ static/css/ # Stylesheets (future) +โ”œโ”€โ”€ images/ # Icons and images (future) +โ”œโ”€โ”€ templates/ +โ”‚ โ””โ”€โ”€ index.html # Main HTML template +โ”œโ”€โ”€ test-documents/ +โ”‚ โ””โ”€โ”€ sample.md # Test content +โ”œโ”€โ”€ test.html # Standalone development +โ”œโ”€โ”€ package.json # Node.js configuration +โ””โ”€โ”€ README.md # Plugin documentation +``` + +### JavaScript Architecture + +- **Configuration Interface**: Clean JSON data transfer via `markitect-config` script element +- **Modular Components**: Each component has single responsibility +- **Event System**: Pub/sub for component communication +- **Control System**: Abstract base class for UI controls +- **Compass Positioning**: Consistent control panel layout + +## CLI Integration + +### Command Line Usage + +```bash +# Use default engine (testdrive-jsui for edit/insert, standard for view) +markitect md-render --edit document.md + +# Specify engine explicitly +markitect md-render --engine testdrive-jsui --edit document.md + +# Use standard engine +markitect md-render --engine standard --edit document.md + +# View available engines +markitect md-render --help +``` + +### Engine Selection Logic + +1. **Default Selection**: + - Edit/Insert modes: `testdrive-jsui` + - View mode: `standard` + +2. **Explicit Selection**: Use `--engine` parameter + +3. **Fallback Strategy**: + - Engine not found โ†’ fallback to standard + - Mode not supported โ†’ fallback to standard + - Plugin error โ†’ fallback to standard + +### Integration Points + +The CLI integrates with the plugin system through: + +```python +# Engine discovery +plugin_manager = PluginManager() +rendering_manager = RenderingEngineManager(plugin_manager) + +# Engine selection +engine = rendering_manager.get_engine(engine_name) + +# Configuration +config = RenderingConfig( + asset_base_url="_markitect", + development_mode=False, + output_directory=output_path.parent +) + +# Rendering +html_content = engine.render_document(content, mode, config) +``` + +## Development Workflows + +### Standalone JavaScript Development + +1. **Setup**: + ```bash + cd testdrive-jsui + python -m http.server 8080 + ``` + +2. **Development**: + - Edit JavaScript files in `static/js/` + - Refresh browser to see changes + - Use `test.html` for testing + - Browser DevTools for debugging + +3. **Benefits**: + - No Python environment required + - Fast iteration cycle + - Standard web development tools + - Hot reloading + +### Integrated Development + +1. **Plugin Testing**: + ```bash + python demo_plugin_integration.py + ``` + +2. **CLI Testing**: + ```bash + markitect md-render --engine testdrive-jsui --edit test.md + ``` + +3. **Integration Verification**: + ```bash + python test_complete_integration.py + ``` + +## Asset Management + +### Development Mode + +```python +config = RenderingConfig( + asset_base_url=".", + development_mode=True, + plugin_source_dirs={"testdrive-jsui": Path("testdrive-jsui")} +) + +# Assets served as: file://testdrive-jsui/static/js/main.js +``` + +### Production Mode + +```python +config = RenderingConfig( + asset_base_url="_markitect", + development_mode=False, + output_directory=Path("/output") +) + +# Assets served as: _markitect/plugins/testdrive-jsui/static/js/main.js +``` + +### Asset Types + +- **js**: JavaScript files +- **css**: Stylesheets +- **images**: Icons, graphics +- **external**: CDN resources + +### Deployment Strategy + +1. **Assets Copying**: Plugin assets copied to `_markitect/plugins/{name}/` +2. **URL Generation**: Automatic URL generation for templates +3. **Cache Management**: Asset versioning and cache control +4. **Error Handling**: Fallback for missing assets + +## Configuration Interface + +### Python โ†’ JavaScript Data Transfer + +All dynamic data passes through a clean JSON interface: + +```html + +``` + +### JavaScript Configuration Loading + +```javascript +// Clean configuration loading +class MarkitectConfig { + constructor() { + this.loadConfig(); + } + + loadConfig() { + const configElement = document.getElementById('markitect-config'); + this.config = JSON.parse(configElement.textContent); + } +} +``` + +### Benefits + +- **No String Interpolation**: Prevents template literal escaping issues +- **Type Safety**: JSON validation and error handling +- **Clean Separation**: No JavaScript code in Python strings +- **Debuggable**: Easy to inspect configuration in browser + +## Testing + +### Plugin Testing + +```bash +# Basic plugin discovery +python test_plugin_discovery.py + +# CLI integration logic +python test_cli_simple.py + +# Complete scenario testing +python test_complete_integration.py + +# Full integration demo +python demo_plugin_integration.py +``` + +### Browser Testing + +1. **Standalone**: Open `testdrive-jsui/test.html` +2. **Generated**: Open CLI-generated HTML files +3. **DevTools**: Use browser debugging tools +4. **Console**: Check for JavaScript errors + +### Integration Testing + +- **Unit Tests**: Individual component testing +- **Integration Tests**: Component interaction testing +- **E2E Tests**: Full workflow testing +- **Regression Tests**: Ensure stability across changes + +## Extension Points + +### Adding New Engines + +1. Create new plugin extending `RenderingEnginePlugin` +2. Implement required methods (`get_supported_modes`, `render_document`, etc.) +3. Register in `RenderingEngineManager._register_builtin_rendering_engines()` +4. Test with CLI integration + +### Adding New Modes + +1. Add mode to engine's `get_supported_modes()` +2. Update `render_document()` to handle new mode +3. Test mode validation and rendering +4. Update CLI integration if needed + +### Adding New Asset Types + +1. Update `get_required_assets()` return format +2. Modify asset deployment logic in `RenderingConfig` +3. Update template system to handle new asset types +4. Test asset URL generation + +## Best Practices + +### Plugin Development + +- **Single Responsibility**: Each component has one clear purpose +- **Clean Interfaces**: Well-defined APIs between components +- **Error Handling**: Graceful degradation on failures +- **Documentation**: Clear README and code comments + +### JavaScript Development + +- **Modular Architecture**: Avoid monolithic JavaScript files +- **Event-Driven**: Use pub/sub for component communication +- **Configuration-Driven**: Avoid hardcoded values +- **Browser Compatibility**: Test across different browsers + +### Asset Management + +- **Relative Paths**: Use relative paths in asset definitions +- **Versioning**: Include version info for cache management +- **Optimization**: Minimize asset size for production +- **CDN Integration**: Use CDN for external dependencies + +### Testing Strategy + +- **Automated Testing**: Comprehensive test coverage +- **Manual Testing**: User workflow validation +- **Cross-Platform**: Test on different environments +- **Performance Testing**: Monitor rendering performance + +## Troubleshooting + +### Common Issues + +1. **Plugin Not Found**: + - Check plugin registration in `_register_builtin_rendering_engines()` + - Verify plugin class inheritance from `RenderingEnginePlugin` + - Check import paths and module availability + +2. **Asset Loading Errors**: + - Verify asset paths in `get_required_assets()` + - Check file permissions and existence + - Validate URL generation in different modes + +3. **Configuration Errors**: + - Check JSON syntax in configuration + - Verify configuration element ID (`markitect-config`) + - Test configuration loading in JavaScript + +4. **Rendering Failures**: + - Check template file existence and permissions + - Verify template placeholder replacement + - Test with minimal content for debugging + +### Debug Techniques + +- **Console Logging**: Use browser console for debugging +- **Debug Panel**: TestDrive JSUI includes debug information +- **Verbose Mode**: Use CLI `--verbose` flag for detailed output +- **Test Scripts**: Run individual test scripts for isolation + +## Future Enhancements + +### Planned Features + +- **Plugin Package Manager**: npm-like plugin distribution +- **Theme System Integration**: Plugin-aware theme system +- **Performance Monitoring**: Built-in performance tracking +- **Hot Reloading**: Automatic reload on file changes + +### Extension Opportunities + +- **React Integration**: React-based rendering engine +- **Vue Integration**: Vue.js-based rendering engine +- **TypeScript Support**: TypeScript plugin development +- **Testing Framework**: Automated JavaScript testing + +### Community + +- **Plugin Registry**: Central repository for community plugins +- **Documentation**: Expanded examples and tutorials +- **Templates**: Starter templates for new plugins +- **Best Practices**: Community guidelines and patterns + +--- + +*This plugin system enables JavaScript-first development while maintaining clean integration with the MarkiTect Python ecosystem, providing the best of both worlds for UI development and backend processing.* \ No newline at end of file diff --git a/markitect/plugins/builtin/markdown_commands.py b/markitect/plugins/builtin/markdown_commands.py index b2eda7e4..ed693b47 100644 --- a/markitect/plugins/builtin/markdown_commands.py +++ b/markitect/plugins/builtin/markdown_commands.py @@ -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}") diff --git a/markitect/plugins/rendering.py b/markitect/plugins/rendering.py index dbeedb97..2ff4ed53 100644 --- a/markitect/plugins/rendering.py +++ b/markitect/plugins/rendering.py @@ -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) diff --git a/markitect/templates/edit-mode.html b/markitect/templates/edit-mode.html new file mode 100644 index 00000000..a37ae88e --- /dev/null +++ b/markitect/templates/edit-mode.html @@ -0,0 +1,131 @@ + + + + + + {title} + {css_content} + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test_cli_integration.py b/test_cli_integration.py new file mode 100644 index 00000000..a7b3dc70 --- /dev/null +++ b/test_cli_integration.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +""" +Test CLI integration with plugin system +""" + +import sys +from pathlib import Path + +# Add current directory to path for imports +sys.path.insert(0, str(Path(__file__).parent)) + +def test_plugin_cli_integration(): + """Test the CLI integration with plugin system.""" + + # Import the command function + from markitect.plugins.builtin.markdown_commands import md_render_command + import click + + # Create a mock context + class MockContext: + def __init__(self): + self.obj = {} + self.resilient_parsing = False + self.allow_extra_args = False + self.allow_interspersed_args = True + self.ignore_unknown_options = False + self.help_option_names = ['--help'] + self.token_normalize_func = None + self.color = None + self.terminal_width = None + self.max_content_width = None + + ctx = MockContext() + + # Test parameters + input_file = "test_cli_plugin.md" + output = "/tmp/test_cli_plugin_default.html" + + print("๐Ÿงช Testing CLI Plugin Integration") + print("=" * 50) + + try: + # Test 1: Default engine (should use testdrive-jsui for edit mode) + print("\n1๏ธโƒฃ Testing default engine for edit mode...") + + md_render_command( + ctx=ctx, + input_file=input_file, + output=output, + theme=None, + css=None, + edit=True, + insert=False, + engine=None, # Should default to testdrive-jsui + editor_theme='github', + keyboard_shortcuts=True, + use_publication_dir=False, + dont_use_publication_dir=False, + nodogtag=False, + ship_assets=None, + no_ship_assets=False, + verbose=True, + silent=False, + image_max_width=None, + image_max_height=None + ) + + print("โœ… Default engine test completed") + + # Test 2: Explicit testdrive-jsui engine + print("\n2๏ธโƒฃ Testing explicit testdrive-jsui engine...") + + output2 = "/tmp/test_cli_plugin_explicit.html" + md_render_command( + ctx=ctx, + input_file=input_file, + output=output2, + theme=None, + css=None, + edit=True, + insert=False, + engine='testdrive-jsui', # Explicit engine + editor_theme='github', + keyboard_shortcuts=True, + use_publication_dir=False, + dont_use_publication_dir=False, + nodogtag=False, + ship_assets=None, + no_ship_assets=False, + verbose=True, + silent=False, + image_max_width=None, + image_max_height=None + ) + + print("โœ… Explicit engine test completed") + + # Test 3: Standard engine fallback + print("\n3๏ธโƒฃ Testing standard engine fallback...") + + output3 = "/tmp/test_cli_plugin_standard.html" + md_render_command( + ctx=ctx, + input_file=input_file, + output=output3, + theme=None, + css=None, + edit=True, + insert=False, + engine='standard', # Explicit standard engine + editor_theme='github', + keyboard_shortcuts=True, + use_publication_dir=False, + dont_use_publication_dir=False, + nodogtag=False, + ship_assets=None, + no_ship_assets=False, + verbose=True, + silent=False, + image_max_width=None, + image_max_height=None + ) + + print("โœ… Standard engine test completed") + + # Test 4: Unknown engine (should fallback) + print("\n4๏ธโƒฃ Testing unknown engine (should fallback to standard)...") + + output4 = "/tmp/test_cli_plugin_unknown.html" + md_render_command( + ctx=ctx, + input_file=input_file, + output=output4, + theme=None, + css=None, + edit=True, + insert=False, + engine='unknown-engine', # Unknown engine + editor_theme='github', + keyboard_shortcuts=True, + use_publication_dir=False, + dont_use_publication_dir=False, + nodogtag=False, + ship_assets=None, + no_ship_assets=False, + verbose=True, + silent=False, + image_max_width=None, + image_max_height=None + ) + + print("โœ… Unknown engine test completed") + + print("\n๐ŸŽ‰ All CLI integration tests completed!") + print("\nGenerated files:") + for output_file in [output, output2, output3, output4]: + if Path(output_file).exists(): + size = Path(output_file).stat().st_size + print(f" ๐Ÿ“„ {output_file} ({size:,} bytes)") + + except Exception as e: + print(f"โŒ CLI integration test failed: {e}") + import traceback + traceback.print_exc() + return False + + return True + +if __name__ == "__main__": + success = test_plugin_cli_integration() + sys.exit(0 if success else 1) \ No newline at end of file diff --git a/test_cli_plugin.md b/test_cli_plugin.md new file mode 100644 index 00000000..d3b0aa13 --- /dev/null +++ b/test_cli_plugin.md @@ -0,0 +1,25 @@ +# CLI Plugin Integration Test + +This is a test document to verify that the CLI integration with the testdrive-jsui plugin works correctly. + +## Features to Test + +- Plugin selection via `--engine` parameter +- Default engine selection for edit mode +- Fallback to standard rendering if plugin fails + +## Test Scenarios + +1. **Default behavior**: `markitect md-render --edit test.md` + - Should use testdrive-jsui by default + +2. **Explicit plugin**: `markitect md-render --engine testdrive-jsui --edit test.md` + - Should use testdrive-jsui explicitly + +3. **Standard fallback**: `markitect md-render --engine standard --edit test.md` + - Should use standard CleanDocumentManager + +4. **Unknown engine**: `markitect md-render --engine unknown --edit test.md` + - Should fallback to standard with warning + +This test will verify the plugin infrastructure integration. \ No newline at end of file diff --git a/test_cli_simple.py b/test_cli_simple.py new file mode 100644 index 00000000..77b3a1bd --- /dev/null +++ b/test_cli_simple.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +""" +Test CLI plugin integration by directly calling the core logic +""" + +import sys +from pathlib import Path + +# Add current directory to path for imports +sys.path.insert(0, str(Path(__file__).parent)) + +def test_cli_integration(): + """Test CLI integration logic without Click framework.""" + + print("๐Ÿงช Testing CLI Plugin Integration Logic") + print("=" * 50) + + try: + # Import required components + from markitect.plugins import PluginManager, RenderingEngineManager, RenderingConfig + + # Test input + input_file = "test_cli_plugin.md" + output_file = "/tmp/test_cli_integration.html" + + if not Path(input_file).exists(): + print(f"โŒ Test file {input_file} not found") + return False + + # Read markdown content + content = Path(input_file).read_text(encoding='utf-8') + print(f"๐Ÿ“„ Read test content ({len(content)} characters)") + + # Test 1: Plugin system initialization + print("\n1๏ธโƒฃ Initializing plugin system...") + plugin_manager = PluginManager() + rendering_manager = RenderingEngineManager(plugin_manager) + print(" โœ… Plugin system initialized") + + # Test 2: Engine selection logic (same as CLI) + engine_name = None + edit_mode = True + + # Default engine selection (copied from CLI logic) + if engine_name is None: + if edit_mode: + engine_name = 'testdrive-jsui' # Default to testdrive-jsui for interactive modes + else: + engine_name = 'standard' # Use standard CleanDocumentManager for non-interactive + + print(f" ๐ŸŽฏ Selected engine: {engine_name}") + + # Test 3: Engine loading + print(f"\n2๏ธโƒฃ Loading rendering engine '{engine_name}'...") + rendering_engine = rendering_manager.get_engine(engine_name) + + if rendering_engine is None: + print(f" โŒ Rendering engine '{engine_name}' not found") + return False + + print(f" โœ… Engine loaded: {rendering_engine.metadata.name}") + print(f" ๐Ÿ“ Description: {rendering_engine.metadata.description}") + print(f" ๐ŸŽฏ Supported modes: {rendering_engine.get_supported_modes()}") + + # Test 4: Mode validation + current_mode = 'edit' + if not rendering_engine.validate_mode(current_mode): + print(f" โŒ Engine doesn't support mode '{current_mode}'") + return False + + print(f" โœ… Mode '{current_mode}' is supported") + + # Test 5: Rendering configuration + print(f"\n3๏ธโƒฃ Setting up rendering configuration...") + render_config = RenderingConfig( + asset_base_url="_markitect", + development_mode=False, # Production deployment for CLI usage + output_directory=Path(output_file).parent + ) + print(f" โœ… Configuration created") + print(f" ๐Ÿ“ Output directory: {render_config.output_directory}") + print(f" ๐Ÿ”— Asset base URL: {render_config.asset_base_url}") + + # Test 6: Document rendering + print(f"\n4๏ธโƒฃ Rendering document...") + html_content = rendering_engine.render_document(content, current_mode, render_config) + print(f" โœ… Document rendered ({len(html_content):,} characters)") + + # Test 7: Output writing + print(f"\n5๏ธโƒฃ Writing output file...") + Path(output_file).write_text(html_content, encoding='utf-8') + output_size = Path(output_file).stat().st_size + print(f" โœ… Output written: {output_file} ({output_size:,} bytes)") + + # Test 8: Verification + print(f"\n6๏ธโƒฃ Verifying output...") + if Path(output_file).exists() and output_size > 0: + print(f" โœ… Output file exists and has content") + print(f" ๐ŸŒ Open in browser: file://{Path(output_file).absolute()}") + else: + print(f" โŒ Output file missing or empty") + return False + + print(f"\n๐ŸŽ‰ CLI integration test completed successfully!") + print(f"\n๐Ÿ“Š Summary:") + print(f" Engine: {engine_name}") + print(f" Mode: {current_mode}") + print(f" Input: {input_file} ({len(content)} chars)") + print(f" Output: {output_file} ({output_size:,} bytes)") + + return True + + except Exception as e: + print(f"โŒ CLI integration test failed: {e}") + import traceback + traceback.print_exc() + return False + +if __name__ == "__main__": + success = test_cli_integration() + sys.exit(0 if success else 1) \ No newline at end of file diff --git a/test_complete_integration.py b/test_complete_integration.py new file mode 100644 index 00000000..256175d7 --- /dev/null +++ b/test_complete_integration.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +""" +Complete integration test demonstrating all CLI plugin functionality +""" + +import sys +from pathlib import Path + +# Add current directory to path for imports +sys.path.insert(0, str(Path(__file__).parent)) + +def test_engine_scenarios(): + """Test different engine scenarios.""" + + print("๐Ÿš€ Complete CLI Plugin Integration Test") + print("=" * 60) + + scenarios = [ + ("Default (edit mode)", None, True, False), + ("Explicit testdrive-jsui", "testdrive-jsui", True, False), + ("Standard engine", "standard", True, False), + ("Unknown engine", "unknown-engine", True, False), + ("Default (view mode)", None, False, False), + ] + + results = [] + + for scenario_name, engine, edit, insert in scenarios: + print(f"\n๐Ÿงช Testing: {scenario_name}") + print("-" * 50) + + try: + # Import the core logic components + from markitect.plugins import PluginManager, RenderingEngineManager, RenderingConfig + + input_file = "test_cli_plugin.md" + if not Path(input_file).exists(): + print(f" โŒ Test file {input_file} not found") + continue + + content = Path(input_file).read_text(encoding='utf-8') + + # Initialize plugin system + plugin_manager = PluginManager() + rendering_manager = RenderingEngineManager(plugin_manager) + + # Engine selection logic (copied from CLI) + selected_engine = engine + if selected_engine is None: + # Default engine selection + if edit or insert: + selected_engine = 'testdrive-jsui' # Default to testdrive-jsui for interactive modes + else: + selected_engine = 'standard' # Use standard CleanDocumentManager for non-interactive + + print(f" ๐ŸŽฏ Selected engine: {selected_engine}") + + # Check if engine is available + if selected_engine != 'standard': + rendering_engine = rendering_manager.get_engine(selected_engine) + if rendering_engine is None: + print(f" โš ๏ธ Engine '{selected_engine}' not found, would fallback to standard") + selected_engine = 'standard' + rendering_engine = None + else: + # Check mode support + current_mode = 'edit' if edit else ('insert' if insert else 'view') + if not rendering_engine.validate_mode(current_mode): + print(f" โš ๏ธ Engine '{selected_engine}' doesn't support '{current_mode}', would fallback to standard") + selected_engine = 'standard' + rendering_engine = None + else: + print(f" โœ… Engine supports mode '{current_mode}'") + + # Perform rendering if plugin engine is available + if selected_engine != 'standard' and rendering_engine: + current_mode = 'edit' if edit else ('insert' if insert else 'view') + render_config = RenderingConfig( + asset_base_url="_markitect", + development_mode=False, + output_directory=Path("/tmp") + ) + + html_content = rendering_engine.render_document(content, current_mode, render_config) + + # Save output + output_file = f"/tmp/test_scenario_{scenario_name.lower().replace(' ', '_').replace('(', '').replace(')', '')}.html" + Path(output_file).write_text(html_content, encoding='utf-8') + output_size = Path(output_file).stat().st_size + + print(f" โœ… Rendered using plugin engine ({output_size:,} bytes)") + print(f" ๐Ÿ“„ Output: {output_file}") + + results.append({ + 'scenario': scenario_name, + 'engine': selected_engine, + 'status': 'success', + 'output_file': output_file, + 'size': output_size + }) + + else: + print(f" โ„น๏ธ Would use standard CleanDocumentManager") + results.append({ + 'scenario': scenario_name, + 'engine': 'standard', + 'status': 'fallback', + 'output_file': None, + 'size': 0 + }) + + except Exception as e: + print(f" โŒ Failed: {e}") + results.append({ + 'scenario': scenario_name, + 'engine': selected_engine, + 'status': 'error', + 'output_file': None, + 'size': 0 + }) + + # Summary + print(f"\n๐Ÿ“Š Test Summary") + print("=" * 60) + + successful = sum(1 for r in results if r['status'] == 'success') + fallback = sum(1 for r in results if r['status'] == 'fallback') + failed = sum(1 for r in results if r['status'] == 'error') + + for result in results: + status_icon = { + 'success': 'โœ…', + 'fallback': '๐Ÿ”„', + 'error': 'โŒ' + }[result['status']] + + size_info = f"({result['size']:,} bytes)" if result['size'] > 0 else "" + print(f" {status_icon} {result['scenario']:<25} โ†’ {result['engine']:<15} {size_info}") + + print(f"\n๐ŸŽฏ Results: {successful} successful, {fallback} fallback, {failed} failed") + + if successful > 0: + print(f"\n๐ŸŒ Generated files can be opened in browser:") + for result in results: + if result['output_file']: + print(f" file://{Path(result['output_file']).absolute()}") + + return failed == 0 + +if __name__ == "__main__": + success = test_engine_scenarios() + sys.exit(0 if success else 1) \ No newline at end of file diff --git a/test_plugin_discovery.py b/test_plugin_discovery.py new file mode 100644 index 00000000..feeb004a --- /dev/null +++ b/test_plugin_discovery.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +""" +Test plugin discovery and basic integration +""" + +import sys +from pathlib import Path + +# Add current directory to path for imports +sys.path.insert(0, str(Path(__file__).parent)) + +def test_plugin_discovery(): + """Test that the plugin system can discover testdrive-jsui.""" + print("๐Ÿ” Testing Plugin Discovery") + print("=" * 50) + + try: + # Test basic plugin imports + print("1๏ธโƒฃ Testing plugin imports...") + from markitect.plugins import PluginManager, RenderingEngineManager, RenderingConfig + print(" โœ… Plugin classes imported successfully") + + # Test plugin manager initialization + print("\n2๏ธโƒฃ Testing plugin manager initialization...") + plugin_manager = PluginManager() + print(" โœ… Plugin manager initialized") + + # Test rendering engine manager + print("\n3๏ธโƒฃ Testing rendering engine manager...") + rendering_manager = RenderingEngineManager(plugin_manager) + print(" โœ… Rendering engine manager initialized") + + # List available engines + print("\n4๏ธโƒฃ Testing engine discovery...") + engines = rendering_manager.list_engines() + print(f" ๐Ÿ“‹ Found engines: {engines}") + + # Test testdrive-jsui specifically + print("\n5๏ธโƒฃ Testing testdrive-jsui engine...") + engine = rendering_manager.get_engine('testdrive-jsui') + if engine: + print(f" โœ… TestDrive JSUI engine found!") + print(f" ๐Ÿ“ Name: {engine.metadata.name}") + print(f" ๐Ÿ“„ Description: {engine.metadata.description}") + print(f" ๐ŸŽฏ Supported modes: {engine.get_supported_modes()}") + + # Test render capabilities + print("\n6๏ธโƒฃ Testing render capabilities...") + test_content = "# Test\n\nThis is a test document." + + config = RenderingConfig( + asset_base_url="_markitect", + development_mode=False, + output_directory=Path("/tmp") + ) + + html_output = engine.render_document(test_content, 'edit', config) + print(f" โœ… Rendered HTML ({len(html_output):,} characters)") + + # Save test output + test_file = Path("/tmp/plugin_discovery_test.html") + test_file.write_text(html_output) + print(f" ๐Ÿ“„ Saved test output: {test_file}") + + else: + print(" โŒ TestDrive JSUI engine not found") + return False + + print(f"\n๐ŸŽ‰ Plugin discovery test completed successfully!") + return True + + except Exception as e: + print(f"โŒ Plugin discovery test failed: {e}") + import traceback + traceback.print_exc() + return False + +if __name__ == "__main__": + success = test_plugin_discovery() + sys.exit(0 if success else 1) \ No newline at end of file