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:
452
docs/PLUGIN_SYSTEM.md
Normal file
452
docs/PLUGIN_SYSTEM.md
Normal file
@@ -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
|
||||||
|
<script id="markitect-config" type="application/json">
|
||||||
|
{
|
||||||
|
"markdownContent": "# Document content...",
|
||||||
|
"mode": "edit",
|
||||||
|
"theme": "github",
|
||||||
|
"keyboardShortcuts": true,
|
||||||
|
"originalFilename": "document.md",
|
||||||
|
"version": "Markitect v0.8.1"
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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.*
|
||||||
@@ -2033,6 +2033,8 @@ def md_list_command(ctx, output_format, names_only):
|
|||||||
help='Open in interactive edit mode with stable section editing')
|
help='Open in interactive edit mode with stable section editing')
|
||||||
@click.option('--insert', is_flag=True,
|
@click.option('--insert', is_flag=True,
|
||||||
help='Open in interactive insert mode with heading protection (levels 1-3 read-only)')
|
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',
|
@click.option('--editor-theme', default='github',
|
||||||
type=click.Choice(['github', 'monokai', 'tomorrow', 'dark']),
|
type=click.Choice(['github', 'monokai', 'tomorrow', 'dark']),
|
||||||
help='Editor theme for live edit mode (default: github)')
|
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,
|
@click.option('--image-max-height', type=str, default=None,
|
||||||
help='Maximum height for images (default: 20cm, supports px, em, %, cm, in, etc.)')
|
help='Maximum height for images (default: 20cm, supports px, em, %, cm, in, etc.)')
|
||||||
@click.pass_context
|
@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,
|
keyboard_shortcuts, use_publication_dir, dont_use_publication_dir, nodogtag,
|
||||||
ship_assets, no_ship_assets, verbose, silent, image_max_width, image_max_height):
|
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
|
# For directory output, ship to the same directory as the HTML file
|
||||||
_ship_assets(input_path, output_path.parent, verbose, silent)
|
_ship_assets(input_path, output_path.parent, verbose, silent)
|
||||||
|
|
||||||
# Initialize clean document manager
|
# Determine rendering engine to use
|
||||||
from markitect.clean_document_manager import CleanDocumentManager
|
if engine is None:
|
||||||
doc_manager = CleanDocumentManager(config.get('db_manager'))
|
# 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
|
# Render the file
|
||||||
if edit:
|
if edit:
|
||||||
# Edit mode - generate HTML with editing capabilities
|
if engine != 'standard':
|
||||||
result = doc_manager.render_file(input_file, str(output_path),
|
# Plugin-based rendering for edit mode
|
||||||
template=theme, css=css,
|
try:
|
||||||
edit_mode=True,
|
# Read markdown content
|
||||||
editor_theme=editor_theme,
|
content = input_path.read_text(encoding='utf-8')
|
||||||
keyboard_shortcuts=keyboard_shortcuts,
|
|
||||||
nodogtag=nodogtag,
|
# Configure rendering
|
||||||
image_max_width=final_image_max_width,
|
render_config = RenderingConfig(
|
||||||
image_max_height=final_image_max_height)
|
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:
|
if not silent:
|
||||||
click.echo(f"✅ Rendered with INTERACTIVE editing mode to: {output_path}")
|
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"Theme: {theme or 'default'}")
|
||||||
click.echo(f"CSS: {css or 'default'}")
|
click.echo(f"CSS: {css or 'default'}")
|
||||||
elif insert:
|
elif insert:
|
||||||
# Insert mode - generate HTML with insert capabilities and heading protection
|
if engine != 'standard':
|
||||||
result = doc_manager.render_file(input_file, str(output_path),
|
# Plugin-based rendering for insert mode
|
||||||
template=theme, css=css,
|
try:
|
||||||
insert_mode=True,
|
# Read markdown content
|
||||||
editor_theme=editor_theme,
|
content = input_path.read_text(encoding='utf-8')
|
||||||
keyboard_shortcuts=keyboard_shortcuts,
|
|
||||||
nodogtag=nodogtag,
|
# Configure rendering
|
||||||
image_max_width=final_image_max_width,
|
render_config = RenderingConfig(
|
||||||
image_max_height=final_image_max_height)
|
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:
|
if not silent:
|
||||||
click.echo(f"✅ Rendered with INTERACTIVE insert mode to: {output_path}")
|
click.echo(f"✅ Rendered with INTERACTIVE insert mode to: {output_path}")
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ class RenderingEngineManager:
|
|||||||
|
|
||||||
def _discover_rendering_engines(self):
|
def _discover_rendering_engines(self):
|
||||||
"""Discover rendering engine plugins."""
|
"""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()
|
all_plugins = self.plugin_manager.discover_plugins()
|
||||||
|
|
||||||
for plugin_name, plugin_info in all_plugins.items():
|
for plugin_name, plugin_info in all_plugins.items():
|
||||||
@@ -197,6 +197,22 @@ class RenderingEngineManager:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"⚠️ Failed to load rendering engine {plugin_name}: {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]:
|
def get_engine(self, name: str) -> Optional[RenderingEnginePlugin]:
|
||||||
"""Get a rendering engine by name."""
|
"""Get a rendering engine by name."""
|
||||||
return self._engines.get(name)
|
return self._engines.get(name)
|
||||||
|
|||||||
131
markitect/templates/edit-mode.html
Normal file
131
markitect/templates/edit-mode.html
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{title}</title>
|
||||||
|
{css_content}
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"
|
||||||
|
onload="window.markitectMarkedLoaded = true"
|
||||||
|
onerror="window.markitectMarkedError = true"></script>
|
||||||
|
</head>
|
||||||
|
<body class="{mode_class}">
|
||||||
|
|
||||||
|
<div id="markdown-content"></div>
|
||||||
|
|
||||||
|
<!-- Configuration Data Interface - ONLY place where Python data enters JavaScript -->
|
||||||
|
<script id="markitect-config" type="application/json">{config_json}</script>
|
||||||
|
|
||||||
|
<!-- Pure Static JavaScript Components - Embedded inline to avoid path issues -->
|
||||||
|
<script>
|
||||||
|
{js_config_loader}
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
{js_debug_system}
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
{js_section_manager}
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
{js_debug_panel}
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
{js_document_controls}
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
{js_dom_renderer}
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
{js_control_base}
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
{js_contents_control}
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
{js_status_control}
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
{js_debug_control}
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
{js_edit_control}
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
{js_main}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Initialization Script -->
|
||||||
|
<script>
|
||||||
|
// Clean initialization - no Python-generated code!
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
console.log('🎯 DOM loaded, starting Markitect initialization...');
|
||||||
|
|
||||||
|
// Wait for configuration to be ready before initializing
|
||||||
|
if (window.markitectConfig) {
|
||||||
|
window.markitectConfig.waitForReady(function() {
|
||||||
|
console.log('🎯 Configuration ready, initializing ' + window.markitectConfig.mode + ' mode...');
|
||||||
|
|
||||||
|
// Initialize edit/insert capabilities
|
||||||
|
if (window.markitectConfig.isEditMode || window.markitectConfig.isInsertMode) {
|
||||||
|
try {
|
||||||
|
console.log('🚀 Initializing clean ' + window.markitectConfig.mode + ' capabilities...');
|
||||||
|
|
||||||
|
// Initialize main application
|
||||||
|
if (typeof MarkitectMain !== 'undefined' && MarkitectMain.initialize) {
|
||||||
|
MarkitectMain.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('✅ Clean ' + window.markitectConfig.mode + ' mode active - click any section to edit');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Clean ' + window.markitectConfig.mode + ' mode failed to initialize:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error('❌ Configuration system not available');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if modular components are being used for content rendering
|
||||||
|
if (typeof SectionManager !== 'undefined') {
|
||||||
|
console.log('✓ Modular components detected - using modular architecture');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback content rendering if modular components failed
|
||||||
|
const contentDiv = document.getElementById('markdown-content');
|
||||||
|
if (contentDiv) {
|
||||||
|
if (typeof marked !== 'undefined') {
|
||||||
|
try {
|
||||||
|
const html = marked.parse(window.markitectConfig.markdownContentWithDogtag);
|
||||||
|
const htmlWithTargetBlank = html.replace(/<a href="([^"]*)"([^>]*)>/g, '<a href="$1" target="_blank"$2>');
|
||||||
|
contentDiv.innerHTML = htmlWithTargetBlank;
|
||||||
|
console.log('✓ Content rendered successfully with fallback');
|
||||||
|
} catch (error) {
|
||||||
|
contentDiv.innerHTML = '<p>Error rendering markdown: ' + error.message + '</p>';
|
||||||
|
console.error('Content rendering failed:', error.message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Basic fallback without marked.js
|
||||||
|
const fallbackHtml = window.markitectConfig.markdownContent
|
||||||
|
.replace(/^# (.*$)/gim, '<h1>$1</h1>')
|
||||||
|
.replace(/^## (.*$)/gim, '<h2>$1</h2>')
|
||||||
|
.replace(/^### (.*$)/gim, '<h3>$1</h3>')
|
||||||
|
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
||||||
|
.replace(/\*(.*?)\*/g, '<em>$1</em>')
|
||||||
|
.replace(/\n\n/g, '<br><br>')
|
||||||
|
.replace(/\n/g, '<br>');
|
||||||
|
contentDiv.innerHTML = '<div style="white-space: pre-wrap;">' + fallbackHtml + '</div>';
|
||||||
|
console.warn('Content rendered with basic fallback parser');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('load', function() {
|
||||||
|
if (window.markitectMarkedError) {
|
||||||
|
console.error('CDN library failed to load - network or firewall blocking marked.js');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
171
test_cli_integration.py
Normal file
171
test_cli_integration.py
Normal file
@@ -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)
|
||||||
25
test_cli_plugin.md
Normal file
25
test_cli_plugin.md
Normal file
@@ -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.
|
||||||
121
test_cli_simple.py
Normal file
121
test_cli_simple.py
Normal file
@@ -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)
|
||||||
152
test_complete_integration.py
Normal file
152
test_complete_integration.py
Normal file
@@ -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)
|
||||||
80
test_plugin_discovery.py
Normal file
80
test_plugin_discovery.py
Normal file
@@ -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)
|
||||||
Reference in New Issue
Block a user